NSArray、NSMutableArrayなどでタイピングを強制する方法はありますか?


回答:


35

-addSomeClass:コンパイル時の静的型チェックを許可するメソッドを使用してカテゴリを作成することもできます(そのため、そのメソッドを介して別のクラスであることがわかっているオブジェクトを追加しようとすると、コンパイラが通知する可能性があります)が、それを強制する実際の方法はありません配列には、特定のクラスのオブジェクトのみが含まれます。

一般に、Objective-Cではこのような制約は必要ないようです。経験豊富なCocoaプログラマーがその機能を望んでいることを聞いたことがないと思います。他の言語のプログラマーで、その言語でまだ考えているように見える唯一の人々。配列内の特定のクラスのオブジェクトのみが必要な場合は、そのクラスのオブジェクトのみを配列に貼り付けます。コードが適切に動作していることをテストする場合は、テストしてください。


136
私は「経験豊富なCocoaプログラマー」には何が欠けているのかわからないと思います-Javaの経験から、型変数はコードの理解を改善し、より多くのリファクタリングを可能にすることがわかります。
tgdavies 2010年

11
まあ、JavaのGenericsサポートは、最初から入れていないので、それ自体が大きく壊れています...
dertoni

28
Gottaは@tgdaviesに同意します。C#で持っていたインテリセンス機能とリファクタリング機能がありません。動的なタイピングが必要な場合は、C#4.0で取得できます。私が強く型付けしたいものが欲しいとき、それも持つことができます。どちらにも時間と場所があることに気づきました。
Steve

18
@charkrit Objective-Cの何が「必要ない」のですか?C#を使用しているときに必要だと思いましたか?Objective-Cでは必要ないという人が多いと思いますが、これらの同じ人々は、どの言語でも必要がないと思うので、好みやスタイルの問題であり、必要ではありません。
12

17
これは、コンパイラが実際に問題を見つけるのを助けることを可能にすることではありません。確かに、「配列内の特定のクラスのオブジェクトのみが必要な場合は、そのクラスのオブジェクトのみをそこに固定する」と言うことができます。しかし、テストがそれを実施する唯一の方法である場合、不利になります。コードを書くことから遠ざかるほど、問題が見つかり、その問題のコストが高くなります。
GreenKiwi、2012年

145

まだ誰もここに置いてないので、やります!

これは現在、Objective-Cで正式にサポートされています。Xcode 7以降、次の構文を使用できます。

NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];

注意

これらはコンパイラの警告のみであり、技術的にはオブジェクトを配列に挿入できることに注意してください。すべての警告をエラーにしてビルドを妨げるスクリプトが利用可能です。


私はここで怠惰ですが、なぜこれはXCode 7でしか利用できないのですか?nonnullXCode 6でを使用できますが、私が覚えている限り、それらは同時に導入されました。また、そのような概念の使用法はXCodeバージョンまたはiOSバージョンに依存しますか?
Guven

@Guven-nullabilityは6で登場、正解ですが、ObjCジェネリックはXcode 7まで導入されませんでした
Logan

Xcodeのバージョンのみに依存していると確信しています。ジェネリックはコンパイラの警告のみであり、実行時には表示されません。あなたが好きなOsにコンパイルできると確信しています。
ローガン

2
@DeanKelly-あなたはこれを次のように行うことができます:@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; 少し不格好に見えますが、トリックを行います!
ローガン、

1
@Loganには、警告が検出された場合にビルドを妨げるスクリプトのセットだけではありません。Xcodeには、「構成」という完璧なメカニズムがあります。これを確認してください。boredzo.org
blog

53

