*

iOSプログラミングのキモ(iOS7から使えるようになったマルチタスク機能、NSURLSessionはこう使え!)

公開日: : Apple, iOS, iPad, iPhone, Objective-C, XCode

今日はQTubeのソースに関する話題ではなく、現在開発中のアプリで使っているNSURLSessionについて書いておきます。

弊社ではFileQというファイル転送サービスを運営しています。FileQはPC向けのサービスなんですが、アクセスログを見るとスマホからのアクセスもちらほらあります、スマホからでも送信は出来るのですが、受信が上手く出来なかったりUIがスマホ向けではないので 使いづらかったりします。

そんなことで スマホ向けFileQ(とりあえずiPhone版)を開発中なのですが、アプリではiOS7から使えるようになったマルチタスク機能NSURLSessionを使っています。NSURLSessionについての解説は 海外・国内でも割りと通り一遍の解説しかなく サンプルも少ない状態です。今回 実際に使って得た ノウハウを書いておきます。

NSURLSessionの外観については こちらこちらが詳しいので、ここでは外観については書きません。

NSURLSession と NSURLSessionUploadTask(親クラス:NSURLSessionTask)が今回の肝クラスです。

使い方は、まず最初に NSURLSession をインスタンス化します。

NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration:@"適当な文字列"];
urlSession_ = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];

大事な点その1

  1. NSURLSessionConfiguration を backgroundSessionConfiguration で生成する。
  2. NSURLSessionはクラス変数として生成して、アプリがメモリ上にあり続ける間 インスタンス として使いまわすようにする。
  3. delegateメソッドはちゃんと実装しておく。

特に2番目と3番目は重要で、NSURLSessionをオート変数として定義してしまうと、EXC_BAD_ACCESS が発生します。EXC_BAD_ACCESS はメモリ解放したインスタンスに対してアクセスしようとすると発生する原因追求がめんどうなエラーです。また 3番めは NSURLSessionTaskDelegate プロトコルを実装したインスタンスを示しますが、ここで実装すべき メソッドを端折ったりすると また EXC_BAD_ACCESS が発生したりしてやっかいです。

NSURLSessionUploadTask を使う場合、重要で実装すべきメソッドは次の2つです。

  1. – (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
  2. – (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error

1つ目は データー転送中の状態を教えてくれるメソッドです、2つ目は転送が完了した時に呼ばれるメソッドです。
これらのメソッドを実装しておくことで、転送の途中・完了をアプリが知ることが出来、適切な処理を行うことが出来ます。
IMG_1256

IMG_1255

IMG_1254

IMG_1253

写真はdelegateで受け取った情報を画面に表示してみた例です。

実際にタスクをスタートするコードはこんなかんじです。

// クッキーをセットする、ここは必ずしも必要ではない
NSDictionary *properties = [[NSMutableDictionary alloc] init];
[properties setValue:cookieSid forKey:NSHTTPCookieValue];
[properties setValue:@"fileuploadSID" forKey:NSHTTPCookieName];
[properties setValue:@"fileq.lisonal.com" forKey:NSHTTPCookieDomain];
[properties setValue:@"/" forKey:NSHTTPCookiePath];

NSHTTPCookie *cookie = [[NSHTTPCookie alloc] initWithProperties:properties];
NSMutableArray *cookies = [[NSMutableArray alloc]init];
[cookies addObject:cookie];
NSDictionary * headers = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];

// request を生成する
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:FILEQ_UPLOAD_URL]];
[request setHTTPMethod:@"POST"];	//メソッドをPOSTに指定します
[request setAllHTTPHeaderFields:headers];

// アップロードするファイルのPATHを file:// 形式で文字列を作る
NSString *urlStr = [NSString stringWithFormat:@"file://%@", filePath];

// NSURLSessionUploadTask は クラス変数として残しておき バックグラウンド送信直後に開放されてしまうのを防ぐ
uploadTask_ = [urlSession_ uploadTaskWithRequest:request fromFile:[NSURL URLWithString:urlStr]];

