RFC Viewer開発を通して学ぶ!! iOS開発のパターン化

Post on 15-May-2015

685 views 1 download

description

RFC Viewer開発を通して学ぶ!! iOS開発のパターン化 http://atnd.org/events/43950 http://www.zusaar.com/event/1077005

Transcript of RFC Viewer開発を通して学ぶ!! iOS開発のパターン化

RFC Viewer開発を通して学ぶ!!���iOS開発のパターン化

2013.10.22 Bitz Co., Ltd. 村上幸雄 @m_yukio

• 村上幸雄 @m_yukio • ビッツ有限会社 http://www.bitz.co.jp/ • 節電対策, Bitz NowPlaying, RFC Viewer(申請中) • UNIX系ソフトハウスと組み込みシステムのベンチャーを経て2001年に独立。���プラットフォームを限定せずに何にでも挑戦してきましたが、最近はiOSアプリ開発に注力しています。

自己紹介

本日の内容

•  iOSアプリケーションについて •  iOSアプリケーション開発の流れ •  iOSアプリケーション開発のパターン化 •  RFC Viewerの製作

iOSアプリケーション開発について

• Mac OS XのCocoaの機能縮小版。ただし、独自のUIや改良個所があるので、単なるサブセットでない。

• 使用禁止APIがあるが、C言語によるネイティブ開発が行える。

•  iOSはUNIX系のOSで、32bit/64bit対応。

Objective-C

•  C言語にオブジェクト指向の拡張を施したもの。

• Objective-C 2.0から、より使いやすくする為の拡張が行われたが、基本は簡素な言語。

• 歴史は非常に古い。

@interface クラス名 : スーパークラス名 { 型名 インスタンス変数名; :}メソッド宣言; :@end@implementation クラス名メソッド定義{ 処理内容} :@end

@protocol プロトコル名メソッド宣言; :@end@interface クラス名 : スーパークラス名 <プロトコル名>:@end

@interface クラス名 (カテゴリ名)メソッド宣言; :@end

#import <Cocoa/Cocoa.h>@interface Song : NSObject { id title;}- (id)title;- (void)setTitle:(id)aTitle;@end

クラス定義 インターフェイス #include クラス名 親クラス

インスタンス変数

メソッド

#import “Song.h”@implementation Song- (id)title{return title;}- (void)setTitle:(id)aTitle{[title autorelease];title = [aTitle retain];}@end

クラス定義 クラス実装 #include

実装するクラス名

インスタンス変数を返す

以前の変数をオートリリースし

渡された変数を所有している

#import “NSString.h”@interface NSString (Hello)- (NSString *)helloString;@end

カテゴリ定義 カテゴリ名

拡張するクラス名

#import “Hello.h”@implementation NSString (Hello)- (NSString *)helloString{ return @”hello, world!”;}@end

カテゴリ実装 カテゴリ名 拡張するクラス名

@protocol Playable- (void)play;- (void)stop;@end

形式プロトコル プロトコル名

#import <Cocoa/Cocoa.h>#import “Playable.h”@interface Song : NSObject <Playable> { id title;}- (id)title;- (void)setTitle;@end

プロトコルの採用

if ([song conformsToProtocol:@protocol(Playable)]) { [song play];}else {}

プロトコル準拠の確認

+ (id)alloc;NSObject *obj = [NSObject alloc];- (void)display;[obj display];

クラスメソッド

インスタンスメソッド クラス

インスタンス

メモリ管理 /* 生成(retain) */NSString *title = [[NSString alloc] initWithString:@”hello”];/* 解放 */[title release];/* autorelease */for (;;) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *s1 = [[NSString alloc] initWithString:@”hello”]; [s1 autorelease]; NSString *s2 = @”hello”; NSString *s3 = [NSString stringWithString:@”hello”]; [pool release];}

#include “MyClass.h” #import “MyClass.h”

#ifndef _H_MyClass#define _H_MyClass@interface MyClass : NSObject:@end#endif

#import “MyClass.h”@interface MyClass : NSObject:@end

#import

Xcode 5 から @import が利用できるようになった。(Modules)例)@import UIKit;@import UIKit.UIView;

@interface Document : NSObject!@property (strong, nonatomic) NSString *version; !!- (id)init; !- (void)dealloc; !@end !!@interface Document () !@property (strong, nonatomic) NSString *text; !- (void)_init; !@end!!@implementation Document !!@synthesize version = _version; !@synthesize text = _text; !!- (id)init !{ ! self = [super init]; ! if (self) { ! [self _init]; ! } ! return self; !} !!- (void)dealloc !{ ! self.version = nil; ! self.text = nil; !} !!- (void)_init !{ ! _version = @”1.0”; ! _text = @”This is a pen.”; !} !!@end !

