2015年4月17日金曜日

仕事で使うiOSアプリ開発

はじめに

趣味の開発だと見過ごしがちな注意点が、仕事の開発だと多く存在します。
特に、ネットで検索した情報だとその場しのぎのプログラムが書かれていることが多いのも原因の一つだと思われます。

今回は、私がiOSのアプリを開発して気づいた点・ノウハウ化されるべき点をまとめてみました。ここで書かれている以外にもまだまだ書くことは多いです。少しずつ蓄積していこうと思います。

User Interactionの制御

UIに関して、Webとスマホアプリとの一番大きな違いは
・同時に複数のオブジェクトを操作できる点
です。

もう少し細かく説明しましょう。
Webであればカーソルは1つしかないので、同時に同じボタンをクリックすることはあり得ません。
しかしスマホの場合、複数の指を用いた操作を提供しているため、ある指でボタンを押しながら、他の指で別のボタンを押したりドラッグする操作を許容してしまいます。

スマホ開発において、ボタンに限らずUIオブジェクトを作成する時は、他のオブジェクトとの同時操作可能性を常に意識しなければなりません。
この可能性を無視してアプリを作ると、アプリがクラッシュ・操作不能になるような致命的なバグが大量に埋め込まれてしまいます。以下、代表的な事例とその回避策をあげてみます。

UIButton
let button = UIButton()
button.exclusiveTouch = true

exclusiveTouchをtrueにすることで、他のボタンとの同時操作をブロックします。
デフォルトではfalseになっています。もちろん、複数の指で操作するUIを組み立てるときはこの限りではありません。ただ、指一本で操作するボタンの方が多いと思われるため、UIButtonを使用する時はexclusiveTouch属性をtrueにするのを癖にするぐらいでちょうど良いと思います。

Gesture Recognizer
let view = UIView()
let gesture = UIGestureRecognizer()
view.addGestureRecognizer(gesture)
view.exclusiveTouch = true

UIButtonと同様です。UIViewにGestureを付与する時は同時操作の可能性が生まれます。
exclusiveTouch属性を忘れないようにしましょう。

Animation

Animation中はuserInteractionEnabledをfalseにしUI操作をブロックしましょう。

データの保存

アプリのデータベースの持ち方は大きく2つに分かれます。
1. サーバーへの保存(リモートのサーバー)
2. ローカルへの保存(iPhone端末内)

1. サーバへの保存

サーバーにデータを保存する際は、個人情報の取り扱いについて明記し・ユーザに許可を求める必要があります。
当たり前のように思われるかもしれませんが、iOSアプリ開発ではこのようなプライバシーポリシーに特に留意しなければなりません。その理由はiOSアプリ審査という手続きに起因します。iPhone端末にインストールされるアプリケーションがユーザに害を及ぼさないことを保証するために、Appleにより厳しい審査が施されます。
ユーザへの許可なく個人情報をサーバーに送るアプリなどは悪質なアプリとみなされApp Storeへの公開を差し押さえられるばかりか、最悪、開発アカウントを凍結される恐れがあります。

ローカルへの保存

とくにスマホゲームアプリの場合、ローカルにユーザデータを保存することがチートの可能性を生み出すことがあります。最近のゲームはソーシャル機能を持ったものが非常に多く、個人のチートがユーザ全体・サービス全体に影響を与えるため致命傷になりかねません。

オフラインの可能性

ネットワークを利用するアプリの場合は、オフラインの可能性を考えましょう。
オフライン時使用不可と割り切るのも一つの手法ですが、ネットワーク接続時にキャッシュとしてローカルにデータを保存し、オフラインコンテンツを提供することでユーザの満足度が上がります。

サーバー連携

サーバーへのデータ送信に失敗した場合の処理、再送信処理などを考慮しましょう。

画面規格対応

見積もりを出す際に、どの画面規格に対応するかを必ず確認しましょう。
iPadなどは勿論ですが、iPhone5 ~ iPhone6Plusでもアスペクト比が異なるため、
特定の端末依存で画面の開発をしていると、違う端末でスタイルが崩れることがあります。
コードベースで画面の組み立てを行っている場合は特に注意です。

確認方法

Xcode 6.0からPreview機能が追加されました。Xcode上で全ての画面規格でのレイアウトを確認することができます。ただし、コードにより動的にaddSubviewした部品は確認できません。

メモリリーク

知らない人にメモリリークとは何かを簡単に説明すると、
・オブジェクト(例:UIView、画面を追加する)を作成するとメモリを確保する必要がある
・不要になったオブジェクトは解放されなければならない

A. 解放したメモリに誤ってアクセスするとエラーによりアプリが強制終了する(クラッシュ)
B. 不要になったメモリを解放せずに放っておくと、確保しているメモリの容量が増大していき、一定容量を超えた時にOSによりアプリが強制終了される

詳しく勉強したい人は、ARCやガベージコレクションについて検索すると良いと思います。

iOS開発でよくあるメモリリークと、その防ぐ方法について説明します。

循環参照

