Objective-C」カテゴリーアーカイブ

iOSのバージョンが上がるたびにdeprecatedが

というわけで、今回はこんな愚痴ともとれるタイトルをつけさせていただきたいと思います。

iOSはバージョンアップが比較的早いOSですので最新の機能が反映されやすい状態になっています。しかしながらこれがプログラマからするとかなりの負担になることもある、ということを今回はちょっと書いていきたいと思います。先に言っておきますがこの場合のdeprecatedとは「推奨されない」という意味を持ち、指定されている機能を使わない様にすることといった意味や今後この機能が廃止されるという警告を含んでいます。

 

iOS7の更新もやばかったがiOS8の更新もやばかったのね

iOS6からiOS7に上がったときはUIが大幅に刷新された影響でそれに関してのdeprecatedが大量に出たターンでした。テキストやらボタンやらで独自の処理をやっていたパターンだと見た目でOSの状態に合わせる、となるとかなりの修正量が必要になったと思います。特にアプリ内で黒を基調としていた状態から白を基調にする様になるパターンに変えるのにバージョン判定により状態を切り替える、というのが頻発することに。

そしてiOS7からiOS8です。今回の変更で最大の問題点はUIActionSheetとUIAlertViewが(コンパイラ上では警告されないが)UIAlertControllerとなることでdeprecatedになったことで、それに付随してiOS7までUIActionSheetでできた「UIActionSheetをシートが標準で現れるUIViewと見なしてシート上に別のUIを配置する」という裏技が完全にできなくなったことですか。UIDatePickerをUIActionSheetに乗せて・・・というパターンは多かったらしく、わざわざこれのためのコードを作った人もいるくらいですからね。ちなみにUIActionSheetやUIAlertViewがUIViewを継承しているのに対してUIAlertControllerがUIViewControllerを継承していることからも分かる様にUIAlertControllerで別のUIを配置することはできないようですのであしからず。がんばってそれができる様なViewを作って処理をしましょう。これに関してはどのくらいUIActionSheetを利用しているか?にも依存しますのでどんなコードが良いのかを示すことは簡単にはできません。臨機応変に。

 

ちなみに、iOSのバージョン判定ですが、コードとしてはものすごい簡単で

BOOL isGreateriOSVersion(int ver)
{
    return [UIDevice currentDevice].systemVersion.floatValue >= (float)ver;
}

というルーチンでも作ってこれを利用すれば問題は無いでしょう。

 

次のバージョンでは何がdeprecatedになるのやら

昔からiOSでアプリを作っている人や修正を請け負っている人だと気になるところだと思います。もちろん私もこういう記事を書いているくらいですから気になります。まあ、さすがに二年たったくらいでUIがまたもや大幅刷新される、ということは無いと思いますが、それでもdeprecatedによってアプリが動かなくなる、というのはかなり厳しいですよね。需要があるアプリであれば仕事にはなるのでそのあたりを考えると何ともいえなくなってしまうのが考え物ですか。

コードの入力補完機能って意外とうるさくないですか?

最近の開発環境だとだいたいこの機能ってついていますよね。VisualStudioにしろXcodeにしろEclipseにしろ。IntelliSenceという名前の場合はVisualStudioだけですが、入力補完処理がほかの統合開発環境(IDE)だとどのように呼ぶのか知らないのでとりあえず汎用的に意味を示すような題名にしてみました。

で、今回はこれです。使っているVisualStudioのバージョンが古いので最近のものは余り知らないのですが・・・。今は仕事の関係でXcode5を使い始めています。が、これの入力補完がやたらとうざいと感じています。まあ、設定を変えて黙らせれば済むだけなのかもしれませんが、多少は使うので結局はこの機能と仲良くなるしかないんですよね・・・。

 

コンパイル前に変な警告をさせるととてもいらつく

警告は警告なのでされないよりはましなのですが・・・。今現在やっているXcode5の処理でいらついている警告が三つありまして、それが

  • swtich-case構文でenumにより宣言されている変数を指定するとdefaultが無い限りすべての処理を記述するように警告される
  • printf系の構文でformatが変数の型と合わない、特にintやlong(+unsigned)の差を正しくするように警告される
  • defineプリプロセッサで可変引数によるマクロの場合、引数がないと警告される

です。プログラム的には分からなくもない(printf系構文であれば変数の型と合わないとアタックをかけられるかもしれないので)ですが、個人的には行き過ぎのような気がしています。しかもいやなのがこれをコンパイル時の警告として表示するのではなくコード上に表示することなんですよね・・・。Xcodeは確かに前からコード中に表示するようになっていましたが、なんか納得がいかないんですよね。

これもありますが、VisualStudioでもあるのが命令文の終了である「;」が抜けた時の警告。単なる書き忘れで行を変えた時に警告が出るのはコンパイル前にチェックできてありがたいのかもしれませんが、なんかコンパイル前にこれが出されると自分がダメ人間のような錯覚を受けるのは私だけなのでしょうか・・・。

 

いらないコードを補完しないでほしい

