Control Fun(5)~Sample Code
2010. 08. 17
先に述べていましたが、手元にある旧版の『はじめてのiPhoneプログラミング』の内容と、新版『はじめてのiPhone3プログラミング』の内容と思しきApress社のサイト内のサンプルコードではいくつかの相違点がありますので、その辺の解説をしてみたいと思います。
●外観上のインターフェイスの違い
旧版のコードを説明通りに作ると、『Control Fun(1)~Image ViewとText Field』から『Control Fun(4)~Action SheetとAlert』で説明してきたものになります。

一方、新版の内容と思われるApress社のサイト内のサンプルコードは下図のようになっています。

中央付近のセグメンテッドコントロールが『Show/Hidden』から『Switches/Button』に変更され、Switchesの場合は2つの連動するスイッチが、Buttonの場合はDo Somethingボタンが表示されるように変更され、起動時にはDo Somethingボタンが表示されないようになっています。

見た目上は、表示の切り替え方式が変更されただけで、スイッチやDo Somethingボタンの機能に違いはありません。
●nibファイルの違い
外見上はスイッチが乗っていたswitchViewをDo Somethingボタン用にもう一枚作って切り替えているように見えるのですが、Control_FunViewController.xibを見るとそうではないことが分かります。

旧版では親ビュー下にスイッチを乗せるサブビューがあり、その下に二組のラベルとスイッチが置かれています。

しかし新版ではサブビューが無く、スイッチとDo Somethingボタンが直接置かれています。
更にキーボードを片付けるための隠しボタンが無く、親ビューがUIViewオブジェクトからUIControlオブジェクトに変更されています。
・隠しボタンの置換
UIViewとUIControlは以下のような関係になっています。
NSObject
|
∟UIResponder
|
∟UIView
|
∟UIImageView
|
∟UIControl
∟UIButton
∟UISegmentedControl
∟UISlider
UIViewはUIControlのスーパークラスなのでUIViewの機能を継承しており、且つUIControlはUIButtonのスーパークラスなので、UIViewとUIControlを置換できるというわけです。
何故置換したかというとUIViewにはアクションを設定できないからで、DocumentウィンドウでUIViewを選択してInspectorウィンドウのConnectionsタブを開くと分かります。

UIViewのConnectionsタブにはアウトレットしか表示されません。

しかしUIControlのConnectionsタブにはアウトレットの他にEventsの項目があり、アクションを設定できるようになっています。
そして隠しボタンで呼び出していたメソッド『backgroundClick:』は『backgroundTap:』と名前を変え、隠しボタン同様『Touch Up Inside』で設定されています。
(backgroundClick:とbackgroundTap:メソッドの内容は同一です)
このようにするには、まずDocumentウィンドウで隠しボタン(親Viewのすぐ下にあるCustim Button)をEditメニューのDeleteで削除します。
(このボタンを削除しないと親Viewより前面にあるために入力を受け付けてしまい、親Viewが応答できない)
次にDocumentウィンドウで親Viewを選択し、InspectorウィンドウのIdentityタブで『Class Identity』の『Class』を『UIControl』に変更します。

そしてDocumentウィンドウでFirst Responderを選択し、InspectorウィンドウのConnectionsタブでbackgroundClick:をControl(旧親View)に接続します。