Objective-C 2.0以降のクラス実装のパターン

プロパティを利用

プライベート宣言はクラス拡張で

ARC有無に関わらず汎用的に使える

@property (nonatmic) NSString *value;

宣言済みプロパティ 同等

- (NSString *)value;- (void)setValue:(NSString *)newValue;

@synthesize value = _value;

@interface MyClass : NSObject@property (nonatmic) NSString *_value;@end

自動に宣言

@implementation MyClass- (NSString *)value { return _value; }- (void)setValue:(NSString *)newValue{ [_value release]; _value = [newValue retain];}@end

ARCなしの場合は こんな感じ?

@interface Song : NSObject { id _title;}- (id)title;- (void)setTitle:(id)title;@end@implementation Song- (id)title{ return _title;}- (void)setTitle:(id)title{ [title release]; _title = [title retain];}@end

プロパティ @interface Song : NSObject@property (strong, nonatmic) NSString *title;@end@implementation Song@synthesize title = _title;@end

プロパティを利用すれば、ARCの利用有無に関わらず、同じ記述が出来る。

Cocoa touch

•  iOSアプリ開発にはC言語の知識も必要。C++も知っておくと便利。

• Objective-Cは簡素。Cocoa touchの知識の有無が重要。

Machのタスクとスレッド

実行状態 プロセスのデータと保護

メモリ管理 シグナル管理

ディスクリプタ管理 タイミングと統計情報

リソース制御 UNIXプロセス Machタスク

プロセスのデータと保護 メモリ管理 シグナル管理

ディスクリプタ管理 タイミングと統計情報

リソース制御

Machスレッド

実行状態 実行状態 実行状態 実行状態

Run Loop

Events

Timers

Run Loop Application

開発の流れ

Apple Developerサイトにアクセス http://developer.apple.com/jp/

iOS Developer Programに参加

Xcodeを入手し、インストールする

シミュレータ/実機で開発

実機でAdHoc版の動作を確認

Storeに申請

休憩

WWDCノススメ

• 方向性を肌で感じる。 • Apple技術者から直接情報を得られる。 • フィードバックも期待できる。 • 全体像をつかめる。とっかかりとなる。 • 参加者同士の交流。 • サードパーティとの交流。 • @twitterapi meetupなど

Appleが年に一回世界中の開発者を集めて、新技術の紹介や開発にあたっての細かな技術を説明する為のイベント。

パターン化

パターン化について

• 自分の型を持つ事は、コードの再利用はもちろん、継続的な改善が期待できる。

• 以下をバイブルとした。���『iOS開発におけるパターンによるオートマティズム』木下誠 著

Model-View-Controllerパターン

View

Controller

Model

従来型

状態取得

通知 更新

UI操作 更新

Cocoa

View

Controller

Model

更新 UI操作

通知 更新

RFCViewerでのMVC

View

データ

更新

UI操作

通知

更新

AppDelegate

Document

ViewController

ViewController

ViewController View View

更新 通知

Model(データ)を管理するコントローラとしてDocumentを用意。

Controller

通信/並列処理のパターン

Connector ResponseParser ViewController

ResponseParser

ResponseParser

要求/応答のパターン •  1対n(お互い知らない) • 通知センター

•  1対n(通知元を知っている) • キー値監視

•  1対1

• デリゲート •  Blocks

通知センター /* 通知元 */ !NSString *ConnectorDidBeginRfc = @"ConnectorDidBeginRfc"; !!![[NSNotificationCenter defaultCenter] postNotificationName:ConnectorDidBeginRfc! object:self userInfo:userInfo]; !!!/* 受信メソッドの登録 */ ![[NSNotificationCenter defaultCenter] addObserver:self ! selector:@selector(_connectorDidBeginRfc:) ! name:ConnectorDidBeginRfc! object:nil]; !!!/** ! * 受信メソッド ! */ !- (void)_connectorDidBeginRfc:(NSNotification*)notification !{ ! RFCResponseParser *parser; ! parser = [[notification userInfo] objectForKey:@"parser"]; !! ... !} !