Objective-Cの場合はインスタンスへのメッセージ送信、という考え方の関係上引数の名前呼び出しが言語の仕様上可能になっています。で、普通にコードを書いている分には対象のインスタンスで呼び出すことができるメッセージを表示してしかも引数としてあり得そうなものまで表示されているのでありがたいような気もするのですが、例えばここで先頭にメッセージ呼び出しの括弧が足りない時はさあ大変。括弧のつじつま合わせをしようとパラメータ部分に勝手に括弧が付け足されるわ、気がついて括弧を削ってもカーソルを移動させたらまた就いてくるわ。ここまで来るとお節介もいいところです。

ちなみにちょっとした間違いで呼び出すメッセージを書き直した場合でも前に補完されたパラメータ部が残ったままになってなんかとんでもない状況となったりと。削除するのも一手間なんですが、これって対処法とかあるのでしょうかね。まだVisualStudioの場合は呼び出すメソッド候補をリストで表示するところまでしかない(はず)なのでマイルドな処理ですよね~。

 

VisualStudioとXcodeのキーアサインの違いに苦しむ

これはツッコミのおまけです。個人的に困っているのが行頭に戻る処理と行末に移動する処理です。VisualStudioは行頭移動がHomeキー、行末移動がEndキーなんでこれを使用することが多いです。が、これがXcodeに持って行くとHomeキーがコードの先頭行への移動、Endキーがコードの終端行への移動となんか困ったことになっています。こちらもキーアサインを何とかすればいいような気もしないでもないですが、行頭にカーソルを持って行きたいだけなのに間違えてコードの先頭行に移動してしまうと元の編集行を探すのが一苦労なんですよね・・・。他にもありますが、この辺は愚痴になるのでこの辺で。

 

この手の補完機能って初心者には親切なのかな?

この後でプログラム講座でもまじめに開こうかと思っている私ですが、補完機能があることにより逆に分からなくなることがありそうで怖いです。特に自動的に表示されるいくつかの警告、例としては行末の「;」を忘れた時の警告はコード内に直接出るようになっていますが、これだと「コンパイルした時に出るエラーの意味をひもといていく」というプログラムを作っていく上で重要な学習の手順が抜けることが問題になると個人的には思っています。それとも逆に「コンパイル前に分かったことが良かったこと」としてとらえるべきなのでしょうかね。これも開発環境の変化による新しい考え方なのでしょうかね。

ちなみに本当の初心者の人であれば「まずはサンプルをいろいろと作ってみて動作を見てみる」のをおすすめします。「サンプルを手に入れてビルド」ではありませんよ。自分で打ち込むことに意義がありますからね。コードを書く時に見やすいコードの書き方やコードの流れを打ち込みながら覚えていく、という手順はプログラムをかけるようになる流れの中で必要だと思いますので。

 

UILocalNotificationのサウンドファイルの説明に惑わされた・・・

これで立ち止まるようなアプリを組んでいる人はいないとは思いますが、実際に私がはまって、かつそれ系の正確な説明がどこにもないのでここでそれを紹介します。

iOS4.0以降はマルチタスクが採用されているので、アプリがバックグラウンド状態になったときにシステムからユーザーへの通知ができるように

UILocalNotificationというインターフェイスを用いてその情報をシステムに登録することができます。Appleの解説ページでも探せばサンプルコードがあります。

問題は、このUILocalNotificationが持っているプロパティで、soundNameというものがあり、これの説明が

For this property, specify the filename (including extension) of a sound resource in the application’s main bundle or UILocalNotificationDefaultSoundName to request the default system sound.

と書いてあります。問題は、この英文の翻訳で、私は簡単にこのように訳しました。

このプロパティはアプリケーションのメインバンドルにあるサウンドリソースを拡張子付きで示されたもの、もしくはデフォルトのシステムサウンドを使うときはUILocalNotificationDefaultSoundNameを与えます。

この説明だと、自前のサウンドファイルを指定したいときは

UILocalNotification *notify;
notify = [[UILocalNotification alloc] init];
・・・
notify.soundName = [[NSBundle mainBundle] pathForResource:@"notify" ofType:@"caf" inDirectory:@"sound"];
・・・

と書きたくなるのですが、これが間違いで、正しいのが

notify.soundName = @"sound/notify.caf";

と、mainBundleからの相対パスで記述しなければならないというものである、ということらしいです。(らしい、なのは一応実機debugでそういう結果が出たのでこういうことだろう、という意味)

つまり、UILocalNotificationのsoundNameは相対パス記述でなければならず、絶対パスの時サウンドは再生されないということ・・・だと思われます。

何とも面倒な仕様ですね。自分の日本語訳が間違っている(相対パスを使うの意味が含まれている)のかもしれませんが・・・

もう一つ陥りやすいミスがUILocalNotificationで使えるサウンドフォーマットは限定されているということです。読み飛ばしていたので原因解析に時間がかかりました・・・。

もう一つ今現在でもつまずいている部分があるのですが、それは解決したらまたネタにしたいと思います。

initWithNibNameのエミュレータと実機の差

