ibisPaint for iPad 開発秘話
2011年6月21日待望のibisPaint for iPadがリリースされた。めでたい。今回はその開発秘話を書くことにしよう。
企画段階
上図、ibisPaint for iPadのタイトル画面。デザイナにはスカッと明るい空で。という感じで依頼
僕は、根っからの技術者だ。小学校3年生でパソコン(松下製JR100)を買ってから、ずっとプログラムばかり書いて来た。寝ても覚めてもコンピュータのこととプログラムのことを考えてきた。大学生のときにMOSAICショック(Firefoxの前身のNetscapeの前身のブラウザ)を目の当たりにして、Web時代の幕開けを感じて日本初のFTPソフト小次郎を作ってシェアウェアとして販売して、プチヒットして資本を用意して、iモードの立ち上がりをみて、26歳のときに独立した。ガラケー時代は、ibisBrowerというiアプリで動くフルブラウザを一人でコーディングして、大量のサーバをクラスタリングしてサービスインさせた。iPhoneのリリースと同時に電光石火で、iPhone用の送受信できるメールソフトとして、世界初でibisMail for iPhoneを作り、AppStoreで国内有料総合1位をとった。
ibisBrowserがリリースされた後は、僕はコーディングをしなくなり、もっぱら企画を練ったり設計をしたりというのが僕の仕事だ。
ibisPaintを企画したときは、その前からずっとソーシャルな企画を考えたいと思って、ネタ帳にたくさんの企画を書いていた。
企画をしたことがない人、自分で自社製品や自社サービスを立ち上げたことがない人は、ネタがすごく大切、アイデアがすごく大切。と思っている人がいるが、企画はもの凄く大切なのだが、実行することと結果を伴うことも含めて企画であり、プロデューサのミッションなのだ。これは、成功体験を得ないとなかなか伝わらない。
「あー、あのアイデア俺も前に考えていたのに、あの会社からリリースされた!」
などと悔しがる人は、大抵、企画をたてて遂行し成功させた人ではない。本当は、チャンスはいつでもどこでもあるけど、実行できないことが多い。それは自分の立場だったり、実績だったり、発言力だったり、会社の資本力だったりする。社員60人の中小零細の社長の僕でもそんなことは日常茶判事である。
それと一般の人は、世の中にでてくる新製品の多くが儲かっていないことを知らない。
「あー、あのアイデア俺も前に考えていたのに、あの会社からリリースされた?」
とぼやくなら、せめてその会社から出た新製品が儲かったか追跡調査すべきだ。AppStoreは、世界と戦わないと行けないので、さらに競争は激烈。日に200本とか新アプリが出るとか、すでに40万本のアプリが出ているとか95%とか97%ぐらいは儲かっていないという。その中で売れる製品を作るには、相当大変。そのため僕は寝ているときでもネタが思いついたらベットの中でiPhoneでネタをメモしてメールで送るようにしている。
あと、iPhoneアプリの企画について書かれた本を読んだことがあるが、ある本は300本アプリを作ったとか、ある本は100本アプリを作ったとか書いてあった(もちろん著者は有名ディベロッパー)が、僕はスタイルが違う。もし売れたときは、長くバージョンアップができる企画のものを湛然(たんねん)に作るスタイルだ。ibisMailも企画時の構想からするとまだまだ実現できていないことが山ほどある。リリースから2年半弱だが今でもカテゴリランキング上位だ。長く情熱をそそいでいる。
リリースして2週間だけランキング入りみたいな感じでバージョンアップしないアプリが多い。ビジネスとして作る場合はこれはしかたがない。僕も売上が止まればバージョンアップはできなくなる。だからこそ長くランキングに入って長くバージョンアップできる企画にしたいと日頃から思っている。
話が脱線したが、ibisPaintは、ASCII.jpの http://ascii.jp/elem/000/000/429/429906/ この記事を読んだのが元ネタの企画である。これはiPadが出る前に読んだもので、そのとき作りたいなと思ったときのメモからの出発である。
「「描いてみた」をiPadで実現する。」これが今回の大元(おおもと)の企画だ。
社内的な事務処理であるが、ここで企画を書いて、コンセプト、開発コスト、開発期間、営業方法、予定収益、競合調査結果を社内でプレゼンをして稟議を通す。
上図、漫画家の神寺千寿先生にibisPaint用に描いていただいた「白井ユリ」
設計段階
ibisMailのときは、とにかく世界初でリリース!AppStoreができてすぐに企画を書いたので競合はゼロ。あるのはOS付属の標準メールのみ。なので標準メール+アルファ。標準メールが普通に進化したらどうなるかをイメージして設計をした。
今回のibisPaintは、iPhone時代からお絵描きアプリを開発している会社もあるし、iPadがリリースされるとともにスタートを切っているお絵描きアプリもある。つまり競合が多々あり、先輩ディベロッパーがいっぱいるということだ。この場合は、設計の仕方が全く異なる。
まず、徹底的に競合を調査する。どのぐらい売れているかの市場調査もする。かたっぱしからお絵描きアプリをかう。レビューを読む。その会社のホームページを読む。「自分のアイデア最高!」と思っている企画者は結構競合調査を嫌う人が多い気がする。他の製品をみないことで、アイデアが研ぎすまされることもあるかもしれないが、実際には競合より工夫が足りないこともあるし、的外れなところへ進んでいくこともある。
僕のスタイルは競合は徹底的に調査するスタイル。その中で、大元の企画からは関係ない細かい中での差別化もはっきりしてくる。競合の工夫をみて別の工夫を思いつくこともある。ひとつ一つの細かいUIや演出については、大変なので説明はしないけど、UIを決めるときは何度も何度も初めてユーザが使ったときの心の動きをイメージする。目をつぶって耳を済ましてイメージする(とはいっても完成したらもっと調整したいもっと調整したいというところはあるのだが)。
ちなみに僕はUIを決めるときは、パワーポイントで部品を配置しながらイメージを固めてゆく。
競合をみていてこれは駄目でしょう。と思ったUIをひとつ紹介しよう。お絵描きアプリで絵を描いた後(PC的に言えばファイルを編集した後)、編集画面を抜けるときに「保存しますか?」「保存」「破棄する』みたいなボタンがでるUIがあった。これはダメだ。iPhone/iPadの世界では、PCであったファイルっぽい概念をユーザに見せるべきではない。ここではだまって保存すべきだ。
UIが固まったらデータ構造や色の計算方法、サーバとの通信方法、TwitterやFacebook、YouTubeとの連携方法、動画エンコーディングなど詳細に落とし込む。
データ構造の設計は最も気を使う部分である。この設計がベストかどうかで、処理速度が違って使い勝手が全然違ってくるからだ。設計段階からどのぐらいのファイルサイズなら保存に何msかかるかとか、シークしたらどのぐらいか。とか。ベンチマークをとってフラッシュメモリの速度を調査したり、圧縮をかけながら保存するとどうか等を調査しながら設計をした。
上図、256階調の選択レイヤー機能。当初の企画段階には含まれていなかったが、お絵かき機能の中でも1つぐらい他にない機能が欲しいと急きょ開発メンバーにお願いして無理して作ってもらった機能
コーディング段階
ibisPaintでは、僕はコードをほとんど書いていない98%ぐらいは開発メンバーのみんなが書いてくれた。その代わり、毎日毎日詳細にソースコードレビューをしつづけた。
もっと高速に動作するように!もっと速く!もっと速く!
コードをみては、ベンチマークをとって何msだから遅い。僕がチューニングして何msに高速化。最悪、設計書からずれても仕様が変わってでも高速化に情熱を傾ける。
たぶん開発メンバーからは、「社長、速度にこだわり過ぎじゃないのか」と一部思われたんじゃないかと思う。僕は、モバイルコンピュータが好きで好きで仕方がないので、そのハードウェア性能を極限まで発揮させてあげないと、コンピュータがかわいそうと思ってしまう性格なのだ。
当初設計書の最初には、「「さらさら」描ける快適さが必要、最悪でも20fpsを切ってはならない」と書いてあったのだが、実際には20fpsでは快適ではなかったので、チューニングにチューニングを重ねて、60fpsの「さらさら」かけるブラシが作れた(ま、まだ改善したいところは残っているけど)。
こうしてコーディング段階では、進捗管理をしながら品質管理をしながら微調整の仕様変更をしながら進む訳だが、企画が成功するかどうかが日に日に不安になる。不安がつのって「もっとベストを尽くさなきゃ」と毎日夜中の2時、3時まで僕の仕事は続く(ブラック企業ではないので僕以外は普通に20時前後に帰っているけど)。
そして、みんなが一生懸命ベストを尽くそうと努力をしてくれているのをみると、「失敗したらどうしよう。みんなの努力が無駄になる。どう責任を取れば良いのか」とさらに不安になる。製品が完成に近づくにつれ、僕のプレッシャーはどんどん大きくなり、僕の過労はどんどん進んでいく。それと同時に「絶対、負けたくない」「絶対、失敗させないぞ」と毎日、闘志を燃やしながら前に進む。
上図、ブラッシュアップして高速化した60fpsのブラシ。実装直後は30fpsを切っていて頭を悩ませた。
リリースへ向けて
そして試験行程を経て、開発メンバー最後のラストスパートでヘトヘトになりながら、Appleへ申請!僕は開発メンバーみんなに言った。「この申請ボタンを押す瞬間、みんな「俺たちの思いよ届け!」と強く念じてくれ、世界の裏側まで届くように念じてくれ」
Appleへの申請から承認が下りるまでは通常、1〜2週間かかる。そのため、申請後も試験を続けるのは常套だ。試験を続ける中で不具合が発見される。その度に開発メンバーにいっせいに緊張が走る。影響範囲の大きさを評価して、自ら申請取り下げかどうか判断する。自ら申請取り下げとなること数度。その度に、開発メンバーの顔色は真っ青になりながら不具合を修正して行った。
僕は、ここで一区切りという思いで、開発メンバーに向けて、リリースした後、売れずに失敗したときの段取り、ちょっと売れた場合の構想、もっと売れた場合の構想を、開発メンバーへのねぎらいの言葉と共に長い長いメールで送った。メールの最後の一言は、「人事を尽くして天命を待つ」だった。
上図、Twitter、Facebookでの作品URLやYouTube URLのシェア。
リリース初日
Appleから承認が下りた旨のメールがくるのはいつも明け方5:00ごろだ。僕は、ibisMailで朝起きると毎日そのメールを待ち遠しく待っていた。そうして、2011年6月21日 5:00に無事Appleから承認が下りた旨のメールが届いた。会社に行くと開発メンバー一同、リリースされた喜びで明るい顔だった。一方、僕は期待と不安でいっぱいだった。
リリース初日の夜、19時ごろibispaint.comには、ユーザからの投稿作品はゼロ。AppStoreの有料アプリランキングは150位。僕は午後から夜にかけて、どんどん顔が青ざめて行った。開発メンバーにも失敗の空気が流れていた。その晩、何度も何度も「失敗した、負けた。開発メンバーみんな頑張ってくれたのに。お金も時間も情熱も投資したのに。」と心の中でつぶやいた。
上図、本アプリと連動するWebサイトのibispaint.com。Web開発は朝飯前。
翌日
悔しくて悔しくて一晩中眠れず、また、一晩中次の段取りをどうするかずっと頭の中で試行していた僕は眠い目をこすりながら出社した。
昼になると、ついに大手メディアのITMediaが「iPadで“お絵かき工程動画”を作成・共有――新視点のソーシャルイラストアプリ「ibisPaint」」というタイトルで紹介してくれた。Twitter検索でも話題になっている。2時間おきぐらいにAppStoreでの順位が上がっている。開発メンバーからも「総合○位になりました!」とどんどん喜びの声が上がる。ユーザからも作品が投稿されて来る。
結局この日は、全カテゴリの総合4位。翌日には、総合1位を獲得した。
上図、iPad有料アプリ全カテゴリ総合トップ!
その晩、僕は、嬉しくて嬉しくて一晩中眠れず、一晩中次の段取りをどうするかずっと頭の中で試行した。
さぁ、次は世界をめざそう!
あとづけ
やー、もっと機能の紹介とか技術的に気合を入れたところとか詳細を書こうと思っていたけど、プロデューサーとしての自分がメインになってしまった。僕は、これでibisMail for iPhoneに続いて2製品で有料アプリ総合1位をとりました。2製品リリースで2冠!iPhone/iPadアプリに興味のある方は是非トライしてみてください。
ibisPaintは、みなさまの応援がないと続かない企画なので、ibisPaintを買った方は、どんどん作品を公開していただけると幸いです。
僕自身は、売れている限りはバージョンアップしたいと思っているので、要望があればTwitterの@kamiyanまでお伝えください。要望の方が多くて開発速度が追いつかないと思いますが、開発メンバー一同、コツコツ地道に努力を続けますのでよろしくお願いいたします。
#ibisMail for iPadは、法人向けの問い合わせが多々来ています。ビジネスモデルについては、また別の機会に語ろうと思います。
ーー
URL:ibisPaint for iPadのダウンロード、レビューはこちら サイトは、ibispaint.com
URL:ibisMail for iPadのダウンロード、レビューはこちら
ご意見ご要望はTwitterから: @kamiyan
ibisMail for iPadリリース!
やー、相変わらず更新遅いBLOGとなってます。
ibisMail for iPadがリリースされました〜
2011/4/21に念願のibisMail for iPadがリリースされました〜〜。そして初日からビジネスカテゴリ、トップセールス2位!ありがとうございます。
リリースまで時間がかかってしましました。去年の2月ごろiPadの発表からワクワクして、開発キットもリリースされてワクワクして、速攻、ibisMail for iPadの企画書と設計書を書いていたのですが、予算とか人員とかいろいろ大人の事情がありまして開発が遅れてしまいました。
使ってみた感じとしては、やっぱり画面が大きいとメールソフトも使いやすいなと。そして、iPadのOS付属のメールで、縦画面のときにスプリットがなくなってポップオーバー(吹き出し)でアカウント選択とかフォルダ選択とかメッセージ選択とかになるのが、1タップ増えて面倒だなと思っていたので、思い切って縦画面でもスプリットしたまま、ポップオーバーでのメッセージ選択等をなしにしました。iPhoneで慣れているせいか、右側の本文を表示するペインが幅が狭くても割とみられるなと。あと、スプリットは、リサイズできるようにしました。
思えば、2年以上前にibisMail for iPhoneをリリースしましたが、リリース直後に総合有料1位をとりまして、不具合を出してしまって相当ユーザ様にご迷惑をおかけしてしまいましたが、2年間ずっとバージョンアップを重ねて、やっと最近は定番アプリと呼ばれるようになってきて、ファンの方からも「ありがとう」と日々いただきまして、苦労してやってきたかいがあるなと。
iPhoneアプリって儲かっていないアプリがほとんどのためバージョンアップをコンスタントに続けるのは、かなり、しんどくて個人の開発者でない法人ディベロッパーだとリリース直後で開発が止まるってのが多いんですよね。
普通のユーザには、バージョンアップ履歴は、単なる読みにくい箇条書きですが、僕や開発チームにとっては、思いのつまった記録です。
大人の事情でユニバーサルアプリになっていなのでiPhone版とは別料金(900円)となります。ゴメンナサイm(_ _)m。今後もバージョンアップを続けていきたいので、応援よろしくお願いします。
ーー
URL:ibisMail for iPadのダウンロード、レビューはこちら
URL:ibisMail for iPhoneのダウンロード、レビューはこちら
Twitter: @kamiyan
ibisMail ver.2.8.0の新機能
昨日(2011/3/26)にibisMail ver.2.8.0をAppleへ申請しました。
新機能1:IMAP受信の高速化
IMAPの受信が遅いのがずっと気になっていました。ヘビーユーザにもがっつり使ってもらいたいという思いで開発開始したのに、IMAPで毎日何百通と受信する人には遅すぎていました。Apple製メールと同様にヘッダのみを受信するオプションを追加して高速化しました。僕の手元の試験で、3分15秒かかった受信処理が40秒弱になりました。5倍高速化!!です。
設定方法
「設定画面>IMAPのアカウント」に「受信方法」という欄が追加され、そこで変更できます。ver.2.8.0より前と同等の受信方法(1つずつ全部の情報を受信する方法)は、「ヘッダと本文を受信する」です。Apple製メールと同様の受信方法が「ヘッダのみ受信後、本文を自動で受信」になります。バージョンアップすると自動的にこちらに設定されます。旧来の遅い方がよい方は、設定で「ヘッダと本文を受信する」を選択ください。
パケットセーブ
さらにApple製メールにもない機能として、「ヘッダのみ受信」があります。こちらは、海外出張等で、パケット代がかかるときに、「本文を受信するとパケ代かかってしょうがねぇ〜」というときに使えます。メッセージ本文画面を開くと自動で本文を受信しますが、メッセージ本文画面を開かなければ、ヘッダのみの受信となりパケ代を抑えられます。
フォルダ同期
また、Gmail等でフォルダを数百と作っている方は、ver.2.7.1までのibisMailでは、フォルダ同期処理が走った後に受信箱の受信となり、フォルダ同期の時間を待つのが無駄でした。僕もGmailで200弱のフォルダ(GmailのWeb側の設定でフィルタを設定している)がありフォルダ同期に時間がかかっていました。「フォルダ同期>受信処理」という順序をver.2.8.0では、「受信処理>フォルダ同期」の順に変更しました(Apple製メールもそうなっています)。なお、アプリを新規インストール直l後またはバージョンアップ直後の最初の1回のみは、「フォルダ同期>受信処理」の順となります。2回目以降の受信で有効になります。
新機能2:未読・既読の表示
今までは、既読は既定で行全体がグレーになっていましたが、このグレーが暗いイメージなので、僕の中では気にいっていませんでした。ver.2.8.0では、行の右上にオレンジの三角を表示することで未読を表すオプションが増えました。「設定画面>未読・既読の表示」で選択できます。なおバージョンアップした場合は、今まで通りのグレーになりますので、変更したい場合は設定画面からお願いします。なおアプリの新規インストールのときの既定は、三角マークです。ツイッタークライアントでも未読を三角マークで表すものがあり、ちょっと近代的でよいなと。
新機能3:ドメイン指定でゴミ箱へ振分
「この差出人をゴミ箱へ振分」コマンドがありましたが、スパムメールでは、xxxx@yyyy.example.comのようにxxxxの部分とか、yyyyの部分にランダムな文字列をつけてくるためこのコマンドがいまいち使えませんでした。そこで「この差出人をゴミ箱へ振分」ボタンを押したときに、アクションシートで候補を出すようにしました。これでガンガン、ドメイン指定拒否(実際には拒否でなくゴミ箱だけど)ができます。僕も毎日1件ぐらいは拒否に追加しています。
バグ修正
その他、iPod再生中に音量が下がる問題は修正されました。iOSが4.1だったかOSがバージョンアップした瞬間にバグが発生し、僕の中ではOSのバグだと思っていますが、ずっと回避方法が分からなかったのですが、なんとか施行錯誤の研究の上、回避方法が見つかりました。よかったです。(OSのバグの回避はアプリ開発者はよくやる作業です)
まだまだ山ほど改良したいこと追加したい新機能はある。僕も、開発チームもサポートチームも情熱傾けています。これからも応援よろしく〜。
#2011/3/26: ibisMail ver.2.8.0をAppleへ申請しました。
通常1週間ほどでAppleから認可が下りると思います。
#2011/4/5 Appleから承認が下りリリースされました。
ーー
URL:ibisMailのダウンロード、レビューはこちら
Twitter: @kamiyan
メモリ管理
iPhone開発で、メモリ管理の基礎を社員に伝えることが増えてきたので、エントリとして書こう。
Objective-C基礎
メモリ管理の前にObjCの基礎として、メソッド呼び出しの話。
クラスのインスタンスaがmethodAをコールするときは、
[a methodA]
と書く。このとき、aがnilだったときは、エラーではなく、コールされない。methodAに戻り値があるときは、それは、0やnilやNOが返る。ObjCでは、
void dealloc { if(a!=nil){ [a release]; } [super dealloc]; }
は、気持ち悪いので、nilチェックはやめましょう。
なお、ObjCでは、動的にメソッドを差し替えることができ、コールの度にメソッドが存在しているかも確認しています。そのため、LL言語(ライトウェイト言語、スクリプト)のように柔軟な記述が可能です。そして、そのため、事前に宣言されていないメソッド呼び出しはエラーとならず警告になります。
メモリ管理基礎
いろいろな言語がありますが、C言語では、全部自力でメモリ管理しますが、JavaやC#やLL言語ではガーベージコレクションで解放します。ObjCは、参照カウンタで管理します(ObjC2.0のガーベージコレクタは使ったことがない)。参照カウンタでの管理は、全部自力とガーベージコレクタの間ぐらいの管理で、全部自力よりは楽と言ったところです。WindowsのCOMもリファレンスカウンタですね。
参照カウンタのインクリメントが、[a retain];、デクリメントが[a release];、現在のカウントを得るのが[a retainCount];です。これらはNSObjectのメンバです。あと、NSAutorelasePoolがあって、これはスレッドごとに最低1つはインスタンスがあります。
[a autorelease];をコールすると、カレントのオートリリースプールにこのインスタンスを後でrelease(参照カウントのデクリメント)するように依頼します。メインスレッドのコードを書くときは、イベントの最初にオートリリースプールが作成され、イベント処理が終わるとautorelease依頼されたものをreleaseします。このオートリリースプールは、メインスレッドのイベント経由の処理ではシステム側がやってくれるので、アプリは気にする必要がありません。バックグラウンドスレッドを作成するときは、スレッドの最初でオートリリースプールを作成して、スレッドの終わりで解放する必要があります。参照カウンタが0になると、dealloc(デストラクタ)が呼び出されます。
retain, releaseのルール
で、いつretainして、いつreleaseして、いつautoreleaseを使って、いつ自分の書いたコードでないところでカウントアップされるの?というところですが、
- メンバ変数の代入、グローバル変数の代入、static変数の代入など、1イベント処理以降も参照し続ける場合は、retainする。グローバル変数やstatic変数はほとんど使わないだろうから実質メンバ変数への代入でretainと覚えておけば良いでしょう
- ローカル変数(auto変数)では、retain、releaseしない
- allocやnewで始まるメソッドやCopyが含まれるメソッドは、受け取り側がretainするのでなく、そのメソッド内でretainされている。(alloc以外はあんまり使わないので、ここではallocだけ覚えておけば良いでしょう)
というのが基本です。
生成時
他のクラスの生成時ですが、init系のメソッドとクラス名で始まる生成系のメソッドがあります。例えば、NSDataクラスには、「initWithBytes:length:」と「dataWithBytes:length:」とと非常に名前の似たメソッドがあります(init系はあるけど、クラス名で始まる系がないクラスもあります)。init系は、
NSData* data = [[[NSData alloc] initWithBytes:"ABC" length:3] autorelease];
の、ように、クラス名で始まる系は、
NSData* data = [NSData dataWithBytes:"ABC" length:3];
のように書きます。どちらで書いても同じ結果で、クラス名で始まる生成系の方が記述が少なく済みます。で、init系は、alloc時点で参照カウンタが1で、生成されます。そして、上記例だとautoreleseがついているので、このイベントが終わったらreleaseされます。このautoreleaseがついているインスタンスは、今retainCountを見ると1ではあるもののオートリリースプールに登録されているので、このイベントが終わったときには0になるので、イベントが終わったときベースで考えると、参照カウントが0と見なすことができます(実質0)。また、dataWithBytesの方も中身はalloc init autorelaseをやっているものと思われます。単に記述が短くなるように用意されているだけです。
上記例は、ローカル変数への代入であるためretainする必要がありません、今のイベント処理が終わったら全部解放されて欲しいためです。つまり、代入演算子の左辺が一時変数(つまり参照を保持し続けない)であるため、右辺もalloc init autoreleaseで参照カウントを実質0にして、左辺と右辺をバランスさせるということです。
わずかばかりでもメモリの消費が押さえたい場合は、
NSData* data = [[NSData alloc] initWithBytes:"ABC" length:3]; //dataを使った処理 [data release];
と記述することも可能です。この場合、[data release]で参照カウントが0になるなら即メモリが解放されます(「dataを使った処理」で、他のインスタンスにdataを渡して、そのインスタンスが長期保持するならretainされるであろうから解放されない)。ただし、生成行とreleaseが離れると解放忘れをするし、開発チームの他のメンバーがこのソースを見ると、「ちょっと危険なコード」として、ちゃんと解放しているかチェックしてしまうので、お勧めしません。
メンバ変数への代入
メンバ変数など、1つのイベント処理を超えて参照を保持する場合は、retainする必要があります。dataMemberがメンバ変数として、生成のときは、
[dataMember release]; dataMember=[[NSData alloc] initWithBytes:"ABC" length:3]; //alloc内部でretainされる
と書きます。dataMemberがnilと分かっているときは、[dataMember release]は不要ですが、そうでないときは書く必要があります。前述の通り、dataMemberのnilチェックは不要です。
引数でもらった、aDataをメンバ変数に代入するときは、
[aData retain]; [dataMember release]; dataMember=aData;
と書きます。nilを代入したいときは、
[dataMember release]; dataMember=nil;
と書きます。nilの代入を忘れると2重解放の原因になるのでdealloc以外ではnil代入を忘れないようにすること。
なお、誰かの作ったメソッドであるクラスの参照が得られるとき、そのメソッド側で参照カウンタが勝手にあがることはありません(allocや、その他newやcopyがメソッド名につくものなど以外)。例えば、自分のビューの親ビューを得るのに
UIView* parent=[self superview];
と呼び出しても勝手には参照カウントは増えません。参照カウントを増やす責任があるのは、代入を書く人です。逆に自分でメンバ変数を返すコードを書くときもretainして返してはいけません。
-(NSData*)getDataMember { [dataMember retain];//NG! return dataMember; }
と書いてはいけません。何か生成して返すメソッドを書くときもautoreleaseをつけて実質0(今のイベント処理後で0)で返すべきです。
-(NSData*)getData { return [[[NSData alloc] initWithBytes:"ABC" length:3] autorelease]; }
上記は、alloc内部で参照カウントが1で生成されて、autoreleaseで実質0となります。
プロパティをガンガン使おう
さて、何度かretainとreleaseを書く例がでていますが、retain数に比べてreleaseが少ないとメモリリークになるし、retain数に比べてreleaseが多いとrelease時に
malloc: *** error for object 0x4c40740: pointer being freed was not allocated
とコンソールにエラーを吐いてクラッシュします。initしてautoreleaseをコールして、releaseをした場合は、オートリリースプールの解放処理でクラッシュします。当たり前ですが、retainのコールされる数とreleaseのコールされる数は厳密にマッチしていなければならず、プログラマが慎重に管理しなければなりません。そこで、retainとreleaseを極力書かないで済むようにすることをお勧めします。
その為にプロパティを使います。プロパティは、ヘッダファイルに
@interface Person { NSString* name; } @property (nonatomic, retain) NSString* name; @end
と書いて、実装ファイル(*.mや*.mm)に
@implementation Person @synthesize name; //中身 @end
と書きます。@propertyのnonatomic属性は、スレッドセーフでないが高速という属性です。マルチスレッドで複数のスレッドからアクセスされないときは、nonaotmicでよいでしょう。retain属性は、自動でretainやreleaseをしてくれるものです。なお、nonatomicやretainを書くのは、クラスのポインタ型のときであり、プリミティブ型のときは、@property int type;のように、属性は不要です。
実装ファイルに書く@synthesizeは、getterとsetterを自動で書いてくれるものです。
で、そのsetterですが、自動で、
-(void)setName:(NSString*)a { [a retain]; [name release]; name = a; }
というものを自動で作ってくれます(見えないけど、上記と同等のコードに展開されると思われる)。
で、setterの呼び出しは、
self.name = aName;
みたいに書くとsetterが呼び出されます。プロパティとして宣言しても
name = aName;
と書くと直接メンバ変数への代入となり、setterが呼ばれません。
- self.nameとnameは全然違う!
ということです。そして全てのポインタ型のメンバ変数をプロパティにすることで、ほとんど全てのretainやreleaseを書かずに済みます。上に挙げた例を書き直すと、生成してメンバ変数に代入なら
self.dataMember = [NSData dataWithBytes:"ABC" length:3]; //代入前の参照に対してreleaseしてくれる
[dataMember release]をせずに代入でOKになる。クラス名で始まるメソッドがないときは、
self.dataMember = [[[NSData initWithBytes:"ABC" length:3] autorelease];
[dataMember release]をせずに代入でOKになる。引数で来たaDataをメンバ変数に代入なら、
self.dataMember = aData; //代入前の参照に対してreleaseがされ、代入後の参照に対してretainされる
releaseもretainも不要。nilの代入も
self.dataMember = nil;//releaseされる
でOKです。これでほぼすべての場所でretainとreleaseを使わずに済みます。ただ慣習として、deallocは、nil代入でなくreleaseを呼ぶことが多いようです。しかし、プロパティに対してnil代入というコーディングルールにすればreleaseなしにできるでしょう。
- ポインタ型のメンバ変数をすべてプロパティにすればretainとreleaseは、原則呼ばなくてよくなる(例外はあるけど、setterの中とか、マルチスレッドとかね)
生成といっしょに他人にセット
どのメソッド呼び出しで参照カウントがアップされて、いつ解放されるか気になる人がいますが、「メンバ変数(など長期に保持する変数)に代入する人がカウントアップするというのが原則なので、普通、呼び出し側は気にする必要がありません(deallocが呼ばれるタイミングが重要とかマルチスレッドでない限り)。
view.backgroundColor=[[UIColor alloc] initWithRed:0.5f green:0.3f blue:0.1f alpha:1.0f];
これは間違えです。なぜならこれは、
UIColor* color = [[UIColor alloc] initWithRed:0.5f green:0.3f blue:0.1f alpha:1.0f]; view.backgroundColor=color;
と同じだからです。ローカル変数の代入は、alloc init autoreleaseをセットで使うか、クラス名で始まるコンストラクタ系を使うかです(別途、releaseを明示的に呼ぶのも可ですが、ここではretainとreleaseをできるだけ使わないようにしようという趣旨なので)。正しくは、
view.backgroundColor=[UIColor colorWithRed:0.5f green:0.3f blue:0.1f alpha:1.0f];
となります。
この生成されたカラーは、viewからしか参照がなければ(普通そう)、viewの解放処理とともに解放されます。
privateなプロパティ
メンバ変数をプロパティにすると、他のクラスから書き換え、参照し放題になりpublicになってしまいます。retain, release最小化のためにプロパティにしたいが、公開したくない場合は、クラスの拡張を使って、ヘッダファイルに書かずに実装ファイルに書きます。先ほどのPersonクラスのnameメンバ変数の例で行くと、
//Person.h @interface Person { NSString* name; } @end
ヘッダファイルには、@propertyを書きません。そして実装ファイルの上の方に
@interface Person () //クラス拡張 @property (nonatomic, retain) NSString* name; -(void)somePrivateMethod; @end @implementation @synthesize name; //内容 @end
のように、Personを拡張することで、他のクラスから見えないようにします。この拡張を使うことで、privateメソッドも作れます。(ただ、ビルド時に見えないだけで、実行時に無理矢理呼び出せば呼び出せるでしょう)
自分でNSAutoreleasePool
たまに自分でオートリリースプールの作成をすることがあります。1つは前述した別スレッドを使うときです。スレッドメインの最初に
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
を書いてそのスレッドが終わるときに
[pool release];
を書くだけです。その他に、メインスレッドでもサブスレッドでも大量にオートリリースプールに登録するような場合です。
for(int i=0;i<10000;i++){ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSString* a=[NSString stringWithFormat:@"%@=%d", b, c]; //適当な長い処理 [pool release]; }
この場合、stringWithFormatのコールでガンガンメモリが作られるのですが、自分でオートリリースプールを作らないと、ループ中に解放されないのでメモリをどんどん消費します。ループの中にオートリリースプールを作ることで、ループの1回ごとに解放されてメモリ使用量を抑えることができます。
メモリリークの試験
Xcodeでは、簡単にメモリリークの試験ができます。まず、iOS4からホームボタンをおしてもバックグラウンドでサスペンドされたままにすることができるようになったため、これがONだと試験にならないので、OFFにします。Info.plistで、「Application does not run in background」をONにします。そしてデバッグビルドします。
メニュー>実行>パフォーマンスツールを使って実行>Leaks
で実行します。Instrumentsが起動されるので、その状態で適当に操作してホームボタンを押します。リークしている場合は、リークブロックが出ます。
上のペインにAllocationsとLeaksの2つのグラフが出るのでLeaksをクリックしてカレントにします。上ペインと下ペインの間に「Leaked Blocks」というプルダウンがあるので、そこでCall Treeを選択します。そこのツリーを展開していくと自分のコードでinitやnew、mallocした行が出ます。ソースコードも表示され、newした行がハイライトされます。メモリを確保した位置が分かれば、大抵すぐに修正できるでしょう。
まとめ:かみやん式 retain,releaseしないコーディングルール
ルールをまとめると
- ルール1:クラスのポインタ系のメンバ変数は、すべてretain属性付きのプロパティにする。必要ならばクラス拡張を使ってprivateに。
- ルール2:クラスのポインタ系のメンバ変数への代入は、必ずself.をつけてプロパティ経由で代入する。
- ルール3:インスタンスの生成は、必ずalloc init autoreleaseまたは、クラス名で始まるファクトリメソッドを使う。左辺がメンバ変数のときは、self.をつけてプロパティへの代入にする。
- ルール4:retainは使わない。releaseは、dealloc内とNSAutoreleasePoolのrelease以外は使わない。それ以外の場所で参照カウントを減らしたいときは、self.プロパティ=nil;とする。
という感じ。マルチスレッドとかそれ以外の特殊な場合(循環参照の問題とか、微妙なタイミングの問題とか)でなければretain, releaseはほぼなしにできます。
プロパティを使って retain、releaseを最小にしましょう!
#2011/2/20 18:14 C++のdelete前のNULLチェックの文章をカットしました、指摘ありがとうございます。
#2011/2/20 18:30 NSAutoreleasePoolの章を追記しました
#2011/2/20 19:16 カテゴリを使わず拡張としました
#2011/2/20 21:03 生成といっしょに他人にセット節を追加
#2011/2/20 22:22 指摘ありがとうございます。C++をカット
#2011/2/20 22:41 指摘ありがとうございます。retain->release->代入の順番に修正しました
#2011/3/27 まとめ節を追加しました。
ーー
URL:ibisMailのダウンロード、レビューはこちら
Twitter: @kamiyan
Xcodeの環境設定メモ
やー、Twitter始めて、すっかりBLOGの更新が減るな。前も書いた気がするが。
久しぶりにコーディングしている訳だが、Xcodeの設定メモ。
メニュー>Xcode>環境設定。
【全般】
レイアウトは、オールインワンがいいという人がいるが、オールインワンだとプロジェクト内検索が、右上ペインにでて、そこが狭いので、やはりデフォルトがよい。
【コード入力補助】
- エディタ関数ポップアップ:リストをアルファベット順に並び替えON。デフォルトでは、ソースコード内の順であるが、僕はメソッド名で探すのでアルファベット順がよい。Eclipseでの経験が多いためかもしれない。
【ビルド】
- ビルド結果ウィンドウ:
- ビルド中に開く:問題が起きたとき。警告を無視するのは良くないので。
- ビルド後に閉じる:問題がないとき。問題なければ勝手に閉じるが楽なので。ちなみに「問題がなく完了したとき」では警告があっても閉じてしまう。
- 保存されていないファイルの処理:常に保存。保存しますか?の確認でNOを選択したことがないので。そもそもUNDO効くし。SVNもあるし。
【キーバインド】
僕は、統合環境のバインドをできるだけ周到したい派。環境移るたびに設定するのが面倒だから。でも、やっぱり少しカスタマイズ。
- プロジェクト内で選択されたテキストを検索:コマンド+T。Eclipseで参照検索はガンガン使っていたので。
- 選択されたテキストを検索:コマンド+E。コーディング中に検索は大量に使うので。
- 定義へジャンプ:コマンド+D。Eclipseのときもガンガン使っているので。
- 次のファイル:Alt+コマンド+→。2つのファイルを交互に見ることがよくあるので。
- 前のファイル:Alt+コマンド+←。
- 次のビルド警告またはエラー:コマンド+:。デフォルト設定は英語キーボード用かな?って感じなので。
- 前のビルド警告またはエラー:コマンド+;。
- デバッガ:コマンド+Y。これもよく開く。
- コンソール:コマンド+R。これもよく開く。
- 選択したテキストを製品ドキュメント内で検索:コマンド+H。
特に、検索系に当てた、コマンド+T, E, D, Hは必須。
【テキスト編集】
- ページガイドの表示:ON。80桁。
【インデント設定】
- エディタの行を折り返す:ON。ウインドウの右端で折り返してくれます。他人が書いたコードがやたら右に長いときがあるので。
あとは、環境設定画面ではないけれども、メニュー>実行>デバッガ表示>縦方向レイアウト。デフォルトのスプリットはソースコードペインの高さが足りない。
コーディングは久しぶりだが、毎日毎日楽し〜。良い物を作りたいと爆発的に情熱を傾けていたらきっと世の中に伝わると思う。
そう信じて頑張ります。
テオヤンセン機構その2
前のエントリで、テオ・ヤンセン展に行って、黄金比率を得たので、紙で作ってみたと書きましたが、前回のは、爪楊枝2本をつかってクランクにしましたが、これでは6足全部は作れない。ということで、ホームセンターへ行ってピアノ線を買ってきてリベンジしました。
試作2体目
上図、試作2体目。
なんとか6足作ったが、ガタガタ。直径1mmのピアノ線をクランクにしたが、思いのほか曲げるのに力がいって精度がでていない。あと、ボディフレームがぐらぐら。
試作3体目
上図、試作3体目(またiPhoneでの撮影で90度回転しちゃった(汗))。
クランクの曲げ加工がピアノ線では辛かったので、直径1mmの針金に変更。また、ボディフレームのぐらつきを抑えるために上に三角の屋根をつけた。だいぶん回しやすくなったが、まだ受動歩行できるほど、なめらかでない。
なめらかさを上げるには、精度をあげるしかない気がするが。
テオ・ヤンセン展に行った
12月19日に5歳の娘を連れて、お台場の科学未来館でやっているテオ・ヤンセン展(Theo Jansen)に行ってきた。前回、作品が日本に来たときは、見逃してしまったので、今回は行ってきました。
テオ・ヤンセンの公式サイトは http://www.strandbeest.com/ です。
科学未来館の公式サイトは http://www.miraikan.jst.go.jp/ です。未来館での展示は、2月14日までのようです。
静展示のみならず、手で押す動展示と、巨大な空気エンジンで動く動展示があります。
上図、手で押す動展示。娘が押しています。
静展示だけだと、たぶんがっかりだったと思いますが、空気エンジンで動く動展示は、圧巻でナレーターの女性の方が、ビーチアニマルのことを「この子たち」と呼び、また、敢えて精度の低いプラスチックパイプを使って、きしみながら動く「この子たち」を見てテオの強い情熱を感じました。
リンク機構は昔から色々なものが発明されていて、リンク長さによって動きが違う訳ですが、テオヤンセン機構は神業的な比率で、生き物のように見えるのが特徴で、テオヤンセン展の説明によるとはっきりは書いていないものの、どうも遺伝的プログラミングのようなモンテカルロ法的な手法でコンピュータを使ってこの黄金比率を見つけ出したようです。
そしてそのホーリーナンバー Tシャツが売っていました。すげー!
上図、ホーリーナンバーTシャツ。リンク機構野郎には、後光がさしているようなTシャツ!
そして、ホーリーナンバーもメモのために撮りました。
上図、でた!ホーリーナンバー!これはカメラでメモしなきゃ!(写真クリックして、オリジナルサイズ表示をクリックで数字が読めると思います)
そして、展示会場の最後に、1体1,200円のテオヤンセン機構のおもちゃ(プラモデル)が売っていて、ワークショップになっていました。が、ワークショップというか、おねえさんは、全く作り方を教えてくれません(笑)。買った後、机を貸してくれるので、勝手に作ってと言う感じ。が、このプラモデル非常に作るのが難しい。周りのファミリーもかなり苦戦していたようです。が、私は手こずりながらもとっとと完成させて先に出ましたが、残りのファミリーは完成したのか心配です。
上図、作成したテオヤンセン機構のプラモデル。1,200円。一家に1台必要ですね!