キー値監視 /* Connectorの networkAccessingキーを監視 */ ![[Connector sharedConnector] addObserver:self ! forKeyPath:@"networkAccessing"! options:0 ! context:NULL]; !!!/* 値の変更を受信 */ !- (void)observeValueForKeyPath:(NSString*)keyPath ! ofObject:(id)object ! change:(NSDictionary*)change ! context:(void*)context !{ ! if ([keyPath isEqualToString:@"networkAccessing"]) { ! [self _updateNetworkActivity]; ! } !} !!!/* 通知 */ ![self willChangeValueForKey:@"networkAccessing"]; ![self didChangeValueForKey:@"networkAccessing"]; !

デリゲート

/* プロトコル */ !@protocol RFCResponseParserDelegate <NSObject> !- (void)parser:(RFCResponseParser*)parser didReceiveResponse:(NSURLResponse*)response; !@end !!!/* デリゲートのメソッドを呼び出す */ !if ([self.delegate respondsToSelector:@selector(parser:didReceiveResponse:)]) { ! [self.delegate parser:self didReceiveResponse:response]; !} !

Blocks /* Blocks定義 */ !typedef void (^RFCResponseParserCompletionHandler)(RFCResponseParser *parser); !!!/* 要求メソッドでBlocksを受け取る */ !- (void)rfcIndexWithCompletionHandler:(RFCResponseParserCompletionHandler)completionHandler; !!!!/* Blocksの呼び出し */ !if (parser.completionHandler) { ! parser.completionHandler(parser); !} !!!/* Blockの生成 */ !__block MasterViewController * __weak blockWeakSelf = self; ![[Connector sharedConnector] rfcIndexWithCompletionHandler:^(RFCResponseParser *parser) { ! MasterViewController *tempSelf = blockWeakSelf; ! if (! tempSelf) return; ! ! [Document sharedDocument].indexArray = parser.indexArray; ! [tempSelf _updateSectionIndexArray]; ! [tempSelf.tableView reloadData]; !}]; !

休憩

Cocoa勉強会

http://www.cocoa-study.com/

RFCViewerの製作

RFC

• 以下にインデックスがある。���http://www.rfc-editor.org/rfc/rfc-index.txt

•  RFC文書のURL ���http://www.ietf.org/rfc/rfc四桁の数値.txt���例)http://www.ietf.org/rfc/rfc0821.txt

RFCとは

•  TCP/IPプロトコルの詳細が記述されているレポート。以下に例をあげる。 •  793: TCPプロトコルの仕様。 •  822: 電子メールの形式。

ファイル構成 アプリケーション・デリゲート

テーブル(RFCのインデックス)

詳細画面(RFC文書表示)

データ

通信(コネクタ/パーサ)

@implementation AppDelegate- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ /* ConnectのnetworkAccessingキーを監視 */ [[Connector sharedConnector] addObserver:self forKeyPath:@"networkAccessing" options:0 context:NULL]; return YES;}- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context{ /* networkAccessingキーに変化あり */ if ([keyPath isEqualToString:@"networkAccessing"]) { [self _updateNetworkActivity]; }}- (void)_updateNetworkActivity{ /* 通信中ならインジケータを表示 */ [UIApplication sharedApplication].networkActivityIndicatorVisible = [Connector sharedConnector].networkAccessing;}@end

通信中インジケータ ココ アプリのデリゲート

真:表示 偽:非表示

if (! urlRequest) { /* NSURLRequestインスタンスの生成失敗 */ self.networkState = kRFCNetworkStateError; self.error = [self _errorWithCode: kRFCResponseParserGenericError localizedDescription: @"NSURLRequestの生成に失敗しました。"]; return; } /* 受信データの格納バッファの用意 */ self.downloadedData = [[NSMutableData alloc] init]; /* NSURLConnectionインスタンスの生成 (並列処理の為のキューを設定) */ self.urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO]; [self.urlConnection setDelegateQueue:self.queue]; /* 通信中インジケータの更新 */ [self willChangeValueForKey:@"networkState"]; self.networkState = kRFCNetworkStateInProgress; [self didChangeValueForKey:@"networkState"]; /* 通信開始 */ [self.urlConnection start];}

