2012年4月29日日曜日

iPhoneアプリUniversal化



iPhoneアプリをiPadと使い分けするUniversalアプリに拡張する手順です。

Storyboardの追加Menu > New > File... > iOS > User Interface > Storyboard  [Next]

Device Family > iPad  [Next]
   
保存先にlprojフォルダを選択する。ファイル名はなんでもよいが、XcodeでUnivarsalプロジェクトを作る場合は"MainStoryboard_iPad.storyboard”になる。

プロジェクトの変更iPhoneからUniversalに変更する。

iPad用のセクションが追加される。
Storyboardを1で作成した名前に変更する。

Storyboardの設定

iPhoneと同じ部品を配置していく場合は同じ要領で行えばよいのですが、SplitViewをStoryboardを使用する場合は少し注意が必要です。パレットからUISplitViewをStoryboardに配置し、そのままビルド/実行すると白い画面が表示されます。UISplitViewは初期状態では左側のMasterビューが表示されません。横方向表示にすると表示されるのですが、初期状態では縦方向のみの表示となっています。
Masterビューを表示させるには、次の方法があります。

縦・横位置変更時にナビゲーションバーにMasterの表示/非表示切り替えをボタンを配置する方法(フレームワークの標準)
  • iPhoneアプリのメイン画面用のクラス、あるいは右側のDetailView用のクラスを作り、DetailViewのクラスに設定する。(以下MyDdtailViewControllerとする。)
  • @interfaceに
    UISplitViewControllerDelegateプロトコルを追加し、少なくとも次のデリゲートメソッドを追加し、パラメータで渡される(UIPopoverController *)pcをインスタンス変数に保持する。(初期状態が縦方向の場合は表示前にも呼ばれる。)
    - (void)splitViewController:(UISplitViewController *)svc
                   willHideViewController:(UIViewController *)aViewController
                                    withBarButtonItem:(UIBarButtonItem *)barButtonItem
                               forPopoverController:(UIPopoverController *)pc
    {
          popoverController = pc;
    }
  • アプリケーションデリゲートの
    didFinishLaunchingWithOptions、ないしはUISplitViewControllerの
    viewDidLoadでMyDdtailViewControllerオブジェクトをSplitViewのdelegateに設定する。(下の"Xcodeが作るアプリケーションデリゲートのメソッド"参照)
  • Detailのナビゲーションバーにボタンを追加し、アクションメソッドでMasterビューを表示させる。(ボタンはMasterView非表示時のみ表示するのが望ましい。)
    - (IBAction)showMaster:(id)sender
    {
        [popoverController presentPopoverFromBarButtonItem:sender                              permittedArrowDirections:UIPopoverArrowDirectionAny
                                                             animated:YES];    
左側のビューを常に表示させておく方法(iPadの設定画面などで使われている方法)
  • UISplitViewControllerのサブクラスを作り、StoryboardのSplitViewに設定する。
  • shouldAutorotateToInterfaceOrientationをオーバライドする。
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
        return YES;}
横位置表示に対応する

横位置でも表示されるようにするにはUISplitViewControllerのサブクラスで次のメソッドをオーバライドします。Master、Detailのクラスで行っても効果ありません。
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
   return YES;
}

Xcodeが作るアプリケーションデリゲートのメソッド

    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
        UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
        splitViewController.delegate = (id)navigationController.topViewController;
    }

横位置を許す場合はUISplitViewControllerのサブクラスを作る必要があるので、その場合はそのクラスのviewDidLoadで同等のロジックを実装することができます。

- (void)viewDidLoad
{
    [super viewDidLoad];
    UINavigationController *navigationController = [self.viewControllers lastObject];
    self.delegate = (id)navigationController.topViewController;     
}

Universal化する前に多言語対応している場合に次のエラーが発生することがあります。その場合はiPhoneの言語設定を行うと解消します。

reason: 'Could not find a storyboard named 'MainStoryboard_iPad' in bundle NSBundle ...

2012年4月20日金曜日

Manifest+localStorageによるオフラインアプリ作成(1)



iPhone, iPod touchをメインターゲットに、HTML5のCache Manifest+localStorage+JavaScriptでネイティブアプリのような操作性を実現する方法を紹介していきます。

HTML5のmanifestと localStorage はオンライン時のレスポンス向上、負荷軽減が主目的と言えますが、これらを利用することでiPod TouchのようなWiFiしかない機器でネットに接続されていないときでも動作するアプリを作ることが可能です。

これまでに作ったアプリは Nack Lab で公開しています。
HTML+JavaScriptですから他のプラットフォームでも動作可能ですが、主にHTML5/CSS3についてはSafari/iOSを対象としているため、それ以外では動作不可/不良もあります。
iPhoneの場合はアイコンを「ホーム画面に追加」するとネイティブアプリらしくなります。

Cache Manifestの概要

詳細は様々ページで紹介されているのでそちらに譲り、ここではこのようなアプリを作るときの留意点を挙げておきます。

manifestはhtmlの最初の二行に次の要領で宣言します。一行目はHTML5であること、二行目は使用するmanifestファイルの指定です。

<!DOCTYPE html>
<html manifest="index.manifest">

これによりindex.manifestに列挙したリソースをローカルにキャッシュし、ネットワークにアクセスせずに使用することができます。manifestには次の3つの区分があります。


#CACHEキャッシュ済みのデータがあればそれを使うリソース。
#NETWORK必ずネットワークから取得するリソース。
#FALLBACKネットワークから取得できなかった場合の代替。

基本的な使い方は、変更があまりないものを#CACHEに設定しておき、通常はキャッシュされたリソースを使うようにするというものです。

ネット切断時に動作するWebアプリを作るには、対象Webページのhtmlと、そのロード中にアクセスされるリソースを全てmanifestに含めます。これにはLINK、SCRIPT、IMGなどでアクセスされるCSS、JavaScript、イメージも含まれます。また、manifestを宣言したhtmlも含めておく必要があります。

Aタグやボタンなどがクリック(タップ)されてからアクセスが発生するものや、XMLHttpRequestでアクセスするものは必須ではありません。そのhtml内で使用するリソースはキャッシュする意味がありますが、他のhtmlに移動する場合は移動元のhtmlでキャッシュしておいても意味はなく、移動先のhtmlでmanifestを設定しておかなければオフライン時はページにアクセスできす、エラーとなります。

キャッシュされたデータを更新するにはmanifestファイルの内容を変更します。タイムスタンプを変更しただけではキャッシュは更新されません。また、Webブラウザのキャッシュをクリアするとmanifestによるキャッシュもクリアされます。

manifestを変更した場合、その直後のアクセスではキャッシュが使われ、バックグランドでmanifestのチェック、変更があった場合はダウンロードが実行されます。そのため、変更が反映されるのは次のアクセス(以降)になります。

このバックグランド処理は結構時間がかかるため、manifest変更後にリロードを繰り返してもなかなか変更が反映しないという経験をしがちです。applicationCacheのonupdatereadyイベントが発生するまではダウンロードは完了していません。

またindex.htmをmanifestに加えると、htmlを変更したときはmanifestも更新する必要があります。htmlタグからmanifestをはずした場合も、その変更を有効にするためにmanifestファイルを更新する必要があります。これを忘れると一見manifestを使っていないのに、いつまでもキャッシュデータにアクセスし、変更が反映されないhtmlになってしまいます。

参照:Cache Manifestを無効にする方法

WWWサーバーにCache ManifestのMIME TYPEが設定されている必要があります。Apacheの場合はCache Manifestを置くディレクトリに、次の行を含む.htaccessを置きます。
拡張子の推奨が".manifest"から".appcache"に変更されています。
HTML5 Tracker ― 5812.

AddType text/cache-manifest .appcache

サーバーがCache ManifestのMIME TYPEを設定しておらず.htaccessも参照しない環境の場合は、Cache Manifestは利用できません。

localStorage概要

HTML5のlocalStorageはCookieと似ている点がありますが、主に次の点が異なっています。
  • 容量がCookieの4Kバイトに対し、2M~10Mバイト程度ある。
  • キー/バリュー形式でデータを扱う。
また、localStorageはウインドウを閉じたあとも保存されます。

Cookieでも類似のことはできますが、より広範囲に応用できると言えるでしょう。
IE8でもオプションの「DOMストレージを有効にする」がオンになっていれば使えます。

localStorageの消去はブラウザによって異なりますが、iOSのSafariの場合は「Cookieとデータを消去」により消去されます。

localStorage自体はオフラインで使う上で特に問題はありませんが、オフラインアプリとして利用するにはhtmlにCache Manifestが設定されていることが前提となります。

localStorageの使い方はいたってシンプルで、次のことを知っておけば十分です。

  //データ読み出し(keyは項目名)
  var stringData = localStorage[key];
  //または
 localStorage.getItem(key);

  //データ書き込み(dataは文字列として保存される)
  localStorage[key] = data;
  //または
  localStorage.setItem(key, data);

  //localStrageからの個別データ削除
  localStorage.removeItem(key);

  //localStrageからの全データ削除
  localStorage.clear();

  //サイズチェック
 localStorage.length;

localStrageサポートチェック

次のようにlocalStorageが定義されているかどうかでチェックしたいところですが...
  if (typeof localStorage == "object") {
    //localStorageを使った処理
  }

IEでは[DOMストレージを有効にする]にチェックが入っていないとlocalStorageが使えませんが、(typeof localStorage)が"object"となります。次のようにすればより確実です。

 if  (typeof localStorage == "object" && localStorage != null) {
    //localStorageを使った処理
  }

2012年4月16日月曜日

JDI thread evaluation' has encountered a problem


Eclipseで次のエラーが発生することがあります。

"JDI thread evaluation' has encountered a problem. Exception processing async thread queue"

デバッグの"Watch"に追加してある項目を評価するときにエラーが発生していると、この状態になります。"Watch"に"."で結合されている項目があったら、まずそれを削除してみる。それでもダメなら、順次削除して試してみましょう。

JDI Thread Evaluations has encountered a problem

2012年4月13日金曜日

invalid number of rows in section


*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

UITableViewのCellを削除した直後の再描画でtableView:numberOfRowsInSection:が返す行数が直前の行数-削除した行数になっていない場合、あるいはCellを追加した場合は+追加した行数になっていない場合に発生します。

例えばNSArrayの要素とUITableViewのCellが対応しているような場合、Cellを削除したら同時にNSArrayの要素も削除しないと発生します。

property 'someProperty' requires method 'someProperty' to be defined


... warning: property '...' requires method '...' to be defined - use @synthesize, @dynamic or provide a method implementation in this class implementation @implementation MYAppDelegate

@interfaceで@propertyを宣言したが、それに対応するメソッドが見つからない場合に発生します。

対処:
(1)@synthesizeで合成する。
(2)@synthesizeを使わない場合は命名規則に沿ってメソッドを実装する。

例1:
LocationsサンプルのpersistentStoreCoordinatorに関する部分。
persistentStoreCoordinatorをreadonlyとするため、@propertyで宣言したものと同名のインスタンス変数、メソッドを追加する。
readonlyなのでgetterメソッドのみ追加されている。このgetterメソッドがないとこのエラーが発生する。

@interface LocationsAppDelegate : NSObject <UIApplicationDelegate> {
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
}
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@end

@implementation LocationsAppDelegate
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
   ....
   return persistentStoreCoordinator;
}
@end

exit()使うべからず


How do I programmatically quit my iOS application?

iOSのアプリケーションを明示的に終了させるAPIはありません。

exit(0)を使う例が見つかりますが、クラッシュした状況と同じ状況となるため、使用すべきではないと勧告されています。これを守らいないと、やたらとクラッシュするということで、AppStore承認が得られなくなることもあるようです。

アプリ終了はiOSのコントールに委ね、アプリ側ではUIAppilicationデリゲートメソッドで然るべき対処を行う、というのが適切な実装というこのになります。

//ホームボタンが押されたときにトップページに戻しておく方法
- (void)applicationWillResignActive:(UIApplication *)application
{
    UIViewController *navCtr = [_window rootViewController];
    if ([navCtr isKindOfClass:[UINavigationController class]]) {
        [(UINavigationController *)navCtr popToRootViewControllerAnimated:NO];
    }
}

Number Padに「完了」ボタンを付ける方法


キーボードの中で、なぜかNumber Padに完了(Done)ボタンがありません。デザイン上の理由かもしれませんが、ちょっと不思議です。
ナビゲーションバーの「完了」ボタンでアクションを起こすなどすればよいので実際に困る状況はそれほどないかもしれませんが、それでは不十分と思う人がいて、検索するとNumber Padに完了/Doneボタンを付加する方法がいくつか見つかります。

大きく分けると次の二通りあります。

(1)ViewControllerのサブビューに追加する方法
ナビゲーションバーの「完了」ボタンのかわりにNumber Padのすぐ上(あるいはNumber Pad表示後に隠されない位置)にナビゲーションバーなどを配置します。
Number Padのみを使うモーダルビューの場合はお勧めです。
Number Pad以外でもサイズが変化しない英語キーボードであれば利用できますが、日本語キーボードの場合は変換候補がキーボード外に表示されるため、位置調整が難しくなります。


ナビゲーションバーの場合の位置設定



(2)キーボードにボタンを追加する方法
KeyboradのViewを見つけ、その中にボタンを配置します。
この方法ではNumber Padの左下の空きスペースにボタンを配置するのがよさそうです。
この場合は(1)よりも次の手順が増えます。また非公開の仕様を利用するので、将来使えなくなる可能性があります。
  • Keyboradがサブビューに追加されるのはdelegateメソッドの後に行われるため、NotificationCenterに登録し、UIKeyboardWillShowNotificationの通知を受け取る。
  • キーボードのビューを探す。
  • キーボードのビューに完了ボタンを追加する。

実装例

UIKeyboardTypeNumberPad and the missing "return" keyを基に、Storyboardを利用して少し作りやすくしたものです。

UIButtonを画面に配置する。 
この例では位置、サイズはプログラムで設定するので、どこに配置してもよい。


プロパティーのTypeをCustomに設定し、背景色、文字色を設定する。


MYViewControllerのOutlet/doneButton、Action/doneAction:に接続する。

@interface
  @property (strong, nonatomic) IBOutlet UIButton *doneButton;
  - (IBAction)doneAction:(id)sender;
@end

@implementation

- (void)viewDidLoad
{
    [super viewDidLoad];
    //Notificationの登録。Number Padを使用するときだけ登録する。
    [[NSNotificationCenter defaultCenter] addObserver:self 
                               selector:@selector(keyboardDidShow:) 
                                  name:UIKeyboardDidChangeFrameNotification 
                                 object:nil];
    //doneButtonをビューから切り離しておく。
    [doneButton removeFromSuperview];
}