サンプルコードではFirst Responderと接続していますが、File's Ownerから接続しても動作します。
(サンプルコードが何故First Responderから接続しているのか、File's Ownerから接続するのとどう違うのかは分かりません)
・セグメンテッドコントロールの修正
nibファイル上でのセグメンテッドコントロールの違いは少なく、Attributesタブで言えばTitleが『Show/Hide』から『Switches/Button』になっているくらいです。
そしてConnectionsタブを見ると、toggleShowHide:メソッドからtoggleControls:メソッドに変わっています。
両メソッドは中身が異なりますので後述しますが、ここではそのままにしておきます。
(メソッド名を変えると接続し直さなければならないので)
・スイッチの変更
サンプルコードでは、スイッチのラベルが無く、サブビューも使用していません。
最小限の変更で動作確認を済ませたいので、まずDocumentウィンドウでスイッチ二つをドラッグ&ドロップでサブビューから外します。

そしてサブビューを選択し、InspectorウィンドウのAttributesタブで、『View』の『Drawing』で『Hidden』にチェックを入れます。

この状態ですと、サブビューの後面にスイッチがある状態ですが、サブビューをHiddenにすることで無効にできますので、(後で元に戻す手間などを考えると)動作確認する分にはこれで構わないと思います。
また、起動時にDo Somethingボタンを非表示にしたいので、InspectorウィンドウのAttributesタブで、『View』の『Drawing』で『Hidden』にチェックを入れます。

Do Somethingボタンはスイッチと同じような位置にありますが、動作確認するだけですのでそのままの位置に置いておきます。
●ヘッダファイルの違い
サブビューを使用しないため、switchViewアウトレットとプロパティが不要になります。
キーボードを片付けるメソッド名がbackgroundClick:からbackgroundTap:になっていますが、前述の通り中身は一緒です。
(リファレンス類を読んでると、『タップする』ところが『クリックする』になっていることがよくありますし)
アクションシートを表示するメソッド名がdoSomething:からbuttonPressed:になっていますが、これも中身は一緒です。
セグメンテッドコントロールのメソッド名がtoggleShowHide:がtoggleControls:になっていて中身が違うのですが、再接続の手間を考えてメソッド名はそのままにし、中身だけ書き換えます。
それとセグメンテッドコントロールの判定に使う定数名も、kShowSegmentIndexからkSwitchesSegmentIndexに変わっていますが、使用方法は変わらないのでそのままにします。
また、通常インスタンス変数の宣言で『IBOutlet ~』と記述することが多いのですが、新版サンプルコードではプロパティの宣言で『IBOutlet ~』と記述しています。
新版ではこの辺の説明がされているのかもしれませんが、私の手元に無いので違いが分かりません。
●ソースファイルの違い
・セグメンテッドコントロールのメソッド
まずセグメンテッドコントロールのメソッドから見てみます。
旧版のtoggleShowHide:は、スイッチの乗っているビューの表示/非表示を切り替えていましたが、新版のサンプルコードのtoggleControls:ではスイッチおよびDo Somethingボタンをhiddenプロパティで直接表示/非表示の切り替えを行っています。
また細かいところでは、引数senderをUISegmentedControlクラスにキャストして、selectedSegmentIndexプロパティでNSIntegerの変数segmentに代入していた部分が、引数senderからselectedSegmentIndexプロパティで値を得てそのまま比較に使用するよう、簡略化が図られています。

hiddenプロパティはUIViewクラスのものです。
(『スライドショーのフルスクリーン化(2)』を参照してください)


・viewDidUnloadとdealloc
旧版の本書内では説明されていませんが、新版のサンプルコードではアウトレットに対してviewDidUnloadでの所有権の放棄とdeallocでの解放を行っています。
(太字が追加した部分)

この辺は説明が無くても定型文として書くものと考えた方が良さそうです。
(viewDidUnloadメソッドについては『Button Fun』を参照してください)
・dealloc
(NSObjectクラス)
- (void)dealloc
レシーバにより占有されているメモリを解放します。
(解放されたメモリがまだ再使用されていない場合)解放されたオブジェクトにメッセージが送信されると、後のメッセージはエラーと示されて生成されレシーバに送られます。
deallocメッセージは決して直接送信しないでください。
代わりにNSObjectプロトコルのreleaseメソッドを通して、オブジェクトのdeallocメソッドを間接的に呼び出すことができます。
(releaseメッセージを送信した結果、レシーバのretainカウントが0になれば解放します)
これらのメソッドの詳細については『高度なメモリ管理プログラミングガイド』を参照してください。
解放されたオブジェクトが所有していたデータまたはインスタンス変数に、動的にストレージを割り当てるなど、オブジェクトによる任意に追加されたメモリ消費を解放するためには、独自バージョンのdeallocをサブクラスで実装する必要があります。
クラス固有の解放を行った後、サブクラスメソッドはsuper:にスーパークラス用のdeallocメッセージを渡すように組み込む必要があります。
重要:工程のメモリが終了時に自動的にクリアされている場合、アプリケーション終了時にdeallocメッセージが送信できないことがあります。
OSがリソースのクリーンアップを全てのメモリ管理メソッドを呼び出すのに比べて、その方がより効率的です。
その他の理由も含め、deallocで不足がちなリソースの管理をしないでください。
詳細は『高度なメモリ管理プログラミングガイド』を参照してください。
特別な考慮事項
ガベージコレクションが有効な場合、ガベージコレクションはdeallocの代わりにfinalizeをレシーバに送信します。
ガベージコレクションが有効な場合、このメソッドは何もしません。
(編注:現在、iOSはガベージコレクションに対応していません)
参考文献
・NSObject Class Reference
●外観上のインターフェイスの違い
旧版のコードを説明通りに作ると、『Control Fun(1)~Image ViewとText Field』から『Control Fun(4)~Action SheetとAlert』で説明してきたものになります。

一方、新版の内容と思われるApress社のサイト内のサンプルコードは下図のようになっています。

中央付近のセグメンテッドコントロールが『Show/Hidden』から『Switches/Button』に変更され、Switchesの場合は2つの連動するスイッチが、Buttonの場合はDo Somethingボタンが表示されるように変更され、起動時にはDo Somethingボタンが表示されないようになっています。

見た目上は、表示の切り替え方式が変更されただけで、スイッチやDo Somethingボタンの機能に違いはありません。
●nibファイルの違い
外見上はスイッチが乗っていたswitchViewをDo Somethingボタン用にもう一枚作って切り替えているように見えるのですが、Control_FunViewController.xibを見るとそうではないことが分かります。

旧版では親ビュー下にスイッチを乗せるサブビューがあり、その下に二組のラベルとスイッチが置かれています。

しかし新版ではサブビューが無く、スイッチとDo Somethingボタンが直接置かれています。
更にキーボードを片付けるための隠しボタンが無く、親ビューがUIViewオブジェクトからUIControlオブジェクトに変更されています。
・隠しボタンの置換
UIViewとUIControlは以下のような関係になっています。
NSObject
|
∟UIResponder
|
∟UIView
|
∟UIImageView
|
∟UIControl
∟UIButton
∟UISegmentedControl
∟UISlider
UIViewはUIControlのスーパークラスなのでUIViewの機能を継承しており、且つUIControlはUIButtonのスーパークラスなので、UIViewとUIControlを置換できるというわけです。
何故置換したかというとUIViewにはアクションを設定できないからで、DocumentウィンドウでUIViewを選択してInspectorウィンドウのConnectionsタブを開くと分かります。

UIViewのConnectionsタブにはアウトレットしか表示されません。

しかしUIControlのConnectionsタブにはアウトレットの他にEventsの項目があり、アクションを設定できるようになっています。
そして隠しボタンで呼び出していたメソッド『backgroundClick:』は『backgroundTap:』と名前を変え、隠しボタン同様『Touch Up Inside』で設定されています。
(backgroundClick:とbackgroundTap:メソッドの内容は同一です)
このようにするには、まずDocumentウィンドウで隠しボタン(親Viewのすぐ下にあるCustim Button)をEditメニューのDeleteで削除します。
(このボタンを削除しないと親Viewより前面にあるために入力を受け付けてしまい、親Viewが応答できない)
次にDocumentウィンドウで親Viewを選択し、InspectorウィンドウのIdentityタブで『Class Identity』の『Class』を『UIControl』に変更します。

そしてDocumentウィンドウでFirst Responderを選択し、InspectorウィンドウのConnectionsタブでbackgroundClick:をControl(旧親View)に接続します。

サンプルコードではFirst Responderと接続していますが、File's Ownerから接続しても動作します。
(サンプルコードが何故First Responderから接続しているのか、File's Ownerから接続するのとどう違うのかは分かりません)
・セグメンテッドコントロールの修正
nibファイル上でのセグメンテッドコントロールの違いは少なく、Attributesタブで言えばTitleが『Show/Hide』から『Switches/Button』になっているくらいです。
そしてConnectionsタブを見ると、toggleShowHide:メソッドからtoggleControls:メソッドに変わっています。
両メソッドは中身が異なりますので後述しますが、ここではそのままにしておきます。
(メソッド名を変えると接続し直さなければならないので)
・スイッチの変更
サンプルコードでは、スイッチのラベルが無く、サブビューも使用していません。
最小限の変更で動作確認を済ませたいので、まずDocumentウィンドウでスイッチ二つをドラッグ&ドロップでサブビューから外します。

そしてサブビューを選択し、InspectorウィンドウのAttributesタブで、『View』の『Drawing』で『Hidden』にチェックを入れます。

この状態ですと、サブビューの後面にスイッチがある状態ですが、サブビューをHiddenにすることで無効にできますので、(後で元に戻す手間などを考えると)動作確認する分にはこれで構わないと思います。
また、起動時にDo Somethingボタンを非表示にしたいので、InspectorウィンドウのAttributesタブで、『View』の『Drawing』で『Hidden』にチェックを入れます。

Do Somethingボタンはスイッチと同じような位置にありますが、動作確認するだけですのでそのままの位置に置いておきます。
●ヘッダファイルの違い
サブビューを使用しないため、switchViewアウトレットとプロパティが不要になります。
キーボードを片付けるメソッド名がbackgroundClick:からbackgroundTap:になっていますが、前述の通り中身は一緒です。
(リファレンス類を読んでると、『タップする』ところが『クリックする』になっていることがよくありますし)
アクションシートを表示するメソッド名がdoSomething:からbuttonPressed:になっていますが、これも中身は一緒です。
セグメンテッドコントロールのメソッド名がtoggleShowHide:がtoggleControls:になっていて中身が違うのですが、再接続の手間を考えてメソッド名はそのままにし、中身だけ書き換えます。
それとセグメンテッドコントロールの判定に使う定数名も、kShowSegmentIndexからkSwitchesSegmentIndexに変わっていますが、使用方法は変わらないのでそのままにします。
また、通常インスタンス変数の宣言で『IBOutlet ~』と記述することが多いのですが、新版サンプルコードではプロパティの宣言で『IBOutlet ~』と記述しています。
新版ではこの辺の説明がされているのかもしれませんが、私の手元に無いので違いが分かりません。
●ソースファイルの違い
・セグメンテッドコントロールのメソッド
まずセグメンテッドコントロールのメソッドから見てみます。
旧版のtoggleShowHide:は、スイッチの乗っているビューの表示/非表示を切り替えていましたが、新版のサンプルコードのtoggleControls:ではスイッチおよびDo Somethingボタンをhiddenプロパティで直接表示/非表示の切り替えを行っています。
また細かいところでは、引数senderをUISegmentedControlクラスにキャストして、selectedSegmentIndexプロパティでNSIntegerの変数segmentに代入していた部分が、引数senderからselectedSegmentIndexプロパティで値を得てそのまま比較に使用するよう、簡略化が図られています。
- (IBAction)toggleShowHide:(id)sender {
/*
UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
NSInteger segment = segmentedControl.selectedSegmentIndex;
if (segment == kShowSegmentIndex) [switchView setHidden:NO];
else [switchView setHidden:YES];
*/
if ([sender selectedSegmentIndex] == kShowSegmentIndex) {
leftSwitch.hidden = NO;
rightSwitch.hidden = NO;
doSomethingButton.hidden = YES;
}
else {
leftSwitch.hidden = YES;
rightSwitch.hidden = YES;
doSomethingButton.hidden = NO;
}
}
/*
UISegmentedControl *segmentedControl = (UISegmentedControl *)sender;
NSInteger segment = segmentedControl.selectedSegmentIndex;
if (segment == kShowSegmentIndex) [switchView setHidden:NO];
else [switchView setHidden:YES];
*/
if ([sender selectedSegmentIndex] == kShowSegmentIndex) {
leftSwitch.hidden = NO;
rightSwitch.hidden = NO;
doSomethingButton.hidden = YES;
}
else {
leftSwitch.hidden = YES;
rightSwitch.hidden = YES;
doSomethingButton.hidden = NO;
}
}

hiddenプロパティはUIViewクラスのものです。
(『スライドショーのフルスクリーン化(2)』を参照してください)


・viewDidUnloadとdealloc
旧版の本書内では説明されていませんが、新版のサンプルコードではアウトレットに対してviewDidUnloadでの所有権の放棄とdeallocでの解放を行っています。
(太字が追加した部分)
- (void)viewDidUnload {
self.nameField = nil;
self.numberField = nil;
self.sliderLabel = nil;
self.leftSwitch = nil;
self.rightSwitch = nil;
self.switchView = nil;
self.doSomethingButton = nil;
[super viewDidUnload];
}
- (void)dealloc {
[nameField release];
[numberField release];
[sliderLabel release];
[leftSwitch release];
[rightSwitch release];
[switchView release];
[doSomethingButton release];
[super dealloc];
}
self.nameField = nil;
self.numberField = nil;
self.sliderLabel = nil;
self.leftSwitch = nil;
self.rightSwitch = nil;
self.switchView = nil;
self.doSomethingButton = nil;
[super viewDidUnload];
}
- (void)dealloc {
[nameField release];
[numberField release];
[sliderLabel release];
[leftSwitch release];
[rightSwitch release];
[switchView release];
[doSomethingButton release];
[super dealloc];
}

この辺は説明が無くても定型文として書くものと考えた方が良さそうです。
(viewDidUnloadメソッドについては『Button Fun』を参照してください)
・dealloc
(NSObjectクラス)
- (void)dealloc
レシーバにより占有されているメモリを解放します。
(解放されたメモリがまだ再使用されていない場合)解放されたオブジェクトにメッセージが送信されると、後のメッセージはエラーと示されて生成されレシーバに送られます。
deallocメッセージは決して直接送信しないでください。
代わりにNSObjectプロトコルのreleaseメソッドを通して、オブジェクトのdeallocメソッドを間接的に呼び出すことができます。
(releaseメッセージを送信した結果、レシーバのretainカウントが0になれば解放します)
これらのメソッドの詳細については『高度なメモリ管理プログラミングガイド』を参照してください。
解放されたオブジェクトが所有していたデータまたはインスタンス変数に、動的にストレージを割り当てるなど、オブジェクトによる任意に追加されたメモリ消費を解放するためには、独自バージョンのdeallocをサブクラスで実装する必要があります。
クラス固有の解放を行った後、サブクラスメソッドはsuper:にスーパークラス用のdeallocメッセージを渡すように組み込む必要があります。
- (void)dealloc {
[companion release];
NSZoneFree(private, [self zone])
[super dealloc];
}
[companion release];
NSZoneFree(private, [self zone])
[super dealloc];
}
重要:工程のメモリが終了時に自動的にクリアされている場合、アプリケーション終了時にdeallocメッセージが送信できないことがあります。
OSがリソースのクリーンアップを全てのメモリ管理メソッドを呼び出すのに比べて、その方がより効率的です。
その他の理由も含め、deallocで不足がちなリソースの管理をしないでください。
詳細は『高度なメモリ管理プログラミングガイド』を参照してください。
特別な考慮事項
ガベージコレクションが有効な場合、ガベージコレクションはdeallocの代わりにfinalizeをレシーバに送信します。
ガベージコレクションが有効な場合、このメソッドは何もしません。
(編注:現在、iOSはガベージコレクションに対応していません)
参考文献
・NSObject Class Reference
![]() | はじめてのiPhone3プログラミング (2009/12/17) Dave Mark、Jeff LaMarche 他 商品詳細を見る |