Upload
yukio-murakami
View
687
Download
1
Embed Size (px)
DESCRIPTION
RFC Viewer開発を通して学ぶ!! iOS開発のパターン化 http://atnd.org/events/43950 http://www.zusaar.com/event/1077005
Citation preview
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