//Notification登録解除
- (void)viewDidUnload
{
    [super viewDidUnload];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

//iOS 6以降 はviewDidUnloadが呼ばれないのでdeallocにも実装しておく。
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

//Keyboard表示直前に呼ばれる。
- (void)keyboardDidShow:(NSNotification *)note
{
    //どのWindowにKeyboardがセットされていてもよいようにループする。
    for(UIWindow *w in [[UIApplication sharedApplication] windows]) {
        //Keyboardが見つかったらループ終了。
        if ([self setDoneButtonToNumberPad:w]) break;
    }
}

//KeyboardがみつかったらdoneButtonをセットし、YESを返す。
//最後までKeyboardがみつからなかったらNOを返す。
- (BOOL)setDoneButtonToNumberPad:(UIView *)view
{
    for(UIView *v in view.subviews) {
        if ([v.description hasPrefix:@"<UIPeripheralHostView"]) {
            //Number Padの左下の空白セルに合わせて位置、サイズをセットする。
            float h = v.frame.size.height/4;
            CGRect r = CGRectMake(0, h*3, v.frame.size.width/3, h);
            doneButton.frame = r;
            //Keyboardのビューに追加する。
            [v addSubview:doneButton];
            return YES;
        }
    }
    return NO;
}

//doneButtonタップ時のアクション。
- (IBAction)doneAction:(id)sender
{
  //処理実行
}

Number Padの判断

Number Padが使われるかどうかをNotification登録時に判断できる場合は問題ありませんが、通知を受けてから判断する必要がある場合はNumber Padであることを直接判断できる情報がみつからないため、ちょっと面倒です。
Number Padの場合はそのsubviewの中に含まれるキー(UIKBKeyView)が12個と他のキーボードより少ないので、その数で判断できます。

//UIPeripheralHostView内のUIKBKeyViewを探し、arrayに追加する。
- (NSArray *)findKeyViews:(UIView *)view array:(NSMutableArray *)array
{
    for(UIView *v in view.subviews) {
        if ([v.description hasPrefix:@"<UIKBKeyView"]) {
            [array addObject:v];
        } else {
            [self findKeyViews:v array:array];
        }
        //12個を超えたらreturnする。
        if (array.count > 12) break;        
    }
    return array;
}

- (BOOL)setDoneButtonToNumberPad:(UIView *)view
{
    for(UIView *v in view.subviews) {
        if ([v.description hasPrefix:@"<UIPeripheralHostView"]) {
            float h = v.frame.size.height/4;
            CGRect r = CGRectMake(0, h*3, v.frame.size.width/3, h);
            doneButton.frame = r;
            NSArray *arr = [self findKeyViews:v array:[NSMutableArray arrayWithCapacity:13]];
            //12個の場合がNumber Pad
            if (arr.count == 12) {
                [v addSubview:doneButton];
            }            
            return YES;
        }
    }
    return NO;
}

2012年4月10日火曜日

UITableView dataSource must return a cell


*** Assertion failure in -[UITableView _createPreparedCellForGlobalRow:withIndexPath:], ...
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'

UITableViewのdelegateメソッド-tableView:cellForRowAtIndexPath:がnilを返すと発生します。
セルオブジェクト再利用のために次のようにインスタンスを取得しますが、
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
Storyboardで設定したCellのidentifierとCellIdentifierが一致していないとnilとなります。

プロトタイプセルを使用していない場合はメソッドでCellインスタンスを作成します。
プロトタイプセルを使用している場合はStroyboardのidentifierが正しく設定されているか確認します。

duplicate symbol エラー


ld: duplicate symbol _OBJC_METACLASS_$_Person in
....
clang: error: linker command failed with exit code 1 (use -v to see invocation)

同じ名前のクラス、変数、メソッドなどが使われている場合に発生します。変数、メソッドの場合はソース編集中に警告が出るので悩まされることはあまりないと思いますが、クラスの場合はビルドするとエラーが出ます。

既存のCore DataのNSManagedObjectサブクラスを更新するときなどに既存のソースと異なる場所に出力すると、同名クラスがふたつプロジェクトに追加され、このエラーが発生します。

誤った場所に追加したソースをXcodeで削除します。

また、.hをimportすべきところを、誤って.mをimportしても発生します。importの場所に.mがコピーされ、同じクラスが二度コンパイル対象となってしまうためです。

Create NSManagedObject Subclass... 実行後この状態になったが余分なソースが見当たらないという事態が発生しました。プロジェクトのBuild Phase/Compile Sourcesをみると当該ソースが赤字になってます。
当該ファイルをXCodeでdelete/Remove Referenceを行い、再度Add Files to ...でプロジェクトに追加することで回復しました。



2012年4月6日金曜日

Undefined symbols for architecture i386


エラーの例:
Undefined symbols for architecture i386:
  "someVar", referenced from:
    - [SomeClass someMethod:]  in SomeClass.o
ld: symbol(s) not found for architecture i386
clang: error: linker command failed with code 1 .....








リンク時にシンボルが見つからない場合に発生します。

(1).hはimportしたが、frameworkをリンク対象に追加していないときに発生します。.hをimportすることでシンボルが解決されコンパイルは通りますが、ライブラリがないとリンクできず、エラーとなります。
PROJECT > Build Phase > Link Binary With Librariesでframeworkを追加します。

(2)インクリメンタル・コンパイルのため、変更のないソースはコンパイルされません。そのソースが参照する変数などが削除されると、コンパイル済みの.oをリンクするときに参照先がなく、エラーとなります。いったんcleanし、全ソースを再ビルドすると解消します。

Prototype table cells must have reuse identifiers


Prototype table cells must have reuse identifiers
Couldn't comile connection: <IBCocoaTouchOutletConnection ....


UITableViewControllerを追加するとデフォルトでひとつプロトタイプセルが追加されていますが、これにidentifierが設定されていないため、そのままビルドすると警告が出ます。

プロトタイプセルのidentifierを設定するか、UITableViewCellクラスを使用する場合はプロトタイプセルは不要なので個数を0にすれば警告が出なくなります。

2012年4月1日日曜日

DOT.NETのデリゲートとの比較


DOT.NET C#にもDelegateがありますが、Objective-Cのものとはかなり違っています。
C#のものはコールバック関数のようなもので、Objective-Cではblockを使った処理に似ています。
ここではCocoaのでデリゲートのメソッド呼び出しの手順が似ているものとして、NSURLConnectionとDOT.NETのBackgroundWorkerの動作を比較してみます。

DOT.NET(C#) Cocoa
BackgroundWorkerオブジェクトをMyFormに追加する。
インスタンス変数:BackgroundWorker bgWorker;
bgWorker = new BackgroundWorker();
NSURLConnectionオブジェクトを作り、HTTPレスポンスを受け取るオブジェクト(この例ではself)をdelegateにセットする。
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:req delegate:self];
処理開始
MyForm.csにバックグランドで実行するメソッドbgWorker_DoWorkを実装し、bgWorker.RunWorkerAsyncを呼ぶとこのメソッドが呼ばれ、バックグランド処理が開始される。
処理開始
initWithRequest:delegate:メッセージを送るとHTTPリクエスト処理が開始される。
終了処理
MyFormの次のメソッドが呼ばれる。(キャンセル、エラー発生時はパラメータのRunWorkerCompletedEventArgsにフラグがセットされる。)
bgWorker_RunWorkerCompleted()
終了処理
NSURLConnectionの次のdelegateメソッドが呼ばれる。
onnection:connectionDidFinishLoading:
キャンセル処理
メインスレッドからbgWorker.CancelAsyncを呼ぶ。
キャンセル処理
メインスレッドからurlConnectionのcancelメソッドを呼ぶ。
処理中断
バックグランド処理でDoWorkで渡されるパラメータDoWorkEventArgsのCancelプロパティーをtrueにし、returnする。 メインスレッドのbgWorker_RunWorkerCompletedが呼ばれる。
処理中断
受信処理中にエラーが発生すると次のメソッドが呼ばれる。
-(void)connection:(NSURLConnection*)connection didFailWithError:(NSError *)error {

どちらも別スレッドで処理を実行し、処理経過に応じて状態変化をメインスレッドに通知し、メインスレッドでそれに応じた処理を行っています。DOT.NETでは変数名にdelegateは使われていませんが、どちらも同じデリゲートの仕組みです。

一方、次のような相違点があります。
  • DOT.NETではBackgrounWorkerオブジェクトはFormオブジェクトの変数にセットされ、そのFormオブジェクトがデリゲートとなる。
  • CocoaのNSURLConnectionはどのオブジェクトが作るか、どのオブジェクトをデリゲートとするかは前提としていない。
  • DOT.NETではBackgroundWorkerオブジェクトの変数名に応じた名前のメソッドを作り、それを呼び出す。
  • Cocoaでは既定のデリゲートメソッドが呼ばれる。
この相違点は、内部的には更に次のような違いがあります。(実際のソースコードは分からないので、推定です。)
  • DOT.NETではデザイナで設定されたプロパティー名からメソッド名を作り、その名前のメソッドをソースコードに追加し、それを呼び出す。ソースコードに直接追加するため、実行時にメソッドを探すことはしない。
  • Cocoaではdelegateがメソッドに応えるか(respondsToSelector:)か、ないしはプロトコルを実装しているか(conformsToProtpcol:)をチェックし、応える場合はそれを呼ぶ。(プロトコルの場合でも@requiredでない場合はrespondsToSelector:によるチェックが必要。)
この違いは些細なように見えますが、メソッドサーチの根本的な違いに由来するもので、Cocoaでデリゲートが多用されるのに対し、DOT.NETでは同様のコンセプトはありながらもさほど使われないことに関わっています。これはJavaについてもあてはまります。
  • DOT.NETのprotocol、JavaのinterfaceはObjective-Cのprotocolと同等のものですが、それをクラス宣言に加えた場合はすべてのメソッドを実装する必要があります。一部を欠いた場合はコンパイル時にエラーとなります。これはDOT.NETもJavaもコンパイル時に呼び出すメソッドを決定するのが基本だからです。refrectionでメソッドを探すのは特別な場合に限られ、それを多用するとパフォーマンスにも影響します。その場合もシグネチャー(メソッド名+パラメータ型)が一致するメソッドを探し、それを実行するという手順になります。
  • Objective-Cではプロトコルで宣言されたメソッドでも@requiredでなければ実装する必要はありません。Objective-Cでは常にruntimeシステムが実際に実行するメソッドを探します(ダイナミックバインディング)。そのため、コンパイル時のチェックは緩やかです。オブジェクトをid型で宣言すれば、メソッドが宣言されている.hをimportすればどのようなメソッドでもコンパイルは通ります。実行時にそのメソッドを実行できないとエラーとなりますが、respondsToSelector:でチェックすることで避けることができます。delegateはid型のことが多く、respondsToSelector:によるチェックはほとんどのデリゲートメソッドで行われています。また@requiredの場合もコンパイラのチェックに頼らず、conformsToProtpcol:によるチェックが行われているはずです。
デリゲートはオブジェクト指向のデザインパターンとしては言語に限らず一般的なものですが、その使われ方は言語に大きく依存します。

デリゲートを柔軟に使用できるかどうかで、ライブラリの性格も変わってきます。DOT.NETやJavaが機能提供傾向にあり、Cocoaはフレームワーク傾向にあると言えるでしょう。

DOT.NETのDelegateクラスはメソッド参照を持つクラスで、それにシグネチャーが同じメソッドを割り当てるとどのメソッドも同じように扱えるようになる、といったものです。デリゲートメソッドを実行するオブジェクトに実行したいメソッドを割り当てるとそのメソッドが呼ばれる、というもので、コールバックの動作になります。
正規表現のRegex.ReplaceがDelegateを使っています。

public string Replace(string input, MatchEvaluator evaluator)
public delegate string MatchEvaluator(Match match)