これは、強く型付けされた言語(C ++やJavaなど)から、Python、Ruby、Objective-Cなどのより弱いまたは動的に型付けされた言語に移行する人々にとって比較的よくある質問です。Objective-Cでは、ほとんどのオブジェクトはNSObject(type id)から継承します(残りは、などの他のルートクラスから継承し、NSProxy typeにこともできますid)、任意のメッセージを任意のオブジェクトに送信できます。もちろん、認識できないインスタンスにメッセージを送信すると、ランタイムエラーが発生する可能性があります(コンパイラの警告も発生します適切な-Wフラグを使用)。インスタンスが送信したメッセージに応答する限り、どのクラスに属しているかは気にしないでください。これは、「アヒルのタイピング」と呼ばれます。これは、「アヒルのようにガクガクする場合(つまり、セレクターに応答する場合)、アヒルの場合です(つまり、メッセージを処理できるため、クラスを気にかけているためです」。

-(BOOL)respondsToSelector:(SEL)selectorメソッドを使用して、インスタンスが実行時にセレクターに応答するかどうかをテストできます。配列内のすべてのインスタンスでメソッドを呼び出したいが、すべてのインスタンスがメッセージを処理できるかどうかが不明であると想定します(したがって、単にNSArraysを使用することはできません-[NSArray makeObjectsPerformSelector:]。次のようなものが機能します:

for(id o in myArray) {
  if([o respondsToSelector:@selector(myMethod)]) {
    [o myMethod];
  }
}

呼び出すメソッドを実装するインスタンスのソースコードを制御する場合、より一般的な方法は、@protocolそれらのメソッドを含むを定義し、問題のクラスがそのプロトコルをその宣言で実装することを宣言することです。この使用法では、@protocolはJavaインターフェースまたはC ++抽象基本クラスに類似しています。次に、各メソッドへの応答ではなく、プロトコル全体への適合性をテストできます。前の例では、それほど大きな違いはありませんが、複数のメソッドを呼び出す場合は、物事が簡素化される可能性があります。例は次のようになります。

for(id o in myArray) {
  if([o conformsToProtocol:@protocol(MyProtocol)]) {
    [o myMethod];
  }
}

と仮定するとMyProtocol宣言しmyMethodます。この2番目のアプローチは、コードの目的を最初のアプローチよりも明確にするため、推奨されます。

多くの場合、これらのアプローチの1つにより、配列内のすべてのオブジェクトが特定の型であるかどうかを気にする必要がなくなります。それでも気にするなら、標準の動的言語アプローチはユニットテスト、ユニットテスト、ユニットテストです。この要件のリグレッションは(おそらく回復不可能な)ランタイム(コンパイル時ではない)エラーを生成するため、クラッシュを実際にリリースしないように、動作を確認するためのテストカバレッジが必要です。この場合、配列を変更する操作を実行してから、配列内のすべてのインスタンスが特定のクラスに属していることを確認します。適切なテストカバレッジがあれば、インスタンスIDを検証するための追加のランタイムオーバーヘッドも必要ありません。あなたは良いユニットテストのカバレッジを持っていますね?


35
単体テストは、まともな型システムの代わりにはなりません。
tba

8
型付けされた配列が提供するツールを必要とする人はそうです。@BarryWark(および彼が使用、読み取り、理解、およびサポートする必要があるコードベースに触れた人)は、100%のコードカバレッジを持っていると確信しています。ただしid、必要な場合を除いて、JavaコーダーがObjectsを通過する以上のrawは使用しないでください。何故なの?単体テストを持っている場合は必要ありませんか?型指定された配列と同様に、そこにあり、コードをより保守しやすくします。プラットフォームに投資して人々がポイントを認めたくないので、この省略が実際にメリットである理由を発明しているように思えます。
funkybro 2013

「ダックタイピング」?? それは陽気です!これまで聞いたことがない。
ジョンヘンケル2014

11

サブクラス化NSMutableArrayしてタイプセーフを適用できます。

NSMutableArrayクラスクラスタであるため、サブクラス化は簡単ではありません。最終的NSArrayに、そのクラス内の配列から呼び出しを継承して転送しました。結果はと呼ばれるクラスであるサブクラスに簡単。これが私が思いついたものです:ConcreteMutableArray

更新:クラスクラスターのサブクラス化に関するMike Ashからのこのブログ投稿を確認してください。

これらのファイルをプロジェクトに含め、マクロを使用して必要なタイプを生成します。

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

使用法:

NSStringArray* strings = [NSStringArray array];
[strings add:@"Hello"];
NSString* str = [strings get:0];

[strings add:[User new]];  //compiler error
User* user = [strings get:0];  //compiler error

他の考え

  • それはから継承します NSArray、シリアライゼーション/デシリアライゼーションをサポートします
  • 好みに応じて、次のようなジェネリックメソッドをオーバーライド/非表示にすることができます。

    - (void) addObject:(id)anObject


素敵ですが、今のところ、いくつかのメソッドをオーバーライドすることによる強いタイピングに欠けています。現在は弱いタイピングのみです。
2013

7

Objective-Cのコンパイル時(プリプロセッサ実装)のジェネリック実装であるhttps://github.com/tomersh/Objective-C-Genericsをご覧くださいこのブログ投稿には、すばらしい概要があります。基本的に、コンパイル時のチェック(警告またはエラー)が発生しますが、ジェネリックの実行時のペナルティはありません。


1
私はそれを試しました、非常に良い考えですが、悲しいことにバグがあり、追加された要素をチェックしません。
Binarian 2013

4

このGithubプロジェクトは、まさにその機能を実装しています。

その後<>、C#と同じように角かっこを使用できます。

彼らの例から:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed

0

可能な方法はNSArrayをサブクラス化することですが、Appleはそれを行わないことを推奨しています。型指定されたNSArrayの実際の必要性を2度考えるのは簡単です。


1
コンパイル時に静的な型チェックを行う時間を節約し、編集がさらに改善されます。libを長期間使用する場合に特に役立ちます。
pinxue

0

NSArrayのクラスクラスターの性質に関する問題を回避するために、NSArrayオブジェクトをバッキングivarとして使用するNSArrayサブクラスを作成しました。オブジェクトの追加を承認または拒否するには、ブロックが必要です。

NSStringオブジェクトのみを許可するには、AddBlockasを

^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
}

FailBlock要素をテストに失敗した場合に何をすべきかを決定するように定義することができます—フィルタリングのために正常に失敗するか、別の配列に追加するか、—これがデフォルトです—例外を発生させます。

VSBlockTestedObjectArray.h

#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element); 
typedef void(^FailBlock)(id element); 

@interface VSBlockTestedObjectArray : NSMutableArray

@property (nonatomic, copy, readonly) AddBlock testBlock;
@property (nonatomic, copy, readonly) FailBlock failBlock;

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;    
@end

VSBlockTestedObjectArray.m

#import "VSBlockTestedObjectArray.h"

@interface VSBlockTestedObjectArray ()
@property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
@end

@implementation VSBlockTestedObjectArray
@synthesize testBlock = _testBlock;
@synthesize failBlock = _failBlock;
@synthesize realArray = _realArray;


-(id)initWithCapacity:(NSUInteger)capacity
{
    if (self = [super init]) {
        _realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock 
             FailBlock:(FailBlock)failBlock 
              Capacity:(NSUInteger)capacity
{
    self = [self initWithCapacity:capacity];
    if (self) {
        _testBlock = [testBlock copy];
        _failBlock = [failBlock copy];
    }

    return self;
}

-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
    return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}

-(id)initWithTestBlock:(AddBlock)testBlock
{
    return [self initWithTestBlock:testBlock FailBlock:^(id element) {
        [NSException raise:@"NotSupportedElement" format:@"%@ faild the test and can't be add to this VSBlockTestedObjectArray", element];
    } Capacity:0];
}


- (void)dealloc {
    [_failBlock release];
    [_testBlock release];
    self.realArray = nil;
    [super dealloc];
}


- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if(self.testBlock(anObject))
        [self.realArray insertObject:anObject atIndex:index];
    else
        self.failBlock(anObject);
}

- (void) removeObjectAtIndex:(NSUInteger)index
{
    [self.realArray removeObjectAtIndex:index];
}

-(NSUInteger)count
{
    return [self.realArray count];
}

- (id) objectAtIndex:(NSUInteger)index
{
    return [self.realArray objectAtIndex:index];
}



-(void)errorWhileInitializing:(SEL)selector
{
    [NSException raise:@"NotSupportedInstantiation" format:@"not supported %@", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}

@end

次のように使用します。

VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSString", element);
}];


VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
    return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
    NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];


[stringArray addObject:@"test"];
[stringArray addObject:@"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:@"test2"];
[stringArray addObject:@"test3"];


[numberArray addObject:@"test"];
[numberArray addObject:@"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:@"test2"];
[numberArray addObject:@"test3"];


NSLog(@"%@", stringArray);
NSLog(@"%@", numberArray);

これは単なるコード例であり、実際のアプリケーションでは使用されていません。そのためには、おそらくNSArrayメソッドを実装する必要があります。


0

c ++とObjective-cを混在させる場合(つまり、mmファイルタイプを使用する場合)、ペアまたはタプルを使用して入力を強制できます。たとえば、次のメソッドでは、タイプstd :: pairのC ++オブジェクトを作成し、それをOCラッパータイプ(定義する必要があるstd :: pairのラッパー)のオブジェクトに変換して、いくつかに渡すことができます。他のOCメソッド。OCメソッドを使用するには、OCオブジェクトを変換してC ++オブジェクトに戻す必要があります。OCメソッドはOCラッパータイプのみを受け入れるため、型の安全性が保証されます。タプル、可変テンプレート、typelistを使用して、より高度なC ++機能を利用して型の安全性を促進することもできます。

- (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath
{
 std::pair<UITableView*, NSIndexPath*> tableRow(tableView, indexPath);  
 ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease];
 [self performSelector:@selector(selectRow:) withObject:oCTableRow];
}

0

私の2セントは少し「きれい」です。

typedefを使用します。

typedef NSArray<NSString *> StringArray;

コードでできること:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.