Sections(2)~カテゴリ

2010. 11. 18
前回の『Sections(1)~セクションとインデックス』で作ったテーブルはデータ量が多く、インデックスバーだけでは絞り込みが足りないので、検索バーを追加します。

検索バーに文字列を入力すると、その文字列を含む要素が随時絞り込まれてテーブルが更新されていき、検索をキャンセルすると元のテーブルに戻ります。

したがって、元になる不変の辞書とは別に、入力された文字列に応じて適合する要素だけを集めた可変の辞書が必要になります。


●ディープミュータブルコピー

本書の『ディープミュータブルコピー』の項の説明を少し補足します。

NSDictionaryクラスのリファレンスを見ると、概要に『辞書クラスはNSCopyingNSMutableCopyingプロトコルを採用しており、他の辞書の型を容易に変換することができます。』とあります。

したがってNSMutableCopyingプロトコルのmutableCopyWithZone:メソッド(もしくはゾーン指定をしないNSObjectmutableCopyメソッド)を使って、NSDictionaryオブジェクトを可変のNSMutableDictionaryオブジェクトに変換することができます。

しかし『コレクションのコピー』の説明のように、mutableCopyWithZone:メソッドは浅いコピーとなるため、実質的には『可変型のコピーを生成』するのではなく『可変型への変換を行う』ことになり、今回のように元の辞書を不変で別途に保持する必要がある場合には使えません。

『コレクションのコピー』では深いコピーや可変コピーの方法がいくつかあげられていますが、以下の理由により今回のケースでは使えません。

1)initWithArray:copyItems:の第2引数をYESにして使う
辞書の場合はinitWithDictionary:copyItems:になりますが中身は同じですので、返されるオブジェクトが不変になる。

2)コレクションをアーカイブしてアンアーカイブする
全てのコンテンツをNSCodingプロトコルに準拠させる必要があるのと、検索バーに一文字入力する度に辞書をアーカイブ&アンアーカイブするとなると、オーバーヘッドが大変そうです。
全ての階層をコピーできますが、元の特性そのままのコピーになる。

結論として既存のメソッドを使うのではなく、カテゴリで拡張したメソッドを追加するということになります。


●カテゴリ

詳解 Objective-C 2.0』によると、元々は肥大化したクラスを似たような特性毎に複数のモジュールに分割して定義するための仕組みとあります。

その仕組みの中に既存のクラス(システムか自作かを問わず)にメソッドを追加できる機能があります。

インスタンス変数の追加はできないものの、『Dynamic Objective-C』によればサブクラス化との違いとして、

・メソッド呼び出しのオーバーヘッドが少ない
・既存のインスタンスに対しても拡張できる

というメリットがあげられています。

カテゴリの宣言と定義はクラスとほぼ同様で、インスタンス変数の宣言が無いのが特徴です。

・宣言:@interface クラス名 (カテゴリ名)
・定義:@implementation クラス名 (カテゴリ名)

カテゴリを利用する際には、以下の3つの参照(インポート)が必要になります。

・カテゴリのヘッダファイルで元になるクラスを参照する
・カテゴリのソースファイルでカテゴリのヘッダファイルを参照する
・カテゴリのメソッドを使う場合、呼び出すソースファイルはカテゴリのヘッダファイルを参照する

なお『詳細Objective-C 2.0』では、ファイル名は『クラス名 + カテゴリ名』にするのが一般的とありますが、ここでは本書内容に従って『クラス名 - カテゴリ名』のままで作成します。


●カテゴリファイルの追加

今回は『Sections(1)~セクションとインデックス』のプロジェクトに元に改造を行っていきます。

まずカテゴリファイルを追加していきます。

Xcodeの『Classes』を右クリックして『追加』→『新規ファイル...』を選択します。

934

本書で解説されているように、カテゴリ用のテンプレートはありませんが、クラスとほぼ同じなので『iOS』の『Cocoa Touch Class』にある『Objective-C class』(Subclass ofはNSObject)テンプレートを使用し、ファイル名は『NSDictionary-MutableDeepCopy』とします。
Apress社のサイト内のサンプルコードでは『NSDictionary-DeepMutableCopy』となっていますので注意してください)


●カテゴリヘッダファイルの編集

ファイル名にあるように、NSDictionaryクラスの追加メソッドとして、NSDictionaryオブジェクトからNSMutableDictionaryオブジェクトを新規コピーするmutableDeepCopyを作成します。

935

カテゴリのヘッダファイルでは、元となるクラスのインポートと『@interface クラス名 (カテゴリ名)』での宣言を行います。

『Objective-C class』テンプレートで作られているものの内、#import文はNSDictionaryのフレームワークである『Foundation.h』が既に記述されているので変更する必要はありませんので、カテゴリと追加メソッドの宣言を記述するだけとなります。
(太字が追加・修正した部分)

#import <Foundation/Foundation.h>

