55
RFC Viewer開発を通して学ぶ!! iOS開発のパターン化 2013.10.22 Bitz Co., Ltd. 村上幸雄 @m_yukio

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

Embed Size (px)

DESCRIPTION

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

Citation preview

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

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

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

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

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

自己紹介

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

本日の内容

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

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

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

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

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

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

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

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

Objective-C

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

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

• 歴史は非常に古い。

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

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

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

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

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

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

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

インスタンス変数

メソッド

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

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

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

実装するクラス名

インスタンス変数を返す

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

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

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

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

カテゴリ定義 カテゴリ名

拡張するクラス名

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

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

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

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

@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 {}

プロトコル準拠の確認

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

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

クラスメソッド

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

インスタンス

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

メモリ管理 /* 生成(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];}

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

#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

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

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

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

@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有無に関わらず汎用的に使える

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

@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なしの場合は こんな感じ?

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

@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の利用有無に関わらず、同じ記述が出来る。

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

Cocoa touch

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

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

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

Machのタスクとスレッド

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

メモリ管理 シグナル管理

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

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

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

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

リソース制御

Machスレッド

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

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

Run Loop

Events

Timers

Run Loop Application

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

開発の流れ

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

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

iOS Developer Programに参加

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

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

実機でAdHoc版の動作を確認

Storeに申請

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

休憩

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

WWDCノススメ

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

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

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

パターン化

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

パターン化について

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

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

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

Model-View-Controllerパターン

View

Controller

Model

従来型

状態取得

通知 更新

UI操作 更新

Cocoa

View

Controller

Model

更新 UI操作

通知 更新

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

RFCViewerでのMVC

View

データ

更新

UI操作

通知

更新

AppDelegate

Document

ViewController

ViewController

ViewController View View

更新 通知

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

Controller

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

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

Connector ResponseParser ViewController

ResponseParser

ResponseParser

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

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

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

•  1対1

• デリゲート •  Blocks

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

通知センター /* 通知元 */ !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"]; !! ... !} !

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

キー値監視 /* 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"]; !

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

デリゲート

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

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

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]; !}]; !

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

休憩

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

Cocoa勉強会

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

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

RFCViewerの製作

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

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

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

RFCとは

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

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

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

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

詳細画面(RFC文書表示)

データ

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

Page 43: RFC Viewer開発を通して学ぶ!! iOS開発のパターン化
Page 44: RFC Viewer開発を通して学ぶ!! iOS開発のパターン化
Page 45: RFC Viewer開発を通して学ぶ!! iOS開発のパターン化

@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

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

真:表示 偽:非表示

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

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);

通信開始

前のスライド

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

- (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]; }); }}

受信データの格納

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

- (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]; }}

通信終了

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

- (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];}

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

- (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"];}

応答を受け取る

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

- (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]; }];}

要求を投げる

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

NSURLSession

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

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

Demo

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

AdHoc

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

Q & A