オブジェクトAがオブジェクトBを持つ場合、オブジェクトAからオブジェクトBへの強い参照が生まれます。自分自身を指す強い参照がなくなった時に、オブジェクトは自動的に解放されます。つまり、画面遷移などでオブジェクトAが不要になり解放された時にその参照先であるオブジェクトBも自動的に参照されます。

循環参照とは、2つのオブジェクトが相互に強い参照を持った時に、どちらのオブジェクトも解放されなくなることを言います。

例1. オブジェクトBがオブジェクトAを持つ場合

// 強い参照
class ObjectB {
var objA: ObjectA?
// 弱い参照
class ObjectB {
weak var objA: ObjectA?

のように、weak参照子をつけることにより、強い参照を回避できます。(ObjectAから見た時にObjectBからの参照は無視される)

例2. クロージャの場合
複数のclass(オブジェクト)間で情報をやり取りする時にクロージャを使用することがあります。クロージャは強い参照を生み出します。

// 強い参照
closure = {
    ...
}
// 弱い参照
closure = {
    [weak self] in
    ...
}

のように修飾子をつけることで強い参照を回避できます。
修飾子はweak, unownedの2種類存在します。
使い分けは、
・selfがnilの可能性がある時はweak
・selfが常にnilではない時はunowned
となります。
unownedを使用してselfがnilだった場合はアプリがクラッシュするため、
迷う場合はweakを使っていればOKです。

クロージャの循環参照については、この記事が大変参考になります。(日本語)
http://qiita.com/susieyy/items/f57f9362b8e0a89023cb

メモリリークの発見

Add exception break pointにより、例外発生時にブレークできるためデバッグしやすくなります。

画面遷移の作り方

pushした画面をpopする、不要になった画面を解放するなどの処理を怠ると、メモリリークすることがあります。注意しましょう。

アプリ起動処理の実行時間制限

以下の条件を満たした時に、アプリが強制終了します。
・AppDelegateのアプリ起動 〜 最初のUIViewControllerの画面が表示されるまでに一定時間経過する(20秒〜30秒)
・Releaseモードでビルドした場合

つまり、アプリ起動時の初期化処理などをAppDelegateの中に書いてはいけません。
初期化処理用のUIViewControllerを作成し、
viewDidAppear -> 初期化処理 -> アプリのコンテンツ画面に遷移
とする必要があります。

厄介なことに、Macから実機に直接接続しDebugモードで実験している時には確認することができません。要注意です。

こちらの記事も参考になります。(日本語)
http://cocoadays.blogspot.jp/2011/02/ios.html

容量

アプリの容量が100MBを超える場合、Wifi接続時のみしかApp Storeからのアプリインストールができません。

アップデート

DBマイグレーション

かならずDBのバージョン管理を行い、カラムが追加・削除される可能性を考慮しましょう。

ログ

NSLog, printlnの違い

swiftの場合、NSLog, printlnの2種類のログ出力方法があります。
printlnの方が高速かつ手軽であり、ネットで検索した際もprintlnを使用しているコードが多い印象がありますが、製品開発の場合はNSLogを使用するほうをおすすめします。
理由は

  1. printlnはスレッドセーフでない
    printlnの場合、非同期処理の際に、出力が混ざることがあります。

  2. printlnはDevicesからのログに出力されない
    XcodeのコンソールではNSLog, printlnどちらともログ出力されますが、Devicesから確認できるコンソールではNSLogでしかログ出力されません。

例えば、TestFlightで配布したアプリで動作試験をしていたらクラッシュした場合、Deviceからログを確認することになりますが、printlnで出力したログは確認できません。

アーキテクチャ

32bit, 64bit

iPhone5cまでは32bitですが、iphone5s以降は64bitになっています。
バイナリをいじる処理が含まれる場合は双方で十分に試験を行いましょう。
Simulatorで試験をする場合はSimulatorのarchitectureを確認したうえで試験しましょう。

スレッド

サーバー通信やDB操作などはメインスレッドとは別のスレッドで行いましょう。
サーバー通信をメインスレッドで行ってしまうと、処理が重い時に画面がフリーズしてしまいます。

iOSバージョン別対応

iOSバージョンで使用するメソッドが異なることがあるので注意しましょう。

検証端末の取得

iOSのバージョンをダウングレードすることはできません。
最新バージョン以外のiOSに対応する場合は、検証端末をバージョンごとに調達する必要があります。開発会社であれば古いバージョンのiPhone端末をアップグレードしないまま保持しておくことが多いです。
Simulatorを使用することもできますが、カメラなどSimulatorでは再現できない機能もあり、様々な面で完全な試験のためには実機を用意する他ありません。

UILocalNotificationの上限

アラート機能のために使用するUILocalNotificationですが、実は最大64個までしか同時に登録することができません。古いアラートが無効化された時に備えて再登録処理を考慮しましょう。
iOS8でのバグにより64個を超えて登録できることもあるようですが、公式サイトには確かに64個と明記されています。
https://developer.apple.com/library/ios/documentation/iPhone/Reference/UILocalNotification_Class/

0 件のコメント :

コメントを投稿