@interface NSDictionary (MutableDeepCopy)

- (NSMutableDictionary *)mutableDeepCopy;

@end

937

@interfaceでNSDictionaryクラスにMutableDeepCopyカテゴリを追加します。

追加メソッドとして、可変辞書NSMutableDictionaryを返すmutableDeepCopyメソッドの宣言を行っています。


●カテゴリソースファイルの編集

カテゴリのソースファイルでは、カテゴリヘッダファイルのインポートと、カテゴリと追加メソッドの定義を行います。

936

『Objective-C class』テンプレートでヘッダファイルのインポートは済んでいますので、カテゴリと追加メソッドの定義のみ記述するだけとなります。
(太字が追加した部分)

#import "NSDictionary-MutableDeepCopy.h"

@implementation NSDictionary(MutableDeepCopy)

- (NSMutableDictionary *)mutableDeepCopy {
    NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithCapacity:[self count]];
    NSArray *keys = [self allKeys];

    for (id key in keys) {
        id oneValue = [self valueForKey:key];
        id oneCopy = nil;

        if ([oneValue respondsToSelector:@selector(mutableDeepCopy)])
            oneCopy = [oneValue mutableDeepCopy];
        else if ([oneValue respondsToSelector:@selector(mutableCopy)])
            oneCopy = [oneValue mutableCopy];

        if (oneCopy == nil)
            oneCopy = [oneValue copy];

        [ret setValue:oneCopy forKey:key];
    }
    return ret;
}


@end

938

カテゴリの定義は『@implementation クラス名 (カテゴリ名)』となっています。

mutableDeepCopyメソッドは、レシーバのNSDictionaryオブジェクトを可変のNSMutableDictionaryオブジェクトにして返すメソッドです。

最初にdictionaryWithCapacity:メソッドで新規に可変辞書retを生成します。

初期容量として指定する引数numItemsには、レシーバの辞書内のエントリ数をcountメソッドで返しています。

次にallKeysメソッドで、レシーバの辞書内のキーを新規配列keysに取り込みます。

続いて高速列挙for...in文で元になるNSDictionaryオブジェクトからエントリ(要素)を取得します。

高速列挙では、取得するオブジェクトがどんな型でも大丈夫なようにid型の変数を指定します。

先程取り込んだ配列keysからキーを取得してkeyに入れ、{}内でkeyによる処理が終わったら次のキーをkeysから取り出して・・・という繰り返しになります。

取り出したkeyはvalueForKey:メソッドで値を取り出し、その値oneValueをrespondsToSelector:でコピーするメソッドの判別を行っています。

respondsToSelector:メソッドでは、引数で指定したメソッドが応答できるかどうかを判別しています。

つまりキーで取得した値oneValueが、

・mutableDeepCopyメソッドで処理可能な場合、mutableDeepCopyでoneCopyにコピー

mutableCopyメソッドで処理可能な場合、mutableCopyでoneCopyに(浅い)コピー

をすることになり、NSMutableCopyingプロトコルに準拠しているオブジェクトのコピーをしていることになります。

両メソッドでコピーできない(NSMutableCopyingプロトコルに準拠していないオブジェクト)場合、oneCopyの値がnilなので、次のif文に入りcopyメソッドでoneCopyにコピーされます。

これはNSCopyingプロトコルに準拠している場合となり、NSMutableCopyingとNSCopying両プロトコルに準拠していないオブジェクトは、oneCopyがnilのままとなるはずです。

そしてoneCopyの値とkeyのキーを使ってsetValue:forKey:メソッドで新規の可変辞書retに追加します。

高速列挙で全てのキーに対するコピーが終了したら、最後に可変辞書retを返します。


dictionaryWithCapacity:

+ (id)dictionaryWithCapacity:(NSUInteger)numItems

numItemsで指定したエントリ数を保持するのに十分なメモリを割り当て、可変辞書を生成して返します。

可変辞書は必要に応じてメモリを追加して割り当てるので、numItemsは単純にオブジェクトの初期容量を確立するためのものです。

numItems:新規辞書の初期容量を指定します。


count

- (NSUInteger)count

辞書内のエントリ数を返します。


valueForKey:

- (id)valueForKey:(NSString *)key

指定したキーに関連付けされた値を返します。

keyが『@』で始まっていない場合はobjectForKey:を呼び出します。

keyが『@』で始まっている場合は、『@』を取り除いた残りの部分で [super valueForKey:] を呼び出します。

key:対応する値を返すためのキーを指定します。
キー値コーディングを使用する場合、キーは文字列である必要があります。
(『Key-Value Coding Programming Guide』を参照してください)


respondsToSelector:

- (BOOL)respondsToSelector:(SEL)aSelector

指定されたメッセージに応答することができる継承されたメソッド、またはレシーバが実装されているかを示すブール値を返します。