- (void)parse{ DBGMSG(@"%s", __func__); NSString *urlString = nil; /* 通信先 */ if (self.index == 0) { /* 目次文書 */ urlString = [Document sharedDocument] .indexUrlString; } else { /* 指定された番号のRFC文書 */ urlString = [[Document sharedDocument] rfcUrlStringWithIndex:self.index]; } /* URLからNSURLRequestのインスタンスを生成 */ NSURLRequest *urlRequest = nil; if (urlString) { NSURL *url; url = [NSURL URLWithString:urlString]; if (url) { urlRequest = [NSURLRequest requestWithURL:url]; } } DBGMSG(@"%s urlString(%@)", __func__, urlString);

通信開始

前のスライド

- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response{ /* デリゲートに通知 */ if ([self.delegate respondsToSelector:@selector(parser:didReceiveResponse:)]) { /* 主スレッドで実行 */ dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate parser:self didReceiveResponse:response]; }); }}- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data{ /* 受信したデータをバッファに格納 */ [self.downloadedData appendData:data]; /* デリゲートに通知 */ if ([self.delegate respondsToSelector:@selector(parser:didReceiveData:)]) { /* 主スレッドで実行 */ dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate parser:self didReceiveData:data]; }); }}

受信データの格納

- (void)connectionDidFinishLoading:(NSURLConnection*)connection{ /* 通信中インジケータの更新 */ [self willChangeValueForKey:@"networkState"]; self.networkState = kRFCNetworkStateFinished; [self didChangeValueForKey:@"networkState"]; /* 目次文書 */ if (self.index == 0) { /* 受信データのパース */ [self _parseIndexArray]; } /* 主スレッドで実行させる */ dispatch_async(dispatch_get_main_queue(), ^{ [self _notifyParserDidFinishLoading]; }); self.urlConnection = nil;}- (void)_notifyParserDidFinishLoading{ /* デリゲートに通知 */ if ([self.delegate respondsToSelector:@selector(parserDidFinishLoading:)]) { [self.delegate parserDidFinishLoading:self]; }}

通信終了

- (void)rfcWithIndex:(NSUInteger)index completionHandler:(RFCResponseParserCompletionHandler) completionHandler{ BOOL networkAccessing = self.networkAccessing; /* パーサのインスタンスを生成 */ RFCResponseParser *parser = [[RFCResponseParser alloc] init]; parser.index = index; parser.queue = self.queue; parser.delegate = self; parser.completionHandler = completionHandler; /* 通信開始 */ [parser parse]; if (parser.error) { /* 通信開始エラー */ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:parser forKey:@"parser"]; [[NSNotificationCenter defaultCenter] postNotificationName:ConnectorDidFinishRfc object:self userInfo:userInfo]; if (parser.completionHandler) { parser.completionHandler(parser); } return; }

通信を要求する

/* 通信中パーサを配列に格納 */ [self.parsers addObject:parser]; /* 通信中インジケータの更新 */ if (networkAccessing != self.networkAccessing) { [self willChangeValueForKey:@"networkAccessing"]; [self didChangeValueForKey:@"networkAccessing"]; } /* 通信開始を通知 */ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:parser forKey:@"parser"]; [[NSNotificationCenter defaultCenter] postNotificationName:ConnectorDidBeginRfc object:self userInfo:userInfo];}

- (void)parserDidFinishLoading:(RFCResponseParser *)parser{ if ([self.parsers containsObject:parser]) { [self _notifyRfcStatusWithParser:parser]; }}- (void)_notifyRfcStatusWithParser:(RFCResponseParser *)parser{ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setObject:parser forKey:@"parser"]; /* 通信完了を通知(通知センター) */ [[NSNotificationCenter defaultCenter] postNotificationName:ConnectorDidFinishRfc object:self userInfo:userInfo]; /* 通信完了を通知(Blocks) */ if (parser.completionHandler) { parser.completionHandler(parser); } /* 通信中インジケータの更新 */ [self willChangeValueForKey:@"networkAccessing"]; [self.parsers removeObject:parser]; [self didChangeValueForKey:@"networkAccessing"];}

応答を受け取る

- (void)viewDidLoad{ [super viewDidLoad]; if (self.rfc.text) { [self configureView]; } /* RFC文書の取得要求を投げる */ __block DetailViewController * __weak blockWeakSelf = self; [[Connector sharedConnector] rfcWithIndex:[self.rfc.rfcNumber integerValue] completionHandler:^(RFCResponseParser *parser) { /* 応答を受けた際の処理 */ DetailViewController *tempSelf = blockWeakSelf; if (! tempSelf) return; if (parser.rfc) tempSelf.rfc.text = parser.rfc; [tempSelf configureView]; }];}

要求を投げる

NSURLSession

iOS7で追加された機能で、NSURLConnection に変わるもの。

Demo

AdHoc

Q & A