開発 | HMDT Blog | ページ 7

カテゴリー : 開発

アプリの審査でリジェクトされてイラッときたこと


久しぶりに、Appleの審査でリジェクトくらってイラッときたこと。

あるアプリで、iOS 5対応を進めていた。iOS 5のAPIを一部使っている。でもiOS 4でも動かしたいんで、respondsToSelector:を使って動作環境によって切り分けるようにしていた。

で、完成して申請したんだけど、とうぜんまだiOS 5のやつは受け付けられていないんで、iOS 4 SDKでビルドして出した。そしたら、non public APIを使っているって言われて、えぇ?と思って確認したら、そのiOS 5のAPIのことだった。どうやら、そのAPIと同じ名前のものがprivate APIとしてあったらしい。

ムカチーン。なにがイラつくって、こっちは当然のプログラミングをしている。iOS 5登場が近いからその対応をしている。iOS 4でもiOS 5でも動くようにしている。private APIは、iOS 5まで含めれば、使っていない。そもそもそのAPI、iOS 4上の実際の動作では呼び出されない。

それを機械的にツールかなにかで判定して、private使っているから申請し直せって。その、アプリの動作や作りをまったく無視した審査が気に食わない。private API、使ってないっつーの。

そんなに審査したいんだったら、すべてのAPI呼び出しをチェックできるObjective-Cランタイムを用意して、その上でアプリを動作させて、本当にprivate APIを呼び出しているかどうか確認しろ。それなら許す。

属性付きラベルを作る その3


前回に続き、属性付き文字列を表示する話。

属性付き文字列を表示したら、次はそれをタッチしたくなるのが人情というものだな。えーっと、想定しているのは、文中にリンクを埋め込みたい、ということね。文章の一部を青字、または下線付きで表示する事で、リンクがあることを表す。ユーザがそれをタップしたら、情報を表示したり、別のページへジャンプする。ってことをやりたいよね。ということで、タッチイベントを拾ってみよう。

タッチイベントを受け取ること自体は簡単だ。いま作っているラベルはUILabelのサブクラスなので、touchesEnded:withEvent:を上書きすればいい。問題は、タッチしたのが文字列のどの部分なのかを特定する事だ。これを実現するには、Core TextのAPIを使って、文字列の位置情報を特定していく必要がある。

いまのところ、CTFrameというオブジェクトでテキストの描画を行っている。ここから、まず始めに、行の情報を取り出そう。CTLineというオブジェクトが行を表す。CTFrameからは、CTLineの配列と、それぞれの行の原点座標を取り出す事ができる。

CTLineからは、その行が持つタイポグラフィの情報を取得できる。ascentとかdescentとか、そういうやつ。余談だけど、ascentをアセント、descentをディセント、ってカタカナで書くと、何の事だか分かんなくなるな。アルファベットの方が理解しやすいと思う。これで、CTLineの領域が分かる。したがって、どの行をタッチしたか分かる事になる。そしてさらに、CTLineに対して座標を指定すると、テキストの何文字目にあたるかが分かる。これで、タッチした文字まで特定できるという訳だ。

以上が理屈。では、これをもとに実装をしてみよう。

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    // Get touch point
    UITouch*    touch;
    CGPoint     point;
    touch = [touches anyObject];
    point = [touch locationInView:self];
    point.y = CGRectGetHeight(self.bounds) - point.y;

    // Get lines
    CFArrayRef  lines;
    lines = CTFrameGetLines(_ctFrame);

    // Get line origins
    CGPoint*    origins;
    origins = malloc(sizeof(CGPoint) * CFArrayGetCount(lines));
    CTFrameGetLineOrigins(_ctFrame, CFRangeMake(0, CFArrayGetCount(lines)), origins);

    int i;
    for (i = 0; i < CFArrayGetCount(lines); i++) {
        // Get line
        CTLineRef   line;
        line = CFArrayGetValueAtIndex(lines, i);

        // Get origin
        CGPoint origin;
        origin = *(origins + i);

        // Get typographics bounds
        float   ascent;
        float   descent;
        float   leading;
        double  width;
        width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

        // Decide line frame
        CGRect  lineFrame;
        lineFrame.origin.x = origin.x;
        lineFrame.origin.y = origin.y - descent;
        lineFrame.size.width = width;
        lineFrame.size.height = ascent + descent;

        // Check with point
        if (CGRectContainsPoint(lineFrame, point)) {
            // Get index for position
            CFIndex index;
            index = CTLineGetStringIndexForPosition(line, point);
            if (index == kCFNotFound) {
                continue;
            }

            // Get attributes at position
            NSDictionary*   attrs;
            NSRange         range;
            attrs = [_attrStr attributesAtIndex:index effectiveRange:&range];

            // Notify to delegate
            if ([_delegate respondsToSelector:
                    @selector(attributedLabelTouchedAtIndex:attributes:effectiveRange:)])
            {
                [_delegate attributedLabelTouchedAtIndex:index 
                        attributes:attrs effectiveRange:range];
            }
        }
    }

    // Release objects
    if (origins) {
        free(origins), origins = NULL;
    }

    // Invoke super
    [super touchesEnded:touches withEvent:event];
}