aSelectorに応答可能な継承されたメソッド、またはレシーバが実装されている場合はYESを、それ以外の場合はNOを返します。
(必須)

アプリケーションには、NOを返された時にエラーかどうかを検討して判断する責任があります。

オブジェクトがsuperキーワードを使ってrespondsToSelector:を送信することにより、メソッドがスーパークラスから継承しているかをテストすることはできません。

このメソッドはオブジェクト全体としてテストするだけで、実際のスーパークラスの実装まではテストしません。

したがって、superにrespondsToSelector:を送信することは、selfに送信するのと同じです。

代わりにオブジェクトのスーパークラスで直接NSObjectクラスメソッドのinstancesRespondToSelector:を呼び出す必要がある場合は、以下に示すのコードで表します。

if( [MySuperclass instancesRespondToSelector:@selector(aMethod)] ) {
    // invoke the inherited method
    [super aMethod];
}

単純に [[self superclass] instancesRespondToSelector:@selector(aMethod)] とすると、サブクラスによって呼び出された場合に失敗する可能性があるため、使用することはできません。

間接的ではありますが、レシーバは他のオブジェクトにaSelectorメッセージを転送し、メッセージの応答を得ることができますが、実際にはこのメソッドはNOを返します。

aSelector:メッセージを識別するセレクタを指定します。


mutableCopy

- (id)mutableCopy

ゾーンがnilのmutableCopyWithZone:で返されるオブジェクトを返します。

これはNSMutableCopyingプロトコルを採用するクラスの便利なメソッドです。

mutableCopyWithZone:が実装されていない場合は例外が発生します。

特別な考慮事項

(ガベージコレクションではない)メモリ管理を使用している場合、このメソッドはオブジェクトを返す前に保持します。

メソッドを呼び出した側は、返されたオブジェクトを解放する責任があります。


setValue:forKey:

- (void)setValue:(id)value forKey:(NSString *)key

辞書にキーと値のペアを追加します。

このメソッドはsetObject:forKey:を使って辞書にvalueとkeyを追加します。

valueがnilの場合、removeObjectForKey:を使ってkeyの削除を試みます。

value:keyの値を指定します。

key:valueのキーを指定します。
キー値コーディングを使用する場合、キーは文字列である必要があります。
(『Key-Value Coding Programming Guide』を参照してください)


initWithCapacity:

- (id)initWithCapacity:(NSUInteger)numItems

numItemsで指定したエントリ数を保持するのに十分なメモリを割り当て、可変辞書を生成して返します。

可変辞書は必要に応じてメモリを追加して割り当てるので、numItemsは単純にオブジェクトの初期容量を確立するためのものです。

numItems:新規辞書の初期容量を指定します。


●可変辞書retの初期化について

Apress社のサイト内のサンプルコードでは、可変辞書retの初期化に(autoreleaseで自動解放される)コンビニエンスメソッドのdictionaryWithCapacity:ではなく、(手動解放する必要がある)通常のイニシャライザのinitWithCapacity:が使われています。

コード中に解放する記述が無いので『Googleブックス/Beginning IPhone 3 Development: Exploring the IPhone SDK』を読んでみると、

我々の(mutableDeepCopy)メソッドは名前に『copy』が入っているので、copyWithZone:と同じメモリの規則に従うため、retainカウントが1のオブジェクトを返すことになっています。
(原文:Because our method has "copy" in its name, it follows the same memory rules as the copyWithZone: method, which are supposed to return an object with a retain count of 1.)

と、説明されていました。

公式リファレンスなどで裏を取ろうと思ったのですが調べきれず、『詳解 Objective-C 2.0』の『コーディングの指針』や関連項目にもそのような記述が見当たらないのでよく分かりません。

ちなみにXcodeの『実行』メニューの『パフォーマンスツールを使って実行』で『Leaks』を選択し、Instrumentsでメモリリークを確認してみると、旧版本書のコードでも新版のサンプルコードでも検索を試しているとリークが発生します。
(このことが原因かどうかは、まだ追いかけていません。未だにInstrumentsの使い方もわかりませんので・・・)



参考文献

NSMutableDictionary Class Reference

NSDictionary Class Reference

NSObject Protocol Reference

NSObject Class Reference

はじめてのiPhone3プログラミングはじめてのiPhone3プログラミング
(2009/12/17)
Dave Mark、Jeff LaMarche 他

商品詳細を見る

詳解 Objective-C 2.0 第3版詳解 Objective-C 2.0 第3版
(2011/12/28)
荻原 剛志

商品詳細を見る

Dynamic Objective-CDynamic Objective-C
(2009/03/27)
木下 誠

商品詳細を見る






Bose QuietComfort 20
0 Comments
Leave a comment
管理者にだけ表示を許可する
Top
0 Trackbacks
Top
Calendar
08 | 2017/09 | 10
Sun Mon Tue Wed Thu Fri Sat
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

Bose QuietComfort 20
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR