カテゴリー : HMDT

先日まで短期のバイトが入ってました


先日まで短期のバイトが入ってました。プログラマバイトです。本当に短期で一週間にも満たなかったんで、お金を稼ぐというよりは、体験を目的とした感じです。

バイトだったり新人社員だったりした場合、うちの会社は仕事のやり方ってのがある程度決まっています。まず、新規のプロジェクトをお願いする事はなくて、既存のプロジェクトに参加してもらいます。そして、あるクラスのある機能の実装をお願いします。そのときに、大体この辺のメソッドをいじればいいんだよ、という事は伝えます。作業時間の目安は2時間程度。そのくらいで出来る規模です。出来上がったら、コードレビューして確認します。

このコードレビューが肝ですね。レビューは、いつも2時間くらい。つまり、実装時間と同じくらいかけます。書いてもらったコードを、ああでもないこうでもないと突き回して、ちょっと書き直させてもらうねと言って、痕跡が残らないくらい書き換えてしまいます。プログラマにとっては、自分の書いたコードをリライトされることほどの屈辱はないですよね。レビューという名を借りて公開処刑をやっているんじゃないか、という気持ちにもなります。

いろんな人の書いたコードを見てきましたが、大体みんなロジックは間違えてないんですよ。プログラミング原語は理解している。UI Kitの使い方もオッケー。アルゴリズムも悪くない。

問題は、「それをどこに書くか」ということです。つまり、どのクラスで、どういうメソッドを切るか、というところに問題の大部分が集中します。経験の浅いプログラマの場合、多くの場面でメソッドの切り方が不適切です。不必要に細かすぎたり、同一処理が分散したりしています。レビューでは、そこを指摘して、メソッドの切り直しを行います。ロジックやアルゴリズム自体は変わらないので、コピー/ペーストで済みます。これを行うと、コードが見違えるようになり、デバッグや拡張が容易になります。

じゃあ如何にして適切なメソッドの切り方を身につけるか? いろいろ考えますが、やっぱり経験しかないですかね。個人的な意見では、適切なメソッドさえ切れれば、プログラミングにまつわる多くの問題は解決します。世の中にはユニットテストとかいう類いのものがありますが、あれはメソッドが適切に切られているということを前提としていますよね。そこが不適切だったら、テストなんか無意味です。本当は、メソッドの切り方が適切かどうかテストする手法が欲しいんですが、それはまだないでしょう。

どうにかしてここを定性化できないか、と思って書いたのがオートマ本だったんですけど、うーん、物足りないですね。ソースコードの質を上げるには、レビュアーの質に頼るしかないのか、というのが現時点での結論です。

『絶対に挫折しないiPhoneアプリ開発「超」入門』が改訂


ここ半年ほどしばらくおとなしくしていましたが、またぞろ、うちの事務所に人が入れ替わり立ち替わりやってくるようになりました。やっぱり人の動きがあると楽しいね。エンジニアのバイトも、案件ごとにお願いしてたりします。

tektek_book

さて、個人的に昔からつきあいのある、TEKTEKの高橋京介さんの著作である『絶対に挫折しないiPhoneアプリ開発「超」入門』が、改訂されましたのでご紹介。ご本人から献本いただきました。改訂版では、iOS 7に完全対応。スクリーンショットなども、すべて差し替えられています。

しかし紙の技術本って、対象ソフトの新しいバージョンが出るたんびにスクリーンショット差し替えて改訂版出してって、なんとかならんのですかね。前の本を買った人は置いてきぼりですからね。こういうところこそ、電子書籍の出番だよなー、と思います。

NSViewAnimationにcompletionを付ける


最近は、iOSプログラミングとOS Xプログラミングが半々の日々です。

OS XのApp Kitでアニメーションをしようとすると、パッと思いつくのはCore Animationフレームワークです。iOSでもおなじみの、CALayerを使ってアニメーションします。Layer-backedアニメーションってやつですね。

このCALayerによるアニメーションは、パフォーマンスもよく、様々なエフェクトもかけられるし、サポートクラスもたくさんあって、とても便利なんですけど、アニメーションさせてももとのビューが動かない、という特徴があります。見せかけだけなんですね。なので、アニメーション終了後にビューのframeを設定し直したりします。

でも、アニメーションの種類によっては、frameを動かす事が重要なものがあります。たとえば、テキストビューのアニメーションです。テキストビューの大きさが変わるアニメーションの場合、その動きに合わせて内部のテキストの配置も変わってほしいですね。これをCALayerでやろうとすると、単純にテキストが拡大/縮小するだけになってしまいます。アニメーションのステップごとに、テキストビューを毎回redrawしてやらないといけないんですよ。

そこで登場するのが、NSViewAnimation。App Kitのアニメーション周りで最初に登場した古いクラスです。こいつは、ビューのframeの大きさを変えてくれます。たぶん、タイマー回してframeの値を順々に変えているだけの、単純なものです。パフォーマンスはよくないですが、求めに応じて使い分けます。

でも、古いクラスなので、あんまり使い勝手がよくないです。いちばん面倒なのが、アニメーションの終了通知がdelegateなこと。最近はブロック記法にすっかり慣れてしまったので、completionブロックを呼び出してほしいところです。

ということで、そこだけ拡張してみました。NSViewAnimationのサブクラスとして、HMViewAnimationというのを作り、アニメーションが終わったらcompletionを呼び出すanimateWIthAnimations:duration:completion:というメソッドを追加してみました。

宣言はこんな感じ。

typedef void (^HMViewAnimationCompletion)(void);

@interface HMViewAnimation : NSViewAnimation

// Property
@property (nonatomic, copy) HMViewAnimationCompletion completion;

// Initialize
+ (void)animateWithAnimations:(NSArray*)animations
duration:(NSTimeInterval)duration copmletion:(void (^)(void))copmletion;

@end

実装はこうです。

@implementation HMViewAnimation

+ (void)animateWithAnimations:(NSArray*)animations 
        duration:(NSTimeInterval)duration copmletion:(void (^)(void))completion
{
    // Create view animation object
    HMViewAnimation*    viewAnimation;
    viewAnimation = [[HMViewAnimation alloc] initWithViewAnimations:animations];
    [viewAnimation setDuration:duration];
    [viewAnimation setDelegate:viewAnimation];
    viewAnimation.completion = completion;

    // Start animation
    [viewAnimation startAnimation];
}

- (void)animationDidEnd:(NSAnimation*)animation
{
    // Invoke completion
    if (_completion) {
        _completion();
    }
}

@end

簡単ですね。トリッキーなことは何もしていませんが、なかなかに便利です。

クラスクラスタのプリミティブメソッドの見つけ方


「The Complete Friday Q&A: Volume I」を読んでます。今日読んだのは、「Subclassing Class Clusters」の項。

Objective-CというかFoundationには、クラスクラスタと呼ばれる仕組みがありまして。これは、同じAPIを使って、異なる実装を自動的に使い分けるための仕組みです。

たとえば、NSStringはクラスクラスタとして提供されています。それが何を意味しているかというと、NSStringのインスタンスを作っても、実はそれはNSStringじゃなくて、NSCFStringやNSPathreStore2っていうクラスになっているんですね。いわば、NSStringは抽象クラスで、自動的に効率のいい実装を持つクラスが選択されています。デザインパターンでいうと、Factory Methodパターンみたいなものです。言語仕様でのサポートというよりは、Foundationで実現しているものです。

クラスクラスタは、提供されているものを使う分には楽しく使えるんですけど、サブクラス作ろうとすると、とたんに面倒になります。まずはまるのが、既存のNSStringの挙動を変えてやろう!と思ってNSStringのサブクラスを作ると、うまくいきません。これは、具体的な実装である、NSCFStringなどに対してサブクラス化やメソッド差し替えを行わないといけないです。

その次には、自前の実装クラスを作って、クラスクラスタに追加してやろう!ということを考えるでしょう。この場合は、NSStringのすべてのメソッドを実装する必要はなく、プリミティブメソッドと呼ばれるものだけをやればいい、ということになっています。他のメソッドは、最終的にはこのプリミティブメソッドを呼び出して、具象クラスにアクセスします。

で、ここからがFriday Q&Aのネタなのですが、このプリミティブメソッドをどうやって見つけるのか?基本は、APIドキュメントを読みます。確かに書いてあるのですが、特別なセクションがある訳ではなく、普通の説明の流れにちょろっと書いてあるだけなので、見落としがちです。

そこで紹介されているもう1つの方法が、ヘッダファイルのinterfaceセクションに宣言されているメソッドがプリミティテブである、というものです。それ以外のものは、カテゴリとして宣言されているのです。これも、言語仕様ではなくてマナーの範疇なので100% sureではないですが、充分に役に立ちそうです。

その観点が面白いので、めぼしいクラスのintefaceで宣言されているメソッドを抜き出してみました。

NSArray

- (NSUInteger)count;
- (id)objectAtIndex:(NSUInteger)index;

NSMutableArray

- (void)addObject:(id)anObject;
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index;
- (void)removeLastObject;
- (void)removeObjectAtIndex:(NSUInteger)index;
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;

NSAttributedString

- (NSString *)string;
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range;

NSMutableAttributedString

- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range;

NSData

- (NSUInteger)length;
- (const void *)bytes NS_RETURNS_INNER_POINTER;

NSDate

- (NSTimeInterval)timeIntervalSinceReferenceDate;

NSDictionary

- (NSUInteger)count;
- (id)objectForKey:(id)aKey;
- (NSEnumerator *)keyEnumerator;

NSMutableDictionary

- (void)removeObjectForKey:(id)aKey;
- (void)setObject:(id)anObject forKey:(id <NSCopying>)aKey;

NSString

- (NSUInteger)length;
- (unichar)characterAtIndex:(NSUInteger)index;

NSMutableString

- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString;

NSValue

- (void)getValue:(void *)value;
- (const char *)objCType NS_RETURNS_INNER_POINTER;

 

なるほど。これらがプリミティブ的に扱われている訳ですね。NSDateがtimeIntervalSinceReferenceDateだけ宣言されているのとか、面白いですね。

「The Complete Friday Q&A: Volume I」を買いました


The Complete Friday Q&A: Volume I」という本を買いました。iBooks Storeで。名前からは分かりにくいですが、Objective-Cに関する本です。

ちょっと古い本で、2011年に出たそうです。実際に書いてある内容は、筆者のBlogで2008年から2010年まで掲載されていたものなので、iPhone登場以前のOS XでのObjective-CおよびCocoaの話です。でも、いまでも問題なく通用しますよ、たぶん。

内容は、Objective-Cランタイムや、OS Xの低レベルの動作の話。僭越ですが、私の「ダイナミックObjective-C」と似たようなところをターゲットとしていますね。目次を一部抜き出してみましょう。

  • Multithreaded Programming and Parallelism Overview
  • Blocks in Objective-C(2008年の話!)
  • Interprocess Communication
  • Code Injection
  • Operations-Based Parallelization
  • Using the Clang Static Analyzer
  • Intro to the Objective-C Runtime
  • Code Generation with LLVM
  • Mac OS X Process Memory Statistics
  • Intro to Grand Central Dispatch
  • A GCD Case Study: Building an HTTP Server
  • NSRunLop Internals
  • Stack and Heap Objects in Objective-C
  • Subclassing Class Clusters

おおお、面白そうだ!こんなリストに反応してしまうのは、自分でもやっぱりおかしいとは思いますが。久しぶりにじっくり読めそうな本を見つけたので、しばらく読み込んでみたいと思います。

arm64対応でObjective-Cランタイムは速くなるのか?


Appleが、2月1日以降はiOS 7への最適が必須である、という話を去年の暮れにしていました。うちはだいたい移行は終わったんですけど、そういえばarm64対応ってのが残ってたな、と思い出しました。

そういえば、これも去年に、iOS 7でarm64対応するとObjective-Cランタイムレベルで速くなる、という話をどっかで聞いたような気がしました。あらためて検索すると、この話ですね。「Hamster Emporium: [objc explain]: Non-pointer isa

普通に考えると、64bit化しても恩恵を受けるのはムービーとかのでかいデータを扱うアプリだけで、多くのアプリはそんなに影響ないです。でも、Objective-Cランタイムが改善されるとなると、話が別です。すべてのアプリが恩恵を受ける事ができますね。件の記事では、アドレスが64bit化されることで余分なビットが生まれるので、isaがポインタではなくなって自身の中に入ってしまうと。これにより、retain countなんかのアクセスが速くなって、ランタイムの速度が上がる、という主張みたいです。

これも一度時間をとって検証してみたいですね。

iOS 7でCGBitmapContextに対する描画が変わった?


iOSでは、高速なグラフィックを実現するために、GPUの機能を積極的に利用しています。その恩恵を受けるには、UIViewが作成したCGContextRefに対してCore GraphicsのAPIを使う事が必要になります。ここで行われる描画は、オフスクリーンのビットマップに対するものとだいぶ違ったものになります。

何が違うのか、分かりやすい例として矩形の塗りつぶしを考えてみます。矩形の塗りつぶしは、もしオフスクリーンバッファに対して行うとすれば、一定領域へのメモリのセットになるんですね。たとえば、白で塗りつぶすとしたら、0xffffffでメモリ領域を埋める事になります。

スクリーンが小さいうちはこれで問題ないのですが、最近のiPad Retinaとかだと大変です。スクリーンサイズは、1536×2048。ということはオフスクリーンバッファを作るとすると、1536x2048x4byte = 12MByteということになります。12MBの領域をmemsetするとなると、これ時間かかるんですよ。iPad実機だと、1秒から2秒くらいかかります。これでは高速な描画なんかできませんね。

Core Graphicsには、矩形領域塗りつぶしのための、CGContextFillRectというAPIがあります。これの呼び出しは、時間はかかりません。ということは、このAPIではメモリのセットはやっていないはずです。おそらく、「矩形塗りつぶし」という手続きだけを覚えておいて、画面描画時にOpenGLテクスチャを作成していると予想されます。これなら充分に速くなります。

CGContextは、UIViewが用意するもの、つまりdrawRect:で取得できるものの他に、オフスクリーンバッファ用に作る事もできます。CGBitmapContextっていうものです。これで作ったCGContextは、Core GraphicsのAPIを使えるのですが、パフォーマンスでは大きく見劣りします。実際、CGContextFillRectの呼び出しにえらい時間がかかります。だから、iOSではCGBitmapContext使わないようにしましょうね。

…という話だったんです。iOS 6までは。

それが、iOS 7ではどうも変わったような気がするんです。CGBitmapContextに対するCGContextFillRectの呼び出しが、すごい速くなっているみたいです。もしかして、メモリセットしないようになった?でもそれだと、いろいろと破綻しねぇ?オフスクリーン使っているから、遅くてどうしようもなくて放っておいたコードがあるんですが、iOS 7で動かしたら速くなっていてビックリしているところです。いま、検証しようとしています。

iOSでは何故responder chainは重要でないのか


最近はOS Xプログラミングもよくやっているんだけど、iOSではあまり気にしないけど、OS Xではとっても意識するものは、responder chainですね。responder chainについては、Appleのドキュメント「Event Delivery: The Responder Chain」を参照してください。または、昔に書いた「ダイナミックObjective-C (89) デザインパターンをObjective-Cで – Chain of Responsibility (1)」もあわせてどうぞ。

どういうものかというと、普通iOSでボタンにターゲットとアクション設定するときは、addTarget:action:forControlEvents:を使うでしょ。このとき、ターゲットとなる第一引数に特定のオブジェクトじゃなくて、nilを指定します。そうすると、ボタンを押したときのアクションは、そのイベントを受け取ってくれる人(actionに対応するメソッドを持っている人)まで数珠つなぎに送られていくのです。これが、反応の連鎖をたどる、ということでresponder chainって呼ばれます。

OS Xプログラミングをやっていると、このresponder chainをとてもよく使うんですよ。なんでかなと考えてみると、OS Xで何らかの操作を行うときは、まず対象をマウスで選択して、メニューからコマンドを実行する、という流れになります。このとき、操作対象とメニューのコマンドには、強い結びつきがないんですね。たとえば、コピーというコマンドは、いろんな対象に対して送られる可能性があります。

それに対してiOSでは、多くの場合1つのタッチ操作で、対象の選択とコマンドの実行を実現しています。つまり、対象とコマンドが一対一関係ですね。その方が直感的でよろしいです。でも、機能が多くなってくると、「対象×コマンド」の数だけボタンを用意しなくてはいけないので、無理が出てくるんですよね。

ということで、マウス/キーボードのインタフェースと、タッチのインタフェースとでは、自ずとイベントハンドリングのプログラミングスタイルが変わってくるんです、という話でした。

OS Xファーストの開発スタイル


去年の末から、うちの会社では、「OS Xファースト」のスタイルで開発するものが増えました。開発するときに、まずOS Xで動かす。その後で、iOSに移植する、というやり方です。これがねぇ、とてもいいんですよ。

このスタイルになった一番の理由は、ドキュメント・ベース・アプリケーションのものが増えたからです。書類を作成するタイプのアプリ、つまりはエディタタイプのものですね。エディタものをiOSで作ろうとすると、色々面倒なんですよ。というより、OS Xの方が圧倒的にエディタに向いているんです。理由は以下の通りです。

標準のファイルブラウザが使える

OS Xには、ファイルシステムに存在するファイルを確認および操作するファイルブラウザがあります。何の事はない、Finderのことですね。忘れがちですが、iOSにはFinderに相当するものがありません。

エディタアプリだと、書類を作成するんです。そうすると、ファイルの確認や操作が必要になるんですよ。OS XだったらFinderでひょいっとのぞけばおしまいなんですが、iOSだとソースコード書き連ねてアプリ側でやらないといけない。メンドクサイ。

あと、オープンダイアログ。OS Xには標準のオープンダイアログがありますが、iOSにはないんです。だから、まず始めにオープンダイアログを自作しないといけない。超メンドクサイ。

プルダウンメニューが使える

エディタアプリは、どうしても操作のためのコマンドが多くなります。これは複雑というよりは、単に求められるものが多いんですね。たとえば、アンドゥでしょ、リドゥでしょ、カットにコピーにペーストでしょ、削除でしょ、すべてを選択でしょ…。これらをボタンとして並べていったら、iPhoneの画面がそれだけでいっぱいになってしまいます。そうならないために色々とUIを考えるんですけど、そっちにやたら時間を取られるんですよ。

でもこれがOS Xならば!プルダウンメニューがあるじゃないか!プルダウンメニューの素晴らしいところは、プルダウンできるところです!当たり前すぎて何言ってるか分からないですが、つまりいつもは隠れていて、必要なときにクリックして引き出す事のできるUIということですね。これは、多くの項目を並べるのにとても適したものなんです。

だから開発の初期段階では、とりあえずコマンドの呼び出しはメニューだけにして、中核機能だけ先に作っちゃえということができます。キャッチーでファンタスティックでバブリーなUIは、後でじっくり考えればいいのです。

キーボードが使える

エディタアプリでは、文字を入力する事が多いです。文字の入力には、そりゃなんだかんだいってもキーボードでしょう。キーボードを使うのがいちばん効率いいです。

ユーザの使い勝手だけでなく、プログラムの立場からでもそうです。iOSでは文字入力時にソフトウェアキーボードが表示されますが、あれが出てくると画面が狭くなるんですね。となると、動的にビューの大きさ変更しないといけないし、狭い画面でも使えるUIにしないといけない。うわー、メンドクサイ。

こういった理由から、まずはOS Xで動くものを作っています。もちろんプロトタイプの使い捨てではなくて、きちんと世の中に出せるレベルまで持っていきます。始めにきちんと設計しておけば、かなりのソースコードをOS XとiOSで共有できます。モデルだけではなく、コントローラクラスも共用できます。OS XではNSViewControllerのサブクラス、iOSではUIViewControllerのサブクラスとして、同じクラスに実装していきます。#ifdefだらけになりますが。

ただこうしてOS X版を作っても、リリースするかどうかはまた別の話ですね。出したとしてもどれだけ売れるのやら、という問題がありますから。

alloc+initよりも、new?


Objective-Cでインスタンス化といえば、alloc+init。もう指が覚えていて、何も意識せずに自動的に入力しちゃいます。

でも最近、いろんなソースコード見ると、newメソッドを使っているものが目につくような気がします。Stack Overflowのサンプルとか。Appleのサンプルコードでも、けっこう使われている。個人的な感覚としては、妙に気持ち悪い。やっぱりそこは、alloc+initだろ、おい。

感情的な事はさておいて、alloc+initの利点は様々なイニシャライザが使えるところですね。たとえば、NSStringだとinitWithFormat:とか、initWithBytes:length:encoding:とか。ズラズラと引数を並べていくのが、Objective-Cの伝統です。いちばん長いのは、あれでしょ。OS Xになるけど、NSBitmapImageRepの、

– (id)initWithBitmapDataPlanes:(unsigned char **)planes pixelsWide:(NSInteger)width pixelsHigh:(NSInteger)height bitsPerSample:(NSInteger)bps samplesPerPixel:(NSInteger)spp hasAlpha:(BOOL)alpha isPlanar:(BOOL)isPlanar colorSpaceName:(NSString *)colorSpaceNamebitmapFormat:(NSBitmapFormat)bitmapFormat bytesPerRow:(NSInteger)rowBytes bitsPerPixel:(NSInteger)pixelBits

 です。なげーよ。引数いくつだよ。エディタのコード補完がなければ、やってられませんね。

こんなイニシャライザメソッドですけど、ある程度流行はあって、最近のトレンドだとinit時に引数を付けないものが多いような気がします。とりあえずinitメソッドでインスタンス化して、その後で各種プロパティを設定していく、という使い方です。これはやっぱり、property構文が導入されたからなんでしょうね。あと、初期化後も可変のプロパティの場合は、プロパティのアクセッサメソッドから設定するのに一本化した方が実装もすっきりするでしょう。ARCでretainを気にしなくてもよくなった、っていうのも影響しているかな。

そう考えると、なるほどnewメソッドを使うのは理にかなっているな、と思いました。alloc+initがnewメソッド一発に置き換わるなら、それもありかと。あと、JavaやC++から移って来た人たちにとっては、やっぱりnewの文字は捨て難いのかもしれません。

とはいっても、自分で書くコードでは、やっぱりalloc+initにしちゃうよな。