// アップロードをスタートする
[uploadTask_ resume];

大事な点その2

  1. NSURLSessionUploadTask は クラス変数として残しておき ファイル送信完了直後に開放されてしまうのを防ぐ
  2. バックグラウンドタスクだからといっても、フォアグラウンド時に実行しても構わない。
  3. アップロードタスクでは、content-type は application/octet-stream しか使えない

1番目は、先のNSURLSession同様で Delegateメソッドの引数に渡されるので、簡単に開放されないようにしておきましょう。
2番目は 結構勘違いしがちなのですが、多くの解説ではバックグラウンド処理はAppDelegate内に
– (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
メソッドを実装して、その中で実行すべしと書いてあります、間違いではないのですが 説明が十分ではないです。

実際にはフォアグラウンド時でも [uploadTask_ resume]; しても全く問題なくバックグラウンドに移動して
– (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
の中で改めて [uploadTask_ resume]; を呼べば問題無いです。

3番目はデータを受け取るサーバー側が影響を受けますが、NSURLSessionUploadTask ではcontent-typeにapplication/octet-streamが使われます、multipart/form-data は使えません。

サーバー側からみたら よくわからんバイナリデータをドカッと渡された・・みたいな感じでしょうか。ということから サーバー側は 受け取ったデータの扱い方を プログラムに決め打ちで書いておくか、Cookieを併用して補足情報をサーバーに渡して適切な処理をさせる。
といった工夫が必要になります。

まとめ

大事な点1・2に書いたとおりですが、NSURLSessionとNSURLSessionTaskは使い捨てのインスタンスとしてではなく、クラス変数としてアプリがメモリ上にいるあいだ 使い続ける(特にNSURLSession)ように配慮した実装が望まれます。

また バックグラウンド用だからといって フォアグラウンドでは使えない、ということではなく フォアグラウンドの時 データアップロードを開始して バックグラウンドに廻っても 接続が中断されない というのが正しい NSURLSessionUploadTask の解釈です。

大きいデータをアップロード/ダウンロードする際には時間がかかることが多いので その間ユーザーが他のやりたいことが出来るというのが 本機能の大きな魅力だと思います。

関連記事

iOSプログラミングのキモ(MainViewController説明 NSNotification/NSNotificationCenter これ大事)

前回のエントリの続きで 今回はNSNotification/NSNotificationCenter

記事を読む

iOSプログラミングのキモ(2:AppDelegate説明 )

実際の AppDelegate.h、AppDelegate.m のソースコードです AppDe

記事を読む

iOSプログラミングのキモ(複雑な画面を複数のViewControllerで制御する その2)

先週は、複数のViewControllerで1つの画面を構成する話のうち、親ViewControll

記事を読む

iOSプログラミングのキモ(AppDelegate説明 NSUserDefaultsに設定情報を格納する )

QTubeは、YouTubeを閲覧するときに 国別コードを設定しています。国別コードは iOSに設定

記事を読む

iOSプログラミングのキモ(行き当たりばったりなプログラミングでも、何とか形にするために守っていること その2)

先週に引き続き、今週も文字中心のエントリーです、今回は 下記3つのことを書いていきます。 M

記事を読む

iOSプログラミングのキモ(AppDelegate説明 デバッグをやりやすくするための工夫:NSSetUncaughtExceptionHandler )

デバッグはプログラミングを進めていく上で避ける事が出来ません。どうしてもバグは入ってきます。重要なの

記事を読む

iOSプログラミングのキモ(拡張子がpchというファイルの役目)

XCodeで プロジェクトを作成すると、-prefix.pch というファイルができています。このフ

記事を読む

iOSプログラミングのキモ(デバッグをやりやすくするための工夫:Debug.hのインクルード )

前回 紹介した Debug.h は、使う際 以下のようにソースファイル(ここでは拡張子が m のもの

記事を読む

FileQ iOS版を開発しようと思った理由

私の会社では FileQというファイル転送サービスを 2008年3月末から始めています、かれこれ6年

記事を読む

iOSプログラミングのキモ(サードパーティ製ライブラリをサクッと入れるcocoapods)

iOSプログラミングでは 便利なライブラリがたくさんあります。特にUI系のライブラリは豊富で自分で作

記事を読む

Comment

  1. まこ より:

    こんにちは。参考にさせていただきました。

    おっしゃる通り、NSURLSessionの情報が少なくわからないことも多く、もしお時間がありましたら質問に答えていただくことはできますでしょうか。

    マルチスレッドで複数の通信処理を行いたいのですが、NSOperationQueueで複数通信の処理を作るのがよいのか、NSRULSessionのバックグラウンド処理を活用できるのか、というところで悩んでいます。

    NSURLSessionのバックグランド処理を連続して実行した場合(1つの通信が終わらないうちに、次の通信が始まるような)、通信処理を行うスレッドはすべて別々になって、NSOperationQueueで複数の並列通信したような処理にすることができるのか、というところが解明できていません。

    上記を解明するために、どのスレッドで通信が行われているのかテストしようとしたのですが、その処理を記載する場所が違うようで期待するような結果がえられませんでした。

    もし詳しいことをご存じでしたら教えていただけますでしょうか。
    よろしくお願いします。

    • 松田 勝己 より:

      NSURLSessionを使う判断としては、バックグラウンドでの処理が発生するかどうかにかかってくると思います。
      私が現在実装中のプログラムは 並列で通信を行うことはしないので、その点ついては あまり調べていません。

      NSURLSesseionでのバックグラウンド処理は実際はiOS側のデーモン(サービス)に処理を依頼することでアプリが
      バックグラウンドになっても処理が継続されるというのがカラクリです。

      で、そのバックグラウンド通信をどのように扱うかは デーモンに一任されていて、アプリから指定できることはあまりない
      状況です。

      唯一 並列処理に関係しそうな設定としては

      NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfiguration:FILEQ_BACKGROUND_FILE_DOWNLOAD_ID];
      sessionConfig.HTTPShouldUsePipelining = YES;

      という HTTPShouldUsePipelining 部分ぐらいでしょうか Pipelining なので 複数のタスクがある場合の処理方法を指定しています。ただし Should とあるので
      必ずしも 指定通りに動く保証は してないようです。
      回線状況(Wifi,3G/LTE)や他のアプリとのネットワーク利用の兼ね合いを総合的に判断してデーモンが決めているようです。

      ですので 並列処理を確実に実行したいのであれば、NSOperarionQueueなりで 実装するのが確実だろうと思います。

      バックグラウンドが必須の場合は 上記のような理由から 確実に並列処理が実行されることは期待できないと思います。

  2. まこ より:

    ご回答いただき、ありがとうございます。
    NSOperationQueueで確実に処理する方針にします。
    とても参考になる情報をありがとうございました。
    これからも参考にさせていただきます。

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

FileQ Hosting 月額99円 容量1GB


サイト管理 Mezzanine
Django上で動くCMS Mezzanine 用のモジュールを作ってみる その1

Django上で動くCMS Mezzanine上で動く、モジュールを作

ホーム Mezzanine
Django上で動くCMS Mezzanine を インストールする MacOSX Yesemite 編

Mezzanineは Django WEBフレームワーク上で動くCMS

EclipseにGWT(Google Web Toolkit) Plugin for Eclipseを入れようとしてハマった

最近PHPでちょっとした業務システムを作りました。業務システムの特徴と

ブログを半年やった成果を Google Analytics から眺める

今年の1月からブログを書き始め、そろそろ半年が経とうとしています。

母校で特別 講義をやってきました。

少し 間が空いてしまいました(^_^;) ちょっと前になりますが

→もっと見る

mautic is open source marketing automation
PAGE TOP ↑