CTFrameからCTLineの配列を取り出すのは、CTFrameGetLinesという関数だ。さらに、行の原点座標を取り出すのは、CTFrameGetLineOriginsという関数。こちらはCGPointの配列領域をあらかじめ確保しておく必要がある。

CTLineのタイポグラフィ情報は、CTLineGetTypographicBoundsで取得する。これを使うと、ascent、descent、leading、そして行の長さを知る事ができる。これらの情報と原点座標を組み合わせて、行のframeを計算する。タッチした座標から文字のインデックスを取り出すのは、CTLineGetStringIndexForPosition。これで、どの文字がタッチされたかが分かった。

タッチされたという情報は、デリゲートに通知する事にした。タッチした文字のインデックスに加えて、属性の情報も送ってやる。その文字がどんな属性を持っているかと、どの範囲までそれが続いているか、という情報だ。

これでできた!試しに動かしてみると、こんな感じ。文字列をタッチすると、その文字と属性とを取り出せているのが分かる。取り出した情報を元に、リンクだったら関連する動作をさせればいい。

ここまでのソースコード:AttrLabel-3.zip

属性付きラベルを作る その2


前回の続き。Cocoa touchで属性付き文字列を表示するラベルを作ろう。

今回は、属性を追加するためのメソッドを追加してみる。太字の属性。それと、文字の前景色の属性。

これらの設定を行うために、専用のメソッドを用意してみた。addBoldFontAttrWithRange:と、addForegroundColorAttrWithColor:range:だ。範囲と、前景色の場合は色とを指定する。

// Attributes
- (void)addBoldFontAttrWithRange:(NSRange)range;
- (void)addForegroundColorAttrWithColor:(UIColor*)color range:(NSRange)range;

これらのメソッドの実装は、指定した範囲と色をインスタンス変数に格納するだけ。実際に文字列に属性を付加するのは、前回説明した_refreshAttributedStringメソッドの中になる。

では、その拡張された実装を。最初の部分は変化なし。フォント属性の追加のところで、太字属性に関する記述が新たに追加されている。

    ...

    //
    // Set font attribute
    //

    // Set normal font attribute
    CTFontRef   ctFont;
    ctFont = CTFontCreateWithName(
            (CFStringRef)self.font.fontName,
            self.font.pointSize,
            NULL);
    [_attrStr addAttribute:(NSString*)kCTFontAttributeName
            value:(id)ctFont range:NSMakeRange(0, length)];

    // Get bold font ranges
    UIFont*     boldFont;
    CTFontRef   ctBoldFont;
    boldFont = [UIFont boldSystemFontOfSize:12.0f];
    ctBoldFont = CTFontCreateWithName(
            (CFStringRef)boldFont.fontName, self.font.pointSize, NULL);
    for (NSValue* boldFontRangeValue in _boldFontRanges) {
        // Add bold font
        NSRange boldFontRange;
        boldFontRange = [boldFontRangeValue rangeValue];
        if (NSMaxRange(boldFontRange) <= length) {
            [_attrStr addAttribute:(NSString*)kCTFontAttributeName
                    value:(id)ctBoldFont range:boldFontRange];
        }
    }

    // Release font
    if (ctFont) {
        CFRelease(ctFont), ctFont = NULL;
    }
    if (ctBoldFont) {
        CFRelease(ctBoldFont), ctBoldFont = NULL;
    }

    ...

