Photo by Cyrus Khamak
こんにちは。2016年も残すところあとわずかになりました。フロントエンドチームの渡邊です。初投稿です。
実は今年の7月頃にHAROiDに参画しておりました。チーム内ではなし崩し的に「大将」と呼ばれてます。酔いって怖いですね。
さて、6ヶ月もの間潜伏しての初投稿なのですが、先日「新たなSDK」のリリースにこぎつけ、やっとひと段落できたので、また忙しくなる前に記憶の限り残しておこうと思いました。
今回提供される「新たなSDK」は2つ、「SDK for iOS」と「SDK for Android」の2パッケージとなります。私は今回iOSのSDKの開発を担当させていただきました。
その上で今回のiOSのフレームワーク開発ではどんな実装方針を取っていたかや、実装に当たり、クリアしないといけなかった課題とその解決策などを記録として残しておければと思います。
今回提供されるSDKはJavascript SDKですでに提供されている機能と同等の機能の提供となります。 またJavascript SDKと異なりパッケージは大きな機能単位で幾つかのパッケージに切り分けての提供としてます。これは不要な機能までEmbedすることをアプリ側にさせることを避けたためです。 対応OSは例外を除きiOS8です。さて、今回リリースいたしますSDKでは幾つか検討すべき事項がありました。
開発環境について
まず今回iOSでのSDKを提供するに当たり、利用した言語についてですが、Objective-Cを採用してます。
割と枯れた技術になりつつあるObjCですが、SDKという性質上、最新の技術を使うのが最適解というわけではなく、あくまで利用するベンダー側の技術スタックを考慮する必要があります。
その場合、ご利用いただきたいベンダーの方としては、最新の技術を利用しているベンダーの方々だけではなく、すでに初期開発が終わっており、運用フェーズに入ってきているベンダーの方々も対象になります。
その場合、Swiftでフレームワークを開発するよりもObjCで開発した方が利用する側にとってメリットが大きいと考えました。
すなわち、
- ObjCのアプリにObjCのフレームワークを導入することは容易である。
- SwiftのアプリにObjCのフレームワークを導入することも先人達のドキュメントが多いため比較的容易にできる。トラブルがあった場合にも対処策などがドキュメントとして豊富にある。
- ObjCにSwiftのフレームワークを導入することは可能だが、ドキュメントが前述のケースと比べて少ない。トラブルが発生した場合に一から対応するケースもありうる。
などの観点から今回の開発においてはObjCでの開発が最適と結論付けました。
StaticではなくDynamic Frameworkにした理由は対応OSがiOS8以降であり、Embedded Frameworkが使えるからというのが一番大きいです。
SDK内部において、使い回しの可能な汎用的な処理(通信制御やエラーハンドリングなど)を1つのパッケージにして管理してます。StaticFrameworkでやるとなるとこれを実現しようとしただけで一苦労を味わうことになると思います。
汎用パッケージをアプリ側でEmbedしてもらう必要が出ますが、基本的にそれ以外の手間が発生せず、どのようなパッケージの構成で実装しようとも1つの汎用パッケージをEmbedすればそれぞれのパッケージ内で内部処理を使い回してくれるという恩恵が大きいのでこの形をとってます。
Outputについて
ネイティブアプリ側で扱うSDKのAPIは基本的にKVO(KeyValueObserve)でSDKからの出力を渡します。 これはHAROiDプラットフォームとの連携を行ったり、ストレージのI/Oを処理するなど、並列処理を必要とする部分の結果を集約するための機構として、KVOが最適であると考えたためです。ブロック関数を採用しなかった理由としては、SDKとアプリの実装を可能な限り疎の状態を保持したいという理由からです。アプリ側ではKVOにより結果を取得し、出力結果のハンドリングを実現できるようにしてます。
デバッグについて
フロントエンドをやっていて避けては通れないメモリ管理はSDKの開発でも同じです。
SDKのメモリリーク検証にあたり、実際にSDKを導入するためのテストアプリを作成し、そちらへ組み込んで検証してます。
その際にお世話になったのは信頼と安心のinstrumentsさんです。メモリリークを視覚化してくれるのでとても強い味方です。足を向けて寝れません。
メモリリーク潰しは地道な検証での根気作業です。一番難儀したのはマルチスレッド処理部分のブロックの内外でARCをそれぞれで対応していないといけないということでした。
SDKのAPIは通信や再帰処理を挟む箇所が多く、サブスレッドで処理を行って非同期に値を返すKVOを採用してます。
そのため、幾つかの処理でマルチスレッドを行う実装を行っておりますが、ここでメモリリークが発生しておりました。
対策としてはブロック内外で明示的にautoreleasepoolを行うことでメモリリークの発生を抑制することができました。
iOS10では実際autoreleasepoolなしでもメモリリークが発生しなかったりしてました。なので、この対応はiOS8や9への対策となります。この辺りは実機がないとわからない部分だと思うので、検証には実機を用意しましょう。
まとめ
- 機能単位でフレームワークを切り出すという構成で一番影響が大きかったのは汎用実装部分をどう扱うかというとこ。
- Outputはできる限り集約できるようにKVOにした。
- クライアント実装の肝であるメモリリークはinstrumentsと実機の用意は必ずしましょう。
そのうちSwiftもニーズが高まれば追加されるのではないかと思います。多分。
それでは。良いお年を。