web-dev-qa-db-ja.com

NSArray、NSMutableArrayなどに型付けを強制する方法はありますか?

all要素がNSMutableArray型であるSomeClassインスタンスを作成できますか?

91
Sam Lee

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

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

35
Chuck

まだ誰もここに置いてないので、やるよ!

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

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

これらはコンパイラの警告のみであり、技術的には任意のオブジェクトを配列に挿入できることに注意することが重要です。すべての警告をビルドに失敗するエラーに変えるスクリプトが利用可能です。

142
Logan

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

-(BOOL)respondsToSelector:(SEL)selectorメソッドを使用すると、インスタンスが実行時にセレクターに応答するかどうかをテストできます。配列内のすべてのインスタンスでメソッドを呼び出したいが、すべてのインスタンスがメッセージを処理できるかどうかわからないと仮定します(したがって、NSArray 's -[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];
  }
}

MyProtocolmyMethodを宣言すると仮定します。この2番目の方法は、最初の方法よりもコードの意図を明確にするため、好まれます。

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

53
Barry Wark

型の安全性を強化するためにNSMutableArrayをサブクラス化できます。

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

Update:チェックアウト 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

11
bendytree

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

7
Barry Wark

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

その後、<>ブラケット、C#の場合と同じように。

例から:

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

NSArrayをサブクラス化する方法も考えられますが、Appleしないことをお勧めします。型付きNSArrayの実際の必要性を2回考える方が簡単です。

0
mouviciel

C++とobjective-cを混合する場合(つまり、mmファイルタイプを使用する場合)、ペアまたはタプルを使用して入力を強制できます。たとえば、次のメソッドでは、std :: pair型のC++オブジェクトを作成し、それをOCラッパー型(定義する必要があるstd :: pairのラッパー)のオブジェクトに変換してから、一部に渡すことができます。他のOCメソッド。使用するためにOCオブジェクトをC++オブジェクトに変換する必要があります。 OCメソッドはOCラッパータイプのみを受け入れるため、タイプの安全性が確保されます。タプル、可変長テンプレート、タイプリストを使用して、より高度な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
colin

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

nSStringオブジェクトのみを許可するには、AddBlockを次のように定義できます。

^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
vikingosegundo

私の2セントは少し「きれい」になります:

typedefを使用します。

typedef NSArray<NSString *> StringArray;

コードでできること:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
0
ingconti