太字設定は、まず太字用のCTFontオブジェクトを作る。今回は、システムのボールドフォントからフォント名を取得した。インスタンス変数の_boldFontRangesという配列に太字を設定する範囲を格納している。そこから範囲を取り出して、addAttribute:value:range:メソッドを使って属性を追加している。

続いて、前景色のための実装。

    ...

    //
    // Set foreground color attribute
    //

    // Set normal body forgrond color
    UIColor*    bodyForegroundColor;
    if (self.highlighted) {
        bodyForegroundColor = self.highlightedTextColor;
    }
    else {
        bodyForegroundColor = self.textColor;
    }
    [_attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName
            value:(id)bodyForegroundColor.CGColor range:NSMakeRange(0, length)];

    // Get foreground colors
    int i;
    for (i = 0; i < [_foregroundColors count]; i++) {
        // Get foreground color and range
        UIColor*    foregroundColor;
        NSRange     foregroundColorRange;
        foregroundColor = [_foregroundColors objectAtIndex:i];
        foregroundColorRange = [[_foregroundColorRanges objectAtIndex:i] rangeValue];

        // Add foreground color attribute
        if (NSMaxRange(foregroundColorRange) <= length) {
            [_attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName
                    value:(id)foregroundColor.CGColor range:foregroundColorRange];
        }
    }

    ...

基本的な流れは、フォントの設定のときと同じだ。色の属性は、CGColorオブジェクトとして設定することに注意。

新たに追加されたメソッドは、次のように呼び出す。

    // Set text
    _attrLabel.text = @"これは太字や色付き文字が表示できるラベルです。";

    // Add attributes
    [_attrLabel addBoldFontAttrWithRange:NSMakeRange(3, 2)];
    [_attrLabel addForegroundColorAttrWithColor:[UIColor blueColor]
            range:NSMakeRange(6, 5)];

で、実行結果がこれ。

確かに属性付き文字列が表示された!これで便利なラベルの出来上がり。

ここまでのソースコード:AttrLabel-2.zip

属性付きラベルを作る その1


iOSのCocoa touchにはUILabelというクラスがある。ご承知の通り、画面上に文字をラベルとして表示するためのクラスだ。簡単に使えて便利なんだけど、単純な表示しかできない。単一フォントで単一サイズの表示しかできない。

プログラミングを進めていくと、当然の事ながら複数のフォントやサイズが混在した、リッチテキストを表示したい、という要求が出てくる。リッチテキストを取り扱えるクラスとしては、HTMLを表示するUIWebViewがある。まぁ、大きいテキスト表示するならそれでいいんだけど、一行とか二行だけ表示したい、ってときもあるよね。または、UITableViewCellの中で表示したいとか。セルにUIWebViewを埋め込むのはいかにもおおげさだ。

つまり、属性付き文字列を表示できるUILabelのようなクラスが欲しい訳だ。ないものは作るべし!ということで、作ってみた。

まずは、どうやってリッチテキストを描画するか?ってことを考えよう。これにはCore Textを使う。Core Textは、テキスト描画のための低レベルのフレームワークだ。リッチテキストの描画機能もこれに含まれる。使えるのはiOS 3.2以降。これを使って描画する、UILabelのサブクラスを作ろう。

AttributedLabelというクラス名にする。クラスの宣言は、こんな感じ。

@interface AttributedLabel : UILabel
{
    NSMutableAttributedString* _attrStr;
    CTFrameRef                 _ctFrame;
    BOOL                       _needsRefreshAttrStr;
}

@end

NSMutableAttributedStringっていうのが、属性付き文字列を取り扱うためのクラス。便利そうな名前のクラスであるが、こいつはそのままでは描画ができず、Core Textと組み合わせて使う必要がある。

その次にあるCTFrameというのが、Core Textのオブジェクト。画面にテキストの描画を行うには、こいつを使う。

実装の方では、まず属性付き文字列を作らなくてはいけない。そのために、_refreshAttributedStringというメソッドを作る。

- (void)_refreshAttributedString
{
    NSDictionary*   attrDict;

    // Release old attributed string and frame
    [_attrStr release], _attrStr = nil;
    if (_ctFrame) {
        CFRelease(_ctFrame), _ctFrame = NULL;
    }

    // Create attributed string
    _attrStr = [[NSMutableAttributedString alloc]
            initWithString:self.text];

    // Get length
    int length;
    length = [_attrStr length];

    //
    // Set font attribute
    //

    // Set normal font attribute
    CTFontRef   ctFont;
    ctFont = CTFontCreateWithName(
            (CFStringRef)self.font.fontName,
            self.font.pointSize,
            NULL);
    attrDict = [NSDictionary dictionaryWithObjectsAndKeys:
            (id)ctFont, (NSString*)kCTFontAttributeName,
            nil];
    [_attrStr setAttributes:attrDict range:NSMakeRange(0, length)];

    // Release font
    if (ctFont) {
        CFRelease(ctFont), ctFont = NULL;
    }

    //
    // Create frame
    //

    // Create frame
    CGMutablePathRef    path;
    CTFramesetterRef    framesetter;
    path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    framesetter = CTFramesetterCreateWithAttributedString(
            (CFMutableAttributedStringRef)_attrStr);
    _ctFrame = CTFramesetterCreateFrame(
            framesetter, CFRangeMake(0, length), path, NULL);
    CGPathRelease(path);

    // Clear flag
    _needsRefreshAttrStr = NO;
}

このメソッドでは、まず古いオブジェクトを破棄して、新しいNSMutableAttributedStringオブジェクトを作る。そして、これに属性を付加していく。とりあえず、フォント属性を指定してみた。ここで注意してほしいのは、フォントの指定にはCTFontを使う必要があること。そして属性辞書を作るんだけど、このとき指定するキーもCore Textで用意されているものを使うこと。これらに注意。

最後にCTFrameを作る。まず始めに、CGFramesetterを作る。CTFramesetterCreateWithAttributedStringという関数があるので、これを使う。これで属性付き文字列に紐付けられたCGFramesetterができる。これに、さらに描画領域を表すパスを紐付けるためにCTFramesetterCreateFrame関数を使い、CTFrameを作る。これで、描画準備完了だ。

あとは、drawRect:で描画してやればいい。

- (void)drawRect:(CGRect)rect
{
    // Refresh attributed string
    if (_needsRefreshAttrStr) {
        [self _refreshAttributedString];
    }

    // Get current context
    CGContextRef    context;
    context = UIGraphicsGetCurrentContext();

    // Get bounds
    CGRect  bounds;
    bounds = self.bounds;

    // Save context
    CGContextSaveGState(context);

    // Flip context
    CGContextTranslateCTM(context, 0, CGRectGetHeight(bounds));
    CGContextScaleCTM(context, 1.0f, -1.0f);

    // Draw frame
    CTFrameDraw(_ctFrame, context);

    // Restore context
    CGContextRestoreGState(context);
}

描画の前に、コンテキストをひっくり返してやる。Core Textは左下原点なんでね。そのまま書くと天地が逆転した文字列が描かれる。そして、CGFrameDrawを呼ぶ事で、画面に描画される。

まずは、普通に文字列を描いてみた。次回は、いろいろと属性を付加してみる。

ここまでのソースコードAttrLabel-1.zip

iOS 4でUIPageViewControllerみたいなものを


iOS 5から、UIPageViewControllerというビューコントローラのクラスが追加される。一言で言うと、iBooksみたいなページめくりを実現してくれるクラスだ。これを使えば、誰でも簡単に書籍みたいなページめくりができる。

便利なんだけど、これを使っちゃうとiOS 4はどうするよ?って問題が出てくる。iOS 5専用にしちゃうという手もあるが、一世代前のOSくらいまではサポートしたいよな。

うーむ、と悩んだ結果、無いものは作れ!といういつもの結論に到達。ということで、iOS 4でも動くUIPageViewControllerの代替クラスを作った。APIはいっしょ。滑らかにページめくりしてくれる。

いまのところ、とりあえず動いている段階だけど、ソースコードが整理できたらどこかで公開するかも。

MacでARMでUniversal Binaryの悪夢再び?


マイコミジャーナルより、『加速する「MacでARM採用」の噂、議論の存在をIntelが認める』。

マジすかー。PowerPCとIntelのUniversal Binary対応が終わったと思ったら、次はIntelとARMのUniversal Binaryすか。3つ同時にサポートすることは、共通して動くOS Xのバージョンがないから、考えなくてもいいな。

しかし、PowerPCからARMだったら、基本のエンディアンが同じだからまだ楽だったろうに。間にIntel挟むから面倒。ま、上位の方しか作らないアプリ開発屋は、あんまりエンディアンの影響受けないけどね。

iPhoneアプリでUDIDを使いたいケースその2


追記による注意:この記事は、はなはだしい勘違いに基づいていて、内容が破綻しています。修正しようにも、最も大事なところが勘違いしているためほぼ全部書き換えになってしまうので、しょうがないのでそのままにしておきます。最後に何が問題だったか書いておくので、そちらを参照して下さい。ということで、そのままの中身を信じないで下さい。

前回に続いて、iPhoneアプリでUDIDを使いたくなるケーススタディ。

あるiOSアプリがあったとして、自宅ではiPadの広い画面でゆうゆうと使いたい。通勤中はiPhoneで使いたい。それぞれのデバイスで、作業を別々に進めていく。どこかで同期を行う必要がある。こういった状況を考えよう。

まず大前提として、iOSのモデルは1アカウントに対して複数デバイスを運用する。これらのデバイスは、iTunesを通じてアプリおよび書類を同期できる。逆に言うと、複数デバイスで異なるデータを保持し続けることを想定していない。

もちろん、ほとんどの状況において、データの同期は善だ。iPhoneとiPadとで違うデータがたまり続けたら目もあてられない。そのためにiTunesを使ったり、同期のための独自サーバを立てたりする。iOS 5からはiCloudも提供される。

でも実際にアプリを使っていると、同期したくないデータも出てくる。たとえば、ブックビューワみたいなアプリを考える。iPhoneでもiPadでも同じ本棚を共有して本が読めるとする。このとき、iPhoneで呼んだ続きをそのままiPadで読みたい、という要求があるだろう。それと同時に、iPhoneで読んだ本はiPhoneで読み続けて、iPadではiPadのものを読み続けたい、という要求も発生するだろう。

現在の同期モデルだと、前者しかできない。すべての書類が同期されてしまうので、「前回読んだ本」を表すデータもただ一つしか存在できないからだ。となると、そのデータをデバイス毎にひも付けた形で保存しておきたいことになる。、、、UDIDさんの出番ですよー。

UDIDが使えれば、それをキーとしてデータを保存しておけばいい。生のデータを使うのが嫌であれば、ハッシュ値でも何でもとればいい。UDIDが使えないとなると、デバイスを識別する方法がなくなっちゃう。仮に自前のUUID作ったとしても、それがデバイス間で同期されたら意味ないし。

デバイスのモデル名を使うという方法があるかもしれない。UIDeviceクラスからは、@”iPhone”や@”iPad”という現在使っているデバイスのモデル名が取得できる。なるほど、これならiPhoneとiPadは識別できるな。、、、一人でiPad二台使ってたらどうすんの?

というわけで、やっぱりUDIDは欲しい。ま、これに関してはMACアドレスで代用は可能だけど。ネットにつながない「閉じた」アプリケーションであっても、複数デバイスを運用する限り、どのデバイス上で動いているか識別したい要求は存在する。

追記による注意:はい、どこが勘違いしていたでしょうか?「iTunesの同期によって、デバイス間の書類も同期される」というところです。実際は、同期されません。したがってUDIDを取得する必要もありませんでした。

もともとこの記事は、UDIDが必要になるケースって何があるかな?と考えて、複数デバイスを使い回しているときにUDIDを意識することがあるかも、というところから書いていきました。その結果、都合のいい勘違いを自分の中で作ってしまったようです。ということで、迷惑をかけた人がいたら、申し訳ありませんでした。

UIDeviceのuniqueIdentifierがdeprecatedに


iOS 5 beta 6で、UIDeviceクラスのuniqueIdentifierプロパティが非推奨(deprecated)になった。将来なくなるってこと。んな、beta 6まで進んだこのタイミングで言われても、という気はする。

uniqueIdentifierプロパティは、デバイス個体のID(UUID)を取得するためのメソッド。これを使うと個体を識別できる。値自体は特に秘密ではなくて、iPhoneをMacにつないで、iTunesを使って確認できる。個体識別番号っていうと、ちょっとでもIT系のニュースを聞きかじっている人なら、即セキュリティやプライバシーの問題を思い浮かべるよね。実際、UUIDを許可無しに流出させるアプリがあるってニュースもあったし、穴はふさいどけ、ってことなんでしょう。

開発側からすると、別にプライバシーを暴くためでもなんでもなくて、UUIDを使いたいっていうケースは存在する。それは実行環境の識別を行いたいから。もっと細かく言うと、デバイスの個体識別と、ユーザ識別がある。

デバイスの個体識別は、ユーザがアプリを複数のデバイス(たとえばiPhoneとiPad)で使っているとき、いまどっちのデバイスで動いているの?というのを知りたいとき。これをやるには、デバイスのUUIDが不可欠なんだよね。これが取れないとなると、MACアドレスを使うか、そもそも個体識別が必要ないように仕様を変えるしかない。

ユーザ識別は、いま使っているユーザを他のユーザと区別したいとき。これをやるにはUUIDは完全ではないんだけど、手軽なソリューションだった。

ユーザ識別をしたいときってのはIn App Purchaseが絡むときが多いので、そのAPIを使うこともできる。でも、あれを使うとユーザにApple IDの入力を頼むことになるんで、警戒されるんだよね。

まぁ、どっちにしろUUID以外のきちんとしたソリューションは考えておかなくちゃいけなかった訳で。今回は先延ばしにしていたものに対して、尻を叩かれたかって感じだ。 the domain shops

iPhoneアプリでUDIDを使いたいケースその1


iOS 5からUDID取得APIがdeprecatedになるぞ、ってのを受けて、どんなときにUDIDを使いたくて、その代替策はどうなるかというケーススタディ。

まずは、機能を制限解除型で購入するアプリ。あるアプリをダウンロードして、一部の機能に制限がかかっている。In App Purchaseで購入することで、その制限を解除することができる。これをどうやってアプリで管理する?または、悪意あるユーザのクラックからどうやって守る?

いちばん簡単なのは、NSUserDefaultsにフラグを設定すること。

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"purchased"];

って感じでね。簡単だけど、初期設定のplistを書き換えることで、簡単に破られてしまう。

じゃあってんで、何らかの固定文字列のハッシュ値を書き込むようにしたらどうだ?これなら、もとの固定文字列がばれない限り破られない。これは、一度だけ誰かが購入して、そのplistをコピーすれば破られてしまう。CFUUIDでUUID作っても、同じ。そのもとのUUIDをどこに保存するの?ってことになる。

そこで、UDID登場。UDID + 固定文字列のハッシュ値を使うようにする。これならこの値が流出しても、同じUDIDを持つデバイスでしか使えない。少しまともになった。

この手法の問題点は、機種変更すると使えなくなってしまうこと。フューチャーフォンとは違い、iPhoneでは機種変更したら前のアプリが使えなくなってしまう、というふざけたことは許されない。これは、In App Purchaseを使っているときは、リストアすることで対応できる。

別の手法を検討してみる。キーチェーンを使う方法。キーチェーンの中に、購入済みフラグを設定する。いまのところ、これは使える。

問題点は、キーチェーンの中に保存した情報は、アプリを削除しても残ること。制限解除キーが永遠に残るのは気持ち悪い。あと、キーチェーンって基本的には、ネットワークサービスのパスワードのように、ユーザが何度も入力するのが面倒な情報を保存しておくものだ。つまり、ユーザがすでに知っている情報が保存されているっていうのが前提。制限解除キーみたいに、ユーザから隠したい情報を入れておくのってどうなのよ、という気はする。もしかすると、将来AppleがiPhone版のキーチェーンアクセスみたいなものを出してきて、キーチェーンの中の情報を自由に見れるようにするかもしれない。そうなると、お手上げ。

