iOS HTTP メモ
- iOSのHTTP関係のプログラムやメモです
- 随時更新していく予定です
Flow
HTTP リクエストを発行しレスポンスを受信するまでのプログラムの流れはだいたい次の通り。
NSURLConnectionの場合
- リクエストヘッダ(NSURLRequest)を作る
- NSURLConnectionのクラスメソッドを用いリクエストを発行する
- レスポンスヘッダ(NSURLResponse)とレスポンスボディを取得する
NSURLSessionの場合
- セッションクラス(NSURLSession)を作る
- タスクを生成する
- タスクの実行する
// playgroundの場合↓のコメントアウトを消す // import XCPlayground /* * NSURLConnection(非同期) */ func myNSURLConnectionAsync() { // 通信先のURLを生成. let url:NSURL = NSURL(string:"http://httpbin.org/get")! // リクエストを生成. let request:NSURLRequest = NSURLRequest(URL:url) let queue:NSOperationQueue = NSOperationQueue() // NSURLConnectionを使ってアクセス NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in // 帰ってきたデータを文字列に変換. var result = NSString(data: data, encoding: NSUTF8StringEncoding)! println("myNSURLConnectionAsync->\(result)") }) // playgroundの場合↓のコメントアウトを消す // XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true) } /* * NSURLConnection(同期) */ func myNSURLConnectionSync() { // 通信先のURLを生成. let url:NSURL = NSURL(string:"http://httpbin.org/get")! // リクエストを生成. let request:NSURLRequest = NSURLRequest(URL:url) // NSURLConnectionを使ってアクセス var response: NSURLResponse? var error: NSError? let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error) var result = NSString(data: data!, encoding: NSUTF8StringEncoding)! println("myNSURLConnectionSync->\(result)") // playgroundの場合↓のコメントアウトを消す // XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true) } /* * NSURLSession(非同期) */ func myNSURLSession() { // 通信先のURLを生成. let url:NSURL = NSURL(string: "http://httpbin.org/get")! // セッションの生成. let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration()) // 通信のタスクを生成. let task = session.dataTaskWithURL(url, completionHandler: { (data, response, err) in // 帰ってきたデータを文字列に変換. var result = NSString(data: data, encoding: NSUTF8StringEncoding)! println("myNSURLSession->\(result)") }) // タスクの実行. task.resume() // playgroundの場合↓のコメントアウトを消す // XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true) }
参考UTL:NSURLSession のまとめ
Json
jsonを取得しパース
func myNSURLConnectionSync() { // 通信先のURLを生成. let url:NSURL = NSURL(string:"http://httpbin.org/get")! // リクエストを生成. let request:NSURLRequest = NSURLRequest(URL:url) // NSURLConnectionを使ってアクセス var response: NSURLResponse? var error: NSError? let data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error) // var result = NSString(data: data!, encoding: NSUTF8StringEncoding)! // println("myNSURLConnectionSync->\(result)") // Jsonでパース let json:NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments, error: nil) as! NSDictionary for (name, value) in json { println("key:\(name) value:\(value)") } }
GCD
GCDを使った非同期完了後の処理
func myNSURLConnectionAsync() { // 通信先のURLを生成. let url:NSURL = NSURL(string:"http://httpbin.org/get")! // リクエストを生成. let request:NSURLRequest = NSURLRequest(URL:url) let queue:NSOperationQueue = NSOperationQueue() // NSURLConnectionを使ってアクセス NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in // 帰ってきたデータを文字列に変換. var result = NSString(data: data, encoding: NSUTF8StringEncoding)! println("myNSURLConnectionAsync->\(result)") // GCD(Grand Central Dispatch) dispatch_async(dispatch_get_main_queue(), { // 完了後に実行する処理(self.tableView.reloadData()等) }) }) }
参考URL:[Swift 1.1] swiftで api を叩いて、JSONをパースして、表示させる方法 (xcodeは6.1, iOSは8.1)
Auth
cookie
Cookieの永続化
// Cookieの永続化 import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { let SavedHTTPCookiesKey = "SavedHTTPCookies" func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { self.window = UIWindow(frame: UIScreen.mainScreen().bounds) self.loadCookie() return true } func applicationDidEnterBackground(application: UIApplication) { self.saveCookie() } func saveCookie() { let cookiesData:NSData = NSKeyedArchiver.archivedDataWithRootObject(NSHTTPCookieStorage.sharedHTTPCookieStorage().cookies!) NSUserDefaults.standardUserDefaults().setObject(cookiesData, forKey: SavedHTTPCookiesKey) } func loadCookie() { if let cookiesData:NSData = NSUserDefaults.standardUserDefaults().objectForKey(SavedHTTPCookiesKey) as? NSData { for cookie:NSHTTPCookie in NSKeyedUnarchiver.unarchiveObjectWithData(cookiesData) as [NSHTTPCookie] { NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookie(cookie) } } } }
Custom URL Scheme
- Custom URL Schemeの処理をシンプルに書く
- URLの設計:
scheme://controller/action?query
- アプリ側は受け取ったcontrollerを元に、使用するcontrollerクラスを選び、そのcontrollerクラスにactionとqueryを渡す。
- 各actionの挙動については各々のcontrollerに任せる
- URLの設計:
import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool { var result:Bool = true let schema:String = url.scheme! let host:String = url.host! let action:String = url.lastPathComponent! let query:String = url.query! return result } }
外部ライブラリ
- [Swift] HTTP通信OSS Alamofire 導入編
- [Swift] HTTP通信OSS Alamofire 応用編
- AFNetworkのSwift版 Alamofireを試してみた
- AFNetworking 2.0 のまとめ
- iOS開発でNetwork周りのManagerを作る (AFNetworking使用例)
- afnetworking-2-0-tutorial
- NSURLSession Tutorial
note
- 特にこだわりがないようであれば、Alamofireを使ったほうがよさそう(画像のキャッシュとか自分でコードを書く量が最終的に少なくてすみそう)
HTTPの確認に便利
- httpbin(1): HTTP Request & Response Service(jsonの確認にカナリ便利!)
参考URL
プッシュ通知 証明書 更新 手順
- プッシュ通知(APNs 証明書)の更新メモ
- Certificates, Identifiers & Profilesにて、証明書を更新する
- サーバーへ新しい証明書を配布する
更新手順
No. | 内容 | Development | Product | 備考 |
---|---|---|---|---|
0 | KeyChain(開発環境)->証明局に証明書(CertificateSigningRequest.certSigningRequest)を要求 | ◯ | ◯ | Development・Product共通、複数のアプリで共通で使う |
1 | Deveropper Center(Safari)->Certificates の作成 | ◯ | ◯ | アプリごと、No.0で要求した「CertificateSigningRequest.certSigningRequest」ファイルを使う |
2 | Deveropper Center(Safari)->Provisioning Profiles の更新 | △ | △ | アプリごとに対応必要。アプリのバージョンアップ時に必要 |
2 | Xcode(開発環境)->Buid Setting->Provisioning Profile のダウンロード | △ | △ | アプリごとに対応必要。アプリのバージョンアップ時に必要。新しくProvisioning Profileは作成されるが、特に更新しなくてもプッシュ通知は受信できる |
3 | KeyChain(開発環境)->p12ファイルを書き出す | ◯ | ◯ | アプリごとに対応必要。鍵ではなく証明書で書きだす |
- No.0
- No.3
手順
- Certificates の削除(更新前のCertificatesの有効期限が切れていない場合)・作成(Apple Push Notification service SSL)→作成後ダウンロード
- Provisioning Profiles の更新
- p12ファイルを書き出す(キーチェーンより「1.Certificates」で作成した証明書を選択し書き出し)
- p12ファイルの鍵を書き出す(キーチェーンより「1.Certificates」で作成した鍵を選択し書き出し)(サーバー側で必要であれば)
- p12をpemファイルへ変換する(サーバー側で必要であれば)
- サーバへp12またはpemファイルを配布
p12からpemへの変換コマンド
openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12 openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12 openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem
参考URL
プッシュ通知文字数(おまけ)
表示箇所 | iOS6 | iOS7 |
---|---|---|
ステータスバー(画面上部) | 全角22文字、半角40文字 | 全角35文字、半角64文字 |
ダイアログ(ロック画面に表示) | 全角57文字、半角108文字 | 全角58文字、半角114文字 |
通知センター | 全角43文字、半角154文字 | 全角58文字、半角114文字 |
iOS 開発 カメラ
- iOS(iPhone,iPad)のカメラ、写真の開発時のメモです
- カメラを起動したり、フォトアルバムから写真を選択してアプリに渡したりできます
- アプリで高解像度(1MB以上)の写真や画像を大量に表示するとメモリ不足でクラッシュします(100KB位まで解像度を下げるのがオススメ)
写真やムービーの撮影
- 2つのテクノロジー
- UIImagePickerController:カスタマイズ可能な基本的なユーザイン ターフェイス
- AVFoundation FW:UIKitと一緒に使用し完全にカスタマイズした静止画像やムービーのキャプチャ機能をアプリケーション向けに作成することができる
- どちらを採用する場合も、Assets Library FWを使用して、メディアのメタデータ(GPS位置情報など)を管理できる
保存済みの写真やムービーをユーザのフォトアルバムから選択
- 2つのテクノロジー
- UIImagePickerController:基本的なユーザインターフェイスを使用してユーザがフォトライブラリからメディアを選択できる
- Assets Library FW:UIKitと一緒に使用し完全にカスタマイズした写真やムービーのブラウザを作成できる
Image Picker Controllerを利用した写真やムービーの撮影
- カメラインターフェイス(UIImagePickerControllerクラスのインスタンス)をインスタンス化してモーダルモードで表示
- システムは、このカメラインターフェイスとユーザとのやり取りを管理(ユーザが写真やムービーを撮影、またはキャンセル)
- システムはImagePickerControllerのデリゲートオブジェクトのメソッドを呼び出し今度は、このメソッドがユーザの操作(たとえば、新規の写真を「カメラロール(Camera Roll)」アルバムに保存する)の結果を処理。(カメラインターフェイスを消去するのもデリゲート)
参考URL
iOS 保存領域 比較 〜NSUserDefaults, CoreData,Keychain,等〜
アプリ容量の制限について
アプリのストレージの最大容量は、おそらく制限なし(20M以上だとDL時にwifiが必要となる)
保存領域について
種類 | 説明 |
---|---|
アプリ領域 | アプリが自由に使え、他のアプリから参照できない領域 |
OS領域 | OSが使用するデータが保存されており、基本的にアプリから参照できない領域 |
共有領域 | メディアライブラリ(写真、音楽等)やイベント(カレンダー等)、連絡先は全てのアプリから参照することができる領域(アプリからできる内容は、書き込みと閲覧のみ) |
永続データの復元(比較)について
- 復元時にデータも復元するには、外部サーバーにデータを保存するかiCloudを利用しないと殆ど初期化される
- 保存領域が共有領域であれば、アプリ自体にiCloudの処理をいれなくても、共有領域のデータは復元できそう
種類 | 復元可否 | 備考 |
---|---|---|
アーカイブ | ☓ | オブジェクトをバイナリ形式に変換してからファイルに永続化(一番基礎的な技術) |
プロパティリスト | - | ユーザーが変更しないもの(定数)、plist |
NSUserDefaults | ☓ | ユーザーが変更するもの |
CoreData | ☓ | データ量多い |
Keychain | ◯ | パスワードや個人情報(暗号化されていない場合復元されない) |
アプリ内にDBとして保存したい場合realmがオススメです。(日本語のドキュメントもしっかりしていて、組み込みも簡単で、ブラウザも用意されています)
参考URL
Xcode デバッグ 値変更
概要
- Xcodeでデバック中に、変数の値や関数の実行する方法の説明です
- デバックエリアでLLDBコマンドを実行して確認します
- 値の変更はデバックエリアで「po 変数名 = 値」で変更できます
LLDBとは、Mac OS X上のXcodeでデフォルトのデバッガで、デスクトップとiOSデバイスとシミュレータ上のC、のObjective-CとC++、Swiftのデバッグをサポートしています。
- 参考URL:About LLDB and Xcode
実行方法
- デバックエリアを表示(メニューバー->View->Debug Area->Show Debug Area)
- 止めたいところにブレークポイント設定
- プログラムの実行
- デバックエリアでLLDBコマンドの実行
実行例
- 変数の値や関数の実行する場合は、「(lldb) po 変数名 または 関数名」を実行します
「po self.dynamicType」でプロジェクト名とクラス名を表示している例
LLDBコマンド一覧(一部抜粋)
省略形 | 内容 | 正式名 |
---|---|---|
po | 式の評価(poはオブジェクト、pは基本型も可) | expression -o -- |
h | ヘルプの表示 | help |
s | ステップイン | thread step-in |
c | 続きを実行(次のbreakPointまで) | process continue |
br l | breakPointを一覧表示 | breakpoint list |
bt | 現スレッドのバックトレース情報を表示 | thread backtrace |
iOS AdMob 実装 方法
AdMob SDKの導入方法や手順が書いてあります。(iPhoneやiPadが対象)
やりたいこと
iOSアプリにAdMobのバナー広告を表示させたい。
(AdMobとは、Googleが運営するアプリ専門のアドネットワーク。日本国内の収益性は微妙だが、海外の広告案件が豊富。)
流れ
- AdMobに登録
- アプリにSDK組込
- 広告ユニットIDの作成
細かい部分はAdMobのスターターズガイドがしっかりしているので、その通り進めば多分大丈夫だと思います。
1. AdMobに登録
AdMobのサイトでユーザー登録をします。
「Sign Up」ボタン押下後適当に個人情報を入力して進みます。
(全文英語ですが、一般的なユーザー登録なので英語読めなくても何とかなるはず。。。)
2. アプリにSDK組込
今回は、CocoaPodsを利用してSDKを組込みます。
CocoaPodsの利用方法がわからない方はこちら。
2-1. Podfileの作成・SDKインストール
ターミナルを起動しPodfileを作成しSDKをインストールします。
$ vi Podfile # Podfileの作成 platform :ios, '8.0' pod 'Google-Mobile-Ads-SDK', '~> 7.0' $ pod install # SDKインストール Analyzing dependencies CocoaPods 0.36.0.beta.2 is available. To update use: `gem install cocoapods --pre` [!] This is a test version we'd love you to try. For more information see http://blog.cocoapods.org and the CHANGELOG for this version http://git.io/BaH8pQ. Downloading dependencies Installing Google-Mobile-Ads-SDK (7.0.0) Generating Pods project Integrating client project [!] From now on use `BannerExample.xcworkspace`. $ # 「プロジェクト名.xcworkspace」が作成されていればインストール完了!
2-2. AdMobを実装
とりあえずSDKが読み込めているか試してみましょう
# swift // Bridging-Header.h @import GoogleMobileAds // BannerExampleViewController.swift class BannerExampleViewController { override func viewDidLoad() { super.viewDidLoad() println("Google Mobile Ads SDK version:\(GADRequest.sdkVersion())") } }
# Objective-C // BannerExampleViewController.m @import GoogleMobileAds; #import "ViewController.h" @implementation BannerExampleViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"Google Mobile Ads SDK version: %@", [GADRequest sdkVersion]); } @end
次に、バナーを作成し画面表示されたらOKです。
広告ユニットIDは、「3. 広告ユニットIDの作成」で作成し自分のIDに変更しましょう
# swift // BannerExampleViewController.swift // GADBannerViewDelegateを追加 class BannerExampleViewController, GADBannerViewDelegate { override func viewDidLoad() { super.viewDidLoad() println("Google Mobile Ads SDK version:\(GADRequest.sdkVersion())") var bannerView: GADBannerView = GADBannerView() bannerView = GADBannerView(adSize:kGADAdSizeBanner) // 広告ユニットIDを指定する(3. 広告ユニットIDの作成参照) bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716" bannerView.delegate = self bannerView.rootViewController = self self.view.addSubview(bannerView) bannerView.loadRequest(GADRequest()) } }
# Objective-C // BannerExampleViewController.h // SDK から GADBannerView の定義をインポートする #import "GADBannerView.h" @interface BannerExampleViewController : UIViewController { // インスタンス変数として 1 つ宣言する GADBannerView *bannerView_; } @end // BannerExampleViewController.m #import "BannerExampleViewController.h" @implementation BannerExampleViewController - (void)viewDidLoad { [super viewDidLoad]; // 画面上部に標準サイズのビューを作成する // 利用可能な広告サイズの定数値は GADAdSize.h で説明されている bannerView_ = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner]; // 広告ユニットIDを指定する(3. 広告ユニットIDの作成参照) bannerView_.adUnitID = MY_BANNER_UNIT_ID; // ユーザーに広告を表示した場所に後で復元する UIViewController をランタイムに知らせて // ビュー階層に追加する bannerView_.rootViewController = self; [self.view addSubview:bannerView_]; // 一般的なリクエストを行って広告を読み込む [bannerView_ loadRequest:[GADRequest request]]; } - (void)dealloc { // プロジェクトで ARC を使用している場合は bannerView_ を解放しない [bannerView_ release]; [super dealloc]; } @end
広告のサイズやターゲットの設定はこちら
3. 広告ユニットIDの作成
- AdMob管理画面に「1. AdMobに登録」で作成したアカウントでログイン
- 広告ユニットIDの作成
- 「収益化->新しいアプリの収益化」ページまで移動
- アプリを追加
- 広告フォーマットの選択と広告ユニット名の設定
- 表示の設定方法
- 完了ボタン押下
- 広告ユニットIDをアプリに設定
- 「収益化->すべてのアプリ」ページまで移動
自分が追加したアプリの「広告ID」が発行されています
「2-2. AdMobを実装」で設定していた「広告ID」を自分の「広告ID」に変更しましょう
- 表示と計測の確認
- アプリを起動し広告が表示されているか確認しましょう(広告IDを変更した場合、よく見かける一般的な広告に差し替わっていると思います。)
- 広告を押下し外部サービスが起動することを確認しましょう
- 管理画面上で計測が正しくとれていることを確認しましょう(リアルタイムに反映されないので時間を置いて確認しましょう(私の場合1時間位で反映されていました))
以上で全て終了です。お疲れ様でした!
CoreData の 関連図 と 手順
概要
- CoreDataの知識が曖昧だったのでクラスの関連図や手順についてのメモです(細かい実装方法や使い方は他の参考サイトを参考にしてください)
- DB自体初めての人には登場人物が多くて難易度高めな気がします
- 参考資料:iOS Core Data チュートリアル
関連図
Core Data | RDB |
---|---|
NSManagedObjectModel | DB |
Entity | Table |
NSManagedObject | Record |
Attribute | Column |
- NSManagedObjectModel:管理オブジェクトモデル(データベース)
- NSManagedObject:管理オブジェクト(レコード)
- NSManagedObjectContext:管理オブジェクトコンテキスト(オブジェクト空間、管理オブジェクトのコレクションを管理する)
- NSPersistentStoreCoordinator:永続ストアコーディネータ(ストアを管理して、管理オブジェクトコンテキストに、1つ の統一されたストアのファサードを提供する)
作成手順
- プロジェクトの準備
- テーブルの作成
- テーブルにデータ追加
- NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
- NSEntityDescriptionでテーブルの指定
- NSManagedObjectで値の受け渡し
- テーブルからデータ取得
- NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
- NSFetchRequestでテーブルの指定
import CoreData class MyClass : UIViewController { // 3. テーブルにデータ追加 @IBAction func insert(sender: AnyObject) { let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate let managedContext: NSManagedObjectContext = appDelegate.managedObjectContext! /* Create new ManagedObject */ let entity = NSEntityDescription.entityForName("テーブル名", inManagedObjectContext: managedContext) let personObject = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext) // let myPersonObject = NSManagedObjectを継承したカスタムクラス(entity: entity!, insertIntoManagedObjectContext: managedContext) // 自分で作成したカスタムを使う場合 /* Set the name attribute using key-value coding */ personObject.setValue("値", forKey: "カラム名") // myPersonObject.カラム名 = "値" // 自分で作成したカスタムを使う場合 /* Error handling */ var error: NSError? if !managedContext.save(&error) { println("Could not save \(error), \(error?.userInfo)") } println("object saved") } // 4. テーブルからデータ取得 @IBAction func selectAll(sender: AnyObject) { /* Get ManagedObjectContext from AppDelegate */ let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate let manageContext:NSManagedObjectContext = appDelegate.managedObjectContext! /* Set search conditions */ let fetchRequest = NSFetchRequest(entityName: "テーブル名") var error: NSError? /* Get result array from ManagedObjectContext */ let fetchResults = manageContext.executeFetchRequest(fetchRequest, error: &error) if let results: Array = fetchResults { for obj:AnyObject in results { let name:String? = obj.valueForKey("カラム名") as? String println(name) } println(results.count) } else { println("Could not fetch \(error) , \(error!.userInfo)") } }
まとめ
- xcdatamodeldでテーブル情報(テーブル名、カラム名、カラム属性、リレーション等)を設定する
- NSManagedObjectを継承したクラスをテーブルのカラム名を書いて、テーブルとの受け渡し用の入れ物に使う
- NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
- NSEntityDescriptionでテーブルの指定
- データの保存場所を確認したい場合、applicationDocumentsDirectoryを見る
- 個人的には、FMDBのライブラリを使って直接SQLでデータをやりとりした方が自由で楽なきがします。。。