iOSのアプリを実機でデバッグしようとしたときに起こった出来事です。

エミュレータ上では自分の思ったとおりに動作しているのになんと実機で実行するとビューの画面そのものが出てこない、という現象にはまりました。

検索をかけてもそんな事例を簡単に発見できるわけもなく、いろいろと調べました・・・。そして、

犯人はinitWithNibNameの中にいる!

というのもばからしいですが、結果がこれでした。

xib(nib)を使った画面設計とinitWithNibName

Interface Builder経由の画面設計は簡単なアプリを作るなら手軽で内部動作を考えないで作ることができるのですが、このとき、対象となるUIViewContollerは必ずinitWithNibNameにより初期化されなければなりません。

つまり、

@interface ViewMain : UIViewController {
//変数宣言
}
@end
//ビューの作成
UIViewController *view = [[ViewMain alloc] initWithNibName:nil bundle:nil];

となるのが普通です。ところが、これにちょっとした落とし穴がありまして、それに引っかかってしまったわけです。

initWithNibNameの第一引数はxib(nib)の名前なんですが

これは、基本的に「ビューを作成したときに読み込まれるxibのファイル名となる」ということを私は知らなかったわけです。ただ、デフォルトの動作として

nilが与えられたとき、対象のクラス名と同じ名前のxib(nib)ファイルがあるものとして初期化を行う

という動作があり、これのおかげでエミュレータ上では正しく動作していました。この「正しく動作する」がくせ者で・・・

エミュレータ上ではファイル名の大文字小文字とクラス名の大文字小文字が区別されないでinitWithNibNameが動作するが、実機では区別されている

私がこれを実装したときのxibのファイル名が「viewmain.xib」とすべて小文字だったわけですね。ところが、インターフェイスの名前は「ViewMain」と単語の先頭が大文字となるように定義していたために

実機上では「ViewMain.xib(nib)が存在しないためにロードできない」と判定されたらしいのです。エミュレータ上ではそれがなぜかスルーして動いていたわけでした。

せめてその動作くらいそろえてもいいのでは?という疑問が残っています。

しばらくObjective-Cのプログラムをやっていました

ほとんど一ヶ月ぶりの記事ですね~と。

新しいタグを増やしましたが、仕事の関係でObjective-Cのプログラムをやっていました。

iPhoneあたりにアプリを下ろすためですが・・・。

言語としてはObjective-Cはおもしろいですね。基本にC++があるのでその概念との比較、という形で覚えることができて便利です。

C++というよりはWindowsのCOMに近い(参照カウンタ、インターフェイスの要求など)ものからObjective-Cを考えるようにするとちょっとわかりやすいかも。

ただ、Objective-Cばかりでプログラムをやっていると一つ重大なミスを犯しそうな気がする違いが一つ。

Objective-Cでは、オブジェクトの解放は基本的には次のように書きます。

[_item release];
_item = nil;

このとき、_itemがnil(C++的にはNULL)でもエラーではなく、なにもしない、という流れをくむことになります。

これが、COMオブジェクト(=C++)での解放はこのように書きます。

if(lpItem != NULL){
	lpItem->Release();
	lpItem = NULL;
}

つまり、lpItemがNULLでないことをチェックしないとReleaseの呼び出し時にNULL参照による実行時エラーとなるわけです。

なんかObjective-Cに慣れすぎるとこの辺がバグの温床にもなるような気がしてなりません。

もう一つ、面倒なのが参照カウンタの管理です。はじめやっているとかなり流れに苦しむところだと思います。COMでもそうですが。

参照カウンタは「オブジェクトを確保して初期化する」と自前管理になり、「直接取得する」とautoreleaseで扱われる、というのが一般的です。

つまり、

NSString *str1,*str2;
str1 = [NSString stringWithFormat:@"%d",256];
str2 = [[NSString alloc] initWithFormat:@"%d",256];

と書くと、以下のようになります。

str1は「直接取得」とみなされれ、この時点ですでにautoreleaseがかかります。放置しておけば基本的に勝手に解放されます。

str2は「オブジェクトを確保して初期化する」ので、自前管理となり、放置するとメモリリークを起こします。使用後にreleaseを行うか、autoreleaseを行って自動解放するようにします。

・・・わかりづらいったらありゃしない。

違いがわかればそれなりに対応ができるのですが、このautoreleaseというのが微妙にくせ者で、本来は最も内側にあるNSAutoreleasePoolが解放される段階で一緒に解放されるらしいのですが、この「最も内側にある」が特にUIKitを使っているとき(WindowsではMFCを使っているような状況)に不定になるらしく、基本的に「ルーチンを抜けるとすぐにreleaseを発行する」くらいでとらえるのが自然かな~という結論が得られたくらいです。メモリの関係でいつ解放されるのか気が気じゃなかったのですが・・・。(作りすぎてメモリ不足、とかがありそうだった)

C++の時は自前制御なので、悩まずにAddRefを使ったりできるんですけれどね~。

ま、オブジェクトへのメッセージ処理など言語的にはおもしろそうなのでしばらくは仕事にできそうな予感です。