Alamofireを読んだ
Alamofireとは
- https://github.com/Alamofire/Alamofire
- Swiftで書かれたHTTP通信ライブラリ。
- AFNetworkingの作者であるmatttさんの新作。
- AFNetworkingをリプレースするものではなく、AFNetworkingはSwiftでも安定して動くのでそのまま使えるとのこと。(参考: http://nshipster.com/alamofire/の最後の方)
- ファイルは
Alamofire.swift
だけで1000行に満たない。
使い方
Alamofire.request(.GET, "http://httpbin.org/get") .responseJSON { (request, response, JSON, error) in println(JSON) } Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]) .authenticate(HTTPBasic: user, password: password) .responseJSON { (request, response, JSON, error) in println(JSON) } .responseString { (request, response, string, error) in println(string) }
※読んだコードのコミット番号は76266c95564912f228e76a1868e50b6a33f104e7
である。
Alamofire.swift
tl;dr
Manager
オブジェクトが通信を行い、通信完了時のdelegateオブジェクトを管理する。- 初期化時に
NSURLSession
オブジェクトやdelegateオブジェクトをプロパティとして保持する。
- 初期化時に
request
メソッドは以下のことをする。response
メソッドは以下のことをする。- 通信が完了するとdelegateメソッドは以下のことをする。
- 停止状態になっているSerial Dispatch Queueを再開する。追加されたタスクは順番に1つずつ実行されていく。
L:25
public struct Alamofire { // ... }
Alamofire
そのものはクラスではなくstructになっている。- Swiftにおいてstructはクラスと同様にプロパティやメソッドを持つことができたりprotocolに準拠することができる等多くの点で共通しているのだけど、structはクラスとは違って常に値渡しになり参照カウントを使わない。
L:928
最初に呼ばれるメソッドであるAlamofire.request
の実装を読む。
extension Alamofire { // ... static func request(method: Method, _ URL: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request { return Manager.sharedInstance.request(encoding.encode(URLRequest(method, URL), parameters: parameters).0) } }
static
とついているのは、クラスではなくstructだからclass func
ではなくstatic func
と書くのであろう。Javaみたいにstatic
に統一してもいいと思う。- 内部では
Manager
クラスのシングルトンインスタンスのrequest
メソッドを呼んでいる。 - 引数に
ParameterEncoding
インスタンスのencode
メソッドの返り値を渡している。.0
というのはtupleの要素を取り出すときにこういう書き方をする。
L:141
Manager
クラスの初期化について見る。
class Manager { class var sharedInstance: Manager { struct Singleton { static let instance = Manager() } return Singleton.instance } }
- このシングルトンパターンの実装はhpique/SwiftSingletonで推奨されているアプローチ。
- 現在はクラスにstaticな定数を定義することができない一方でstructであればそれが可能なので、ネストしたstructにシングルトンオブジェクトを定数として定義してそれを外側のクラスの型プロパティからアクセスできるようにしている。
L:208
func request(request: NSURLRequest) -> Request { // ... var dataTask: NSURLSessionDataTask? dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { dataTask = self.session.dataTaskWithRequest(mutableRequest) } let request = Request(session: self.session, task: dataTask!) self.delegate[request.delegate.task] = request.delegate request.resume() return request }
NSURLSession
オブジェクトからRequest
オブジェクトを初期化している。delegate
を登録し、Request
オブジェクトのresume
メソッドを呼んでいる。Request
オブジェクトを返している。
L:403
class Request { // ... private init(session: NSURLSession, task: NSURLSessionTask) { self.session = session if task is NSURLSessionUploadTask { self.delegate = UploadTaskDelegate(task: task) } else if task is NSURLSessionDownloadTask { self.delegate = DownloadTaskDelegate(task: task) } else if task is NSURLSessionDataTask { self.delegate = DataTaskDelegate(task: task) } else { self.delegate = TaskDelegate(task: task) } } // ... func resume() { self.task.resume() } }
Request
オブジェクトは初期化されるときに渡されたtask
のクラスに合わせてdelegate
プロパティを初期化している。is
はオブジェクトがその型に属するかどうかをチェックする。resume
メソッドはtask
プロパティ、つまりNSURLSessionTask
(またはそのサブクラスの)オブジェクトのresume
メソッドを呼び、ここで通信を開始する。
L:208
Request
オブジェクトの概要をつかんだので、request
メソッドに戻る。
func request(request: NSURLRequest) -> Request { // ... let request = Request(session: self.session, task: dataTask!) self.delegate[request.delegate.task] = request.delegate request.resume() return request }
request.delegate
は実行するtask
に応じたdelegateクラス、つまりUploadTaskDelegate
,DownloadTaskDelegate
,DataTaskDelegate
,TaskDelegate
のいずれかが入る。request.resume()
で通信を開始する。- 開始された通信が完了したときに呼ばれるdelegateは
request.delegate
であり、これはself.delegate
という領域に確保される。このプロパティはSessionDelegate
という型である。
L:229
class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate { private var subdelegates: [Int: Request.TaskDelegate] private subscript(task: NSURLSessionTask) -> Request.TaskDelegate? { get { return self.subdelegates[task.taskIdentifier] } set(newValue) { self.subdelegates[task.taskIdentifier] = newValue } } // ... required override init() { self.subdelegates = Dictionary() super.init() } }
SessionDelegate
オブジェクトは複数のdelegateをラップする構造をもっているようだ。- subscriptを定義することで
self.delegate[request.delegate.task] = request.delegate
のようなアクセスを実現している。内部では、キーとして渡されたRequest.TaskDelegate
オブジェクトのtaskIdentifier
を実際のキーとして使っているようだ。オブジェクトそのものではなくInt型のidentifierをキーとして使った方が効率がいいのだろう。
request
メソッドの実装についておおまかに読んだので、続いてresponse
メソッドを読んでいく。response
メソッドはrequest
メソッドの返り値であるRequest
型に対して呼ばれているので、Request
クラスの定義を調べる。
L:458
func response(completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self { return response({ (request, response, data, error) in return (data, error) }, completionHandler: completionHandler) } func response(priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, queue: dispatch_queue_t? = nil, serializer: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self { dispatch_async(self.delegate.queue, { dispatch_async(dispatch_get_global_queue(priority, 0), { let (responseObject: AnyObject?, error: NSError?) = serializer(self.request, self.response, self.delegate.data, self.delegate.error) dispatch_async(queue ?? dispatch_get_main_queue(), { completionHandler(self.request, self.response, responseObject, error) }) }) }) return self }
response
メソッドにcompletionHandler
だけ渡すと、前者のメソッドが呼ばれ内部的に後者のメソッドが呼ばれる。self.delegate.queue
プロパティはRequest.TaskDelegate
クラス(またはそのサブクラス)のプロパティであり、レスポンスの処理はこのqueueで行われるようだ。このqueueについて詳しく見ていくことにする。
L:497
private class TaskDelegate: NSObject, NSURLSessionTaskDelegate { // ... let queue: dispatch_queue_t? // ... init(task: NSURLSessionTask) { // ... let label: String = "com.alamofire.task-\(task.taskIdentifier)" let queue = dispatch_queue_create((label as NSString).UTF8String, DISPATCH_QUEUE_SERIAL) dispatch_suspend(queue) self.queue = queue } }
queue
はtask
に対して一意なラベルを持ったSerial Dispatch Queueである。- つまり、各タスクに対してキューが1つ作成される。そのキューは追加されたタスクを1つずつ順番に実行していく。
- そして、
dispatch_suspend
によってキューは停止された状態になっているため、この状態ではタスクが追加されてもすぐに実行されるわけではない。
self.delegate.queue
がどのようなキューなのか把握したのでresponse
メソッドに戻る。
L:464
func response(priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, queue: dispatch_queue_t? = nil, serializer: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self { dispatch_async(self.delegate.queue, { dispatch_async(dispatch_get_global_queue(priority, 0), { let (responseObject: AnyObject?, error: NSError?) = serializer(self.request, self.response, self.delegate.data, self.delegate.error) dispatch_async(queue ?? dispatch_get_main_queue(), { completionHandler(self.request, self.response, responseObject, error) }) }) }) return self }
- タスクごとのキューに追加される。ただし、この段階ではキューは停止状態なのでまだ実行されない。
- 各タスクごとのキューから、グローバルキューにタスクを追加している。グローバルキューに追加されたタスクは並列に実行される。
- グローバルキューでは、通信が完了した結果を
serializer
によってシリアライズし、その結果をresponse
メソッドに渡したcompletionHandler
というクロージャに渡して今度はメインキューに追加する。メインキューに追加されたタスクはメインスレッドで実行される。 - キューにタスクを追加したら即時に自分自身を返している。こうすることで
response
メソッド(とそれに準ずるメソッド)をメソッドチェーンでつなげていくことができる。その場合、メソッドチェーンによって追加されていくタスクは各タスクのSerial Dispatch Queueによって追加された順番に実行されていく。
次に、通信が完了したあとdelegateがどのように呼ばれていくか調べる。まず、delegateオブジェクトは何か調べるため、NSURLSession
オブジェクトが初期化されている部分を読む。
L:197
class Manager { // ... required init(configuration: NSURLSessionConfiguration! = nil) { self.delegate = SessionDelegate() self.session = NSURLSession(configuration: configuration, delegate: self.delegate, delegateQueue: self.operationQueue) } }
- まず
NSURLSession
オブジェクトはManagerオブジェクトのプロパティである。 NSURLSession
オブジェクトのdelegateはSessionDelegate
オブジェクトとなっている。
L:229
class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate { // ... }
- 確かにdelegateオブジェクトに必要なprotocolに準拠している。
NSURLSessionDataDelegate
のメソッドの実装を見てみる。
L:336
func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didReceiveData data:NSData!) { if let delegate = self[dataTask] as? Request.DataTaskDelegate { delegate.URLSession(session, dataTask: dataTask, didReceiveData: data) } self.dataTaskDidReceiveData?(session, dataTask, data) }
- 上述の通り、
SessionDelegate
オブジェクトはsubdelegates
というプロパティに実際のdelegateを保持しており、独自のsubscriptからそこにアクセスできる。subdelegatesへのdelegateオブジェクトの追加はrequest
メソッド内で行われているので、そこで追加されたdelegateオブジェクトが実際に処理を行うことになる。 as?
はダウンキャストを行い失敗した場合はnilを返す。self.dataTaskDidReceiveData?
というのはOptional型のクロージャのプロパティ。どこかでセットされていればここで実行するような仕組みになっているのだと思う。
というわけで、実際にdelegateメソッドを実行しているクラスを読む。
L:598
func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didReceiveData data: NSData!) { self.dataTaskDidReceiveData?(session, dataTask) self.mutableData.appendData(data) }
- ここではあんまり大したことはしていない。
NSURLSession
オブジェクトによる通信が完了したときに呼ばれるdelegateメソッドはNSURLSessionTaskDelegate
プロトコルのURLSession(_:task:didCompleteWithError:)
というメソッドなので、これの実装を読む。
L:558
func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!) { self.error = error dispatch_resume(self.queue) }
- このメソッドは先ほどのメソッドが実装されていた
DataTaskDelegate
クラスのスーパークラスであるTaskDelegate
に定義されている。 dispatch_resume
で停止状態になっていたキューを再開し、追加されていたタスクを実行する。上述の通り、このself.queue
はタスクごとに作られたSerial Dispatch Queueであり作成直後に停止状態にしておいたもので、response
メソッド(およびそれに似たメソッド)で追加されたクロージャがここに追加されている。それらのメソッドが通信完了時によばれるdelegateでキューが再開することで順番に実行される、という仕組みになっていることが判明した。
PromiseKit/swiftを読んだ
PromiseKitとは
- http://promisekit.org/
- iOSプログラミングで頻繁に出てくる非同期処理を簡単かつエレガントにするライブラリ。
- JavaScriptとかでおなじみのPromiseパターンの実装と、各種CocoaフレームワークからPromiseを使うための拡張が含まれている。
- Objective-C版とSwift版がある。
使い方
NSURLConnection.GET("http://placekitten.com/250/250").then{ (img:UIImage) in // ... return CLGeocoder.geocode(addressString:"Mount Rushmore") }.then { (placemark:CLPlacemark) in // ... return MKMapSnapshotter(options:opts).promise() }.then { (snapshot:MKMapSnapshot) -> Promise<Int> in // ... let av = UIAlertView() // ... return av.promise() }.then { self.title = "You tapped button #\($0)" }.then { return CLLocationManager.promise() }.catch { _ -> CLLocation in return CLLocation(latitude: 41.89, longitude: -87.63) }.then { (ll:CLLocation) -> Promise<NSDictionary> in // ... }.then // ...
tl;dr
NSURLConnection+PromiseKit.swift
のようなextensionが何種類か用意されている。fulfiller
メソッドは以下を実行する。- Promiseオブジェクトの
status
を.Fulfilled
に更新する。 handlers
にあるクロージャをすべて実行する。
- Promiseオブジェクトの
- Promiseオブジェクトの
then
メソッドを呼ぶと以下のようなクロージャがhandlers
に追加され、新しいPromiseオブジェクトを返す。
NSURLConnection+Promise.swift
public class func GET(url:String) -> Promise<NSData> { // ... }
- いくつかの拡張を見てみるとすべて
Promise<T>
を返すようになってる。 - この返り値に対して
then
やcatch
を呼んでいるので、これらのメソッドはPromise
クラスのメソッドだと考えられる。Promise
クラスについてはあとで見ていく。
public class func GET(url:String) -> Promise<UIImage> { let rq = NSURLRequest(URL:NSURL(string:url)) return promise(rq) }
public class func promise(rq:NSURLRequest) -> Promise<UIImage> { return fetch(rq) { (fulfiller, rejecter, data) in // ... } }
func fetch<T>(var request: NSURLRequest, body: ((T) -> Void, (NSError) -> Void, NSData) -> Void) -> Promise<T> { // ... return Promise<T> { (fulfiller, rejunker) in // ... } }
fetch
内ではPromise<T>
を初期化して返している。初期化時にまたもクロージャを渡している。
// Promise.swift public init(_ body:(fulfiller:(T) -> Void, rejecter:(NSError) -> Void) -> Void) { // ... body(fulfiller, rejecter) }
- 上のようなクロージャを受け取る初期化はこれのようだ。
- まず
body
という引数を受け取る。body
はfulfiller
とrejecter
の2つのクロージャを受け取ってVoid
を返すクロージャ(ややこしい…)である。 - この
init
では引数として受け取ったbody
というクロージャを実行している。body
に渡される2つの引数はinit
内で定義される内部メソッドである。
// Promise.swift public init(_ body:(fulfiller:(T) -> Void, rejecter:(NSError) -> Void) -> Void) { func recurse() { for handler in handlers { handler() } handlers.removeAll(keepCapacity: false) } func rejecter(err: NSError) { if self.pending { self.state = .Rejected(err) recurse() } } func fulfiller(obj: T) { if self.pending { self.state = .Fulfilled(obj) recurse() } } body(fulfiller, rejecter) }
fulfiller
メソッドはstate
を.Fulfilled
に変更しrecurse
を呼ぶ。rejecter
メソッドはstate
を.Rejected
に変更しrecurse
を呼ぶ。recurse
メソッドは、すべてのhandler
を実行したあと消去している。
func fetch<T>(var request: NSURLRequest, body: ((T) -> Void, (NSError) -> Void, NSData) -> Void) -> Promise<T> { // ... return Promise<T> { (fulfiller, rejunker) in NSURLConnection.sendAsynchronousRequest(request, queue:PMKOperationQueue) { (rsp, data, err) in // ... if err { rejecter(err) } else { body(fulfiller, rejecter, data!) } } } }
Promise<T>
の初期化時に引数として渡されたクロージャが実行されるので、このときに非同期通信が実行されるようだ。- 非同期通信が成功した場合、
body(fulfiller, rejecter, data!)
が呼ばれる。このbody
というクロージャはfetch
メソッドに渡されたもので、その中のfulfiller
とrejecter
の2つのクロージャはPromise<T>
のinit
内で定義されたメソッドである。
Promise.swift
public func then<U>(onQueue q:dispatch_queue_t = dispatch_get_main_queue(), body:(T) -> U) -> Promise<U> { }
- シグネチャーがジェネリクスまみれで複雑。
dispatch_queue_t
型と(T) -> U
型を引数にとり、Promise<U>
型を返すメソッドということになる。 T
はPromiseクラスの型変数(←言い方合ってる?)であり、NSURLConnection+Promise.swift
の例で言うと、このT
にはNSData
やNSString
が入ってくる。- 例えば
T
がNSData
の場合、第2引数のbodyは「NSDataを引数にとってU
を返すクロージャ」となる。このU
が例えばMKPlacemark
である場合、then
はPromise<MKPlacemark>
を返すことになる。 - この返り値は
Promise<T>
であるため再度then
を呼び出すことができメソッドチェーンが成立している。
public func then<U>(onQueue q:dispatch_queue_t = dispatch_get_main_queue(), body:(T) -> U) -> Promise<U> { switch state { case .Rejected(let error): // ... case .Fulfilled(let value): // ... case .Pending: // ... } }
state
はPromise<T>
クラスのプロパティでState<T>
型として定義されている。
enum State<T> { case Pending case Fulfilled(@autoclosure () -> T) case Rejected(NSError) }
Fulfilled
は引数に() -> T
型のクロージャをとる。@autoclosure
は指定された引数を暗黙的にクロージャとして扱えるようにする。これによって引数を{ ... }
で囲う必要がなくなる。cf) https://developer.apple.com/swift/blog/?id=4
public func then<U>(onQueue q:dispatch_queue_t = dispatch_get_main_queue(), body:(T) -> U) -> Promise<U> { switch state { case .Rejected(let error): // ... case .Fulfilled(let value): // ... case .Pending: // ... } }
state
はenum型であることが分かったので、then
に戻る。- このswitch文ではvalue bindingsを行っている。マッチしたcase文で宣言された変数に値が割り当てられる。例えば、.Fulfilledにマッチした場合、stateを初期化する際に.Fulfilledに渡されたクロージャが
value
という変数に割り当てられる。
public func then<U>(onQueue q:dispatch_queue_t = dispatch_get_main_queue(), body:(T) -> U) -> Promise<U> { switch state { // ... case .Pending: return Promise<U>{ (fulfiller, rejecter) in // ... } } }
status
は宣言時に初期値として.Pending
を渡しているため、最初は.Pending
のcase文を通ることになりそう。status
が.Pending
である場合、Promise<U>
を初期化して返している。- 初期化の際、引数にクロージャを渡している。上述の通り、渡されたクロージャは初期化処理の最後に実行される。
public func then<U>(onQueue q:dispatch_queue_t = dispatch_get_main_queue(), body:(T) -> U) -> Promise<U> { switch state { // ... case .Pending: return Promise<U>{ (fulfiller, rejecter) in self.handlers.append{ switch self.state { case .Fulfilled(let value): fulfiller(value()) case .Rejected(let error): dispatch_async(onQueue){ fulfiller(body(error)) } case .Pending: abort() } } } } }
バックエンドAPI用のテンプレートを作り始めた
モバイルアプリケーションやJavaScriptアプリケーションのバックエンドとして使うAPIのテンプレートを作り始めた。
https://github.com/naoty/metallic
まだそんなにできてないけど、連休終わるので進捗を書いておく。まだ公開できるレベルではないので公開はしてない。
動機
iOSアプリのバックエンドをサクッと作りたい、でも単なるデータストアとしてではなくて少しロジックを実装したい、ってときにMBaaSを使うよりも自分でサーバーサイドを実装したくなる。そのとき、RailsかSinatraかを選ぶことになる。Railsでももちろん問題ないのだけど、必要十分な処理さえしてくれればいいという思いからSinatraを選びたくなる。だけど、Sinatra単独でバックエンドを実装するのにはかなり時間がかかる。例えば、データベースとの接続やマイグレーションの管理、JSONのパースと出力などなど、地味に大変な実装をこなさないといけない。そこで、SinatraベースでバックエンドAPIを実装するためのテンプレートを作ることにした。
現時点での機能
metallic new APPLICATION_NAME
: テンプレートからプロジェクト作成する。metallic generate controller RESOURCE_NAME
: テンプレートからコントローラーを作成する。コントローラーはSinatra::Baseを継承したRESTful APIを持つクラス。作成されたコントローラーは自動的にRackミドルウェアとしてuseされる。metallic generate model RESOURCE_NAME
: テンプレートからモデルとマイグレーションを作成する。今のところORMはActiveRecord固定で、DBもSQLite3固定になってる。ここはRailsみたいにオプションで切り替えられるようにしたい。Rakefileもテンプレートについてくるので、そのままrake db:migrate
できる。
使用例
$ metallic new todo create todo/Gemfile create todo/Rakefile create todo/app/application.rb create todo/config.ru create todo/config/database.yml $ cd todo $ bundle install $ metallic generate controller tasks create app/controllers/tasks_controller.rb $ metallic generate model Task create app/models/task.rb create db/migrations/20140721224324_create_tasks.rb $ rake db:migrate == 20140721224324 CreateTasks: migrating ====================================== -- create_table(:tasks) -> 0.0011s == 20140721224324 CreateTasks: migrated (0.0012s) ============================= $ rackup [2014-07-21 22:43:34] INFO WEBrick 1.3.1 [2014-07-21 22:43:34] INFO ruby 2.1.2 (2014-05-08) [x86_64-darwin13.0] [2014-07-21 22:43:34] INFO WEBrick::HTTPServer#start: pid=20212 port=9292
$ curl http://localhost:9292/tasks GET /tasks
実装予定
- 各種必要なRackミドルウェア: bodyのJSONをパースするヤツ、パラメータをパースしてヘルパーからページ番号やソートにアクセスできるようにするヤツ、例外をキャッチして適切なステータスコードを返すヤツ、整形されたJSONを返すヤツ、などをRackミドルウェアとして実装したい。
- 他DB対応:
generate
コマンドのオプションでテンプレートを切り替えられるようにしたい。 - あと、方針を決めかねているけど、Railsと組み合わせて使えるようにしたい。というのも、モバイルアプリケーションのバックエンドとしてだけじゃなくてJavaScriptアプリケーションのバックエンドとしても使えるようなものを目指しているので、部分的にmetallicアプリケーションをRailsアプリケーションにマウントさせる、みたいな使い方も考えられそう。なので、そういう場合を想定したテンプレートを考えたい。
APNsの概要と関連ツール群
Apple Push Notification Service(APNs)は、ソフトウェア開発者(プロバイダ)から受け取ったメッセージを安全な方法でデバイスにプッシュ通知するサービスである。
プッシュ通知までの流れ
- プロバイダはデバイストークンとペイロードから成る通知メッセージを作る。
- プロバイダはその通知メッセージをAPNsに送信する。
- APNsは受け取った通知メッセージのデバイストークンから配信先のデバイスを特定し、通知メッセージを配信する。
接続を確立するまでの流れ
- APNsとデバイス間で認証を行う(システムによって行われるため、開発者が実装する必要はない)。
- APNsとプロバイダ間で認証を行う。
- プロバイダがAPNsからサーバ証明書を取得し、検証する。
- プロバイダがプロバイダ証明書をAPNsに送信する。
- デバイストークンを生成しプロバイダと共有する。
- アプリケーションがリモート通知の登録を行う。
- システムがリモート通知の設定を行い、デバイストークンをアプリケーションデリゲートに渡す。
- アプリケーションがデバイストークンをプロバイダに送信する。
- プロバイダからの通信すべてにデバイストークンを添付させる。
APNs関連ツール群
AFNetworkingでおなじみのmatttさんが様々な関連ツールを開発している。それぞれのツールが上で説明した全体像の中でどのような位置づけなのか整理した。
houston
https://github.com/nomad/houston
プロバイダからAPNsに向けて通知メッセージを送るためのクライアント。Ruby製。上記の通り、プロバイダがAPNsにメッセージを送るには、(1)APNsとの間で認証を行う、(2)配信先のデバイストークンを取得する必要がある。なので、houstonでもメッセージを送る際にAPNsのサーバー証明書と配信先のデバイストークンを設定する必要がある。
rack-push-notification
https://github.com/mattt/rack-push-notification
iOSアプリからデバイストークンを受け取りDBに保存するRackアプリケーション(Sinatraベース)。上述の通り、iOSアプリはAPNsからデバイストークンを取得したあと、プロバイダと共有する必要がある。rack-push-notificationはそのデバイストークンを受け取るためのAPIを用意する。
Orbiter
https://github.com/mattt/Orbiter
iOSアプリからデバイストークンを送信するためのクライアント。今までの2つのツールはプロバイダ側のツールだったが、OrbiterはiOSアプリ側のツールである。取得したデバイストークンをプロバイダに送信する処理を簡略化できるようだ。
まとめ
これらの関連ツールを使ったプッシュ通知のフローは以下のようになる。
- iOSアプリはリモート通知の登録を行い、APNsからデバイストークンを取得する。
- iOSアプリはOrbiterを使ってデバイストークンをプロバイダに送信する。
- プロバイダはrack-push-notificationを使って用意したAPIからデバイストークンを受け取りDBに保存する。
- プロバイダはhoustonを使ってプッシュ通知をAPNsに送信する。
- APNsはプロバイダ証明書とデバイストークンからプッシュ通知を転送するデバイスを特定しプッシュ通知を送る。
参考
pecoでハッカーを検索
かなり前にcui-about.meというサービスを作ったんだけど、pecoと相性がいいことに気づいたので組み合わせてみた。
about() { if [ $# -eq 0 ]; then local name=$(curl -s cui-about.me/users | peco) else local name=$1 fi curl cui-about.me/$name }
技術メモの管理
今後ちゃんと学んだことをメモに残しておこうと思い直し、メモを管理する仕組みを整理した。
保存場所
Dropbox/Documents/notes/
以下。複数のPC間で簡単に共有したいのでDropboxで管理する。
メモを書く
まず以下のような関数を用意した。
note() { local note_path=$HOME/Dropbox/Documents/notes/$1 if [ ! -e $note_path ]; then touch $note_path fi open $note_path }
note <ファイル名>
でメモを書き始めることができる。
メモはすべてmarkdown形式で、エディタはvimを使うことにした。vim-templateというプラグインを使うことで、notes/*.mdにマッチするファイルを以下のようなテンプレートで開くようにした。
--- title: <%= expand('%:t:r') %> date: <%= strftime('%Y-%m-%d') %> --- <+CURSOR+>
<%=
と%>
で囲われたコードはVim Scriptとして評価されて展開され、<+CURSOR+>
の位置をカーソルの初期位置としてファイルが開く。このような設定はvim-templateのヘルプにあるのでそちらを参照してほしい。
メモの検索
agとpecoを使う。agは高速なgrepということで、任意の文字列を含むファイルを検索する。
onote() { echo $(ag -l $1 $HOME/Dropbox/Documents/notes/) | peco | xargs open }
template内で日付を必ず入れるようにしているので、日付で検索することもできるようになった。