また別の方法を。今回はIn App Purchaseで購入するというのが前提なので、購入したかどうかの判断をStore Kitを使って行うことができる。iTunesのサーバにアクセスして、購入履歴をリストアすることで判断する。

これの問題点は、ネットワークアクセスが必要になることと、毎回ユーザにApple IDの入力を求めることになること。これはめんどくさい。また、Store Kitへのアクセスをユーザの操作無しに行うと、アプリ申請時にリジェクトされる恐れがある。Store Kitのアクセスは、アプリを削除して新規インストールした、バックアップもなくしちゃいました、というときに留めておくのが無難。

ということで制限解除の許可には、ユーザを識別できて、かつ書き換え不可能なIDが必要なわけで、それにはUDIDは手軽な手段だったんだよね。そういったIDは、他にはApple IDがあるけど、これアプリからは全うな手段では取得できないし。ユーザからアクセス不可な領域ってことでキーチェーンを使うってことになるんだろうけど、なんか不安があるんだよな。

iOS SDK 5 beta 6登場と、新OSへ対応することについて


iOS SDK 5 beta 6が出たという事で、絶賛インストール中。iOS 5もbeta 6まで到達したという事で、対応を行うアプリ開発側も本格化してくる頃でしょう。

新しいバージョンのOSが出ると、アプリ側の対応は大きく分けて2つある。一つは、新しいOSの新機能を採用する対応。やっぱり新機能はいち早く取り入れたいよね。この手の対応は、beta 1が出たらすぐ検討に入る。イケルかも?と思ったら、開発開始。beta 1から正式版リリースまでは、だいたいいつも二ヶ月くらいなので、それに間に合わせるのが必須。

もう一つは、既存アプリの動作検証。新しいOS上で、前のバージョンとまったく同じように動いてくれるかどうか、確認する。この対応は、新OSのベータ番号が上がってくれないと進めらんないんだよね。ベータバージョンが浅いうちは、Appleの問題なのか、こっちの問題なのか、よく分からない事があるから。ある程度こなれてきてから、一気に行う事になる。

新OSが出たら、昔のアプリは動かなくなるものなのか?動かなくなるのは、Appleのバグなのか?まー、これが何とも言えない。Appleのせいとも、開発者のせいとも、言い切れない事もある。

新OS対応で厄介なのは、既存のAPIの挙動が変わっている事があること。ドキュメントにも書かれずに。これ、たまにある。もちろん、追加された新規APIや、既存のAPIの引き数が変化したりしたものは、ドキュメントに書かれている。開発者側もすごく気をつけて、チェックを行う。

でもたまに、APIは変化しないくせに、中身の挙動が変わっている事があるんだよね。推測では、APIは変更せずに、実装のソースコードだけ変えたんじゃないか、って考えられるもの。これがくせもの。API変更してないから問題ないだろう、と思っていたら、なんかしらないけど動かなくなっている。がんばって調べてみると、微妙に挙動が変わっていて、それが影響したりする。

これ、一概にAppleが悪いとは言い切れないんだよね。APIドキュメントに書いてある通りには動いているから。ただ、ドキュメントに書いていない範囲の動作が、前のOSと変わっている。だから、別に嘘はついていない。素直にAPIを使っているアプリでは問題ないんだけど、ギリギリといろんな機能を追加していると、問題が出やすい。そこんところの見極めは、いつも難しいね。

あと、もう一つ厄介なのが、後方互換性の確保。iOS 5対応するには、iOS 5 SDKでビルドしないといけない。そのバイナリが、iOS 4以前でちゃんと動くか?ってのを確認しなきゃいけない。基本的には、iOS 5の新APIを使っていなければ問題ないんだけど、これもたまに落とし穴があったりする。

iOS 5対応で遭遇した問題は、まだNDAの範疇にあるはずだから書かないけど、そのうち改めて書くかも。

そうそう。Undocumented APIを使っているってのは、論外だよ。そんなことして、OSのバージョン上がるたんびにいちいち時間取られるのはバカバカしい。App Storeで配信するアプリでUndocumented APIを使うのは、リスクは高いし労力も多いから割に合わない。 nimbus cloud . Ivseiranrini .