NSMutableArrayに1つの特定のオブジェクトタイプのみを保持させる方法はありますか?
次のようなクラス定義があります。
@interface Wheel:NSObject
{
int size;
float diameter;
}
@end
@interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray *wheels;
}
@end
強制的にwheels配列を保持するにはWheelコードのみのオブジェクトですか? (そして絶対に他のオブジェクトではない)
2015年に更新
この回答は2011年初頭に最初に書かれ、始まりました:
私たちが本当に欲しいのはパラメトリック多相性であるため、たとえば
NSMutableArray<NSString>
;しかし、残念ながらそのようなものは利用できません。
2015年にAppleは明らかにLightweight GenericsをObjective-Cに導入してこれを変更し、次のように宣言できるようになりました。
NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];
しかし、すべてが見た目とはまったく異なります。「軽量」に注意してください。その後、上記の宣言の初期化部分に一般的な表記が含まれていないことに注意してください。 Appleはパラメトリックコレクションを導入し、上記の配列に非文字列を直接追加します、onlyStrings
、たとえば:
[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...
示されているように警告を違法にします、型セキュリティはかろうじて皮膚の深さです。メソッドを検討してください:
- (void) Push:(id)obj onto:(NSMutableArray *)array
{
[array addObject:obj];
}
そして、同じクラスの別のメソッドのコードフラグメント:
NSMutableArray<NSString *> *oops = [NSMutableArray new];
[self Push:@"asda" onto:oops]; // add a string, fine
[self Push:@42 onto:oops]; // add a number, no warnings...
実装されているAppleは、本質的に、タイプセーフなジェネリックのフレーバーを持つSwiftとの自動相互運用を支援するヒントシステムです。ただし、Objective-C側では、コンパイラシステムが「軽量」であり、型の完全性は最終的にはプログラマーに委ねられるという、Objective-Cの方法と同様の追加のヒントを提供します。
それで、あなたはどちらを使うべきですか?新しい軽量/擬似ジェネリック、またはコード用に独自のパターンを考案しますか?本当に正しい答えはありません。あなたのシナリオで何が理にかなっているかを理解し、それを使用してください。
例:Swiftとの相互運用をターゲットにしている場合、軽量ジェネリックを使用する必要があります!ただし、コレクションの型の整合性がシナリオで重要な場合は、軽量ジェネリックを独自のコードと組み合わせることができますSwiftがその側で行う型の整合性を強制するObjective-C側で。
2011年の回答の残り
別のオプションとして、NSMutableArrayの簡単で一般的なサブクラスを使用して、単相配列で必要な種類のオブジェクトで初期化します。このオプションでは静的な型チェックは行われず(Obj-Cで取得する限り)、間違った型を挿入すると実行時例外が発生します。これは、インデックスの実行時例外が範囲外になったときなどに発生します。
これはnot完全にテストされており、NSMutableArrayのオーバーライドに関するドキュメントが正しいことを前提としています...
@interface MonomorphicArray : NSMutableArray
{
Class elementClass;
NSMutableArray *realArray;
}
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems;
- (id) initWithClass:(Class)element;
@end
そして実装:
@implementation MonomorphicArray
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems
{
elementClass = element;
realArray = [NSMutableArray arrayWithCapacity:numItems];
return self;
}
- (id) initWithClass:(Class)element
{
elementClass = element;
realArray = [NSMutableArray new];
return self;
}
// override primitive NSMutableArray methods and enforce monomorphism
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if ([anObject isKindOfClass:elementClass]) // allows subclasses, use isMemeberOfClass for exact match
{
[realArray insertObject:anObject atIndex:index];
}
else
{
NSException* myException = [NSException
exceptionWithName:@"InvalidAddObject"
reason:@"Added object has wrong type"
userInfo:nil];
@throw myException;
}
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[realArray removeObjectAtIndex:index];
}
// override primitive NSArray methods
- (NSUInteger) count
{
return [realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [realArray objectAtIndex:index];
}
// block all the other init's (some could be supported)
static id NotSupported()
{
NSException* myException = [NSException
exceptionWithName:@"InvalidInitializer"
reason:@"Only initWithClass: and initWithClass:andCapacity: supported"
userInfo:nil];
@throw myException;
}
- (id)initWithArray:(NSArray *)anArray { return NotSupported(); }
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { return NotSupported(); }
- (id)initWithContentsOfFile:(NSString *)aPath { return NotSupported(); }
- (id)initWithContentsOfURL:(NSURL *)aURL { return NotSupported(); }
- (id)initWithObjects:(id)firstObj, ... { return NotSupported(); }
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { return NotSupported(); }
@end
使用:
MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3];
[monoString addObject:@"A string"];
[monoString addObject:[NSNumber numberWithInt:42]]; // will throw
[monoString addObject:@"Another string"];
XCode 7では、Objective-Cでジェネリックが利用可能になりました!
したがって、NSMutableArray
を次のように宣言できます。
NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];
配列に非ホイールオブジェクトを配置しようとすると、コンパイラは警告を表示します。
私は間違っている可能性があります(私はnoobです)が、カスタムプロトコルを作成し、配列に追加するオブジェクトが同じプロトコルに従うことを確認すると、使用する配列を宣言するときに
NSArray<Protocol Name>
これにより、前述のプロトコルに従わないオブジェクトが追加されるのを防ぐことができます。
私が知っているように..ホイールmutableArrayにオブジェクトを追加する前に、チェックマークを追加する必要があります。私が追加しているオブジェクトは、クラス「ホイール」です。それが追加される場合、そうでない場合はそうではありません。
例:
if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id)
}
このようなもの。正確な構文は覚えていません。
私はこれが役立つことを願っています(そして働く...:P)
Wheel.hファイル:
@protocol Wheel
@end
@interface Wheel : NSObject
@property ...
@end
Car.hファイル:
#import "Wheel.h"
@interface Car:NSObject
{
NSString *model;
NSString *make;
NSMutableArray<Wheel, Optional> *wheels;
}
@end
Car.mファイル:
#import "Car.h"
@implementation Car
-(id)init{
if (self=[super init]){
self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
}
return self;
}
@end
Xcode 7を使用すると、配列、辞書、さらには独自のクラスでさえもジェネリックを持つものとして定義できます。配列構文は次のとおりです。
NSArray<NSString*>* array = @[@"hello world"];
NSMutableArray
をそのまま使用してそれを行う方法はないと思います。おそらく、すべてのコンストラクターと挿入メソッドをサブクラス化してオーバーライドすることでこれを実施できますが、おそらく価値はありません。これで何を達成したいですか?
プロトコルはおそらく良いアイデアです:
@protocol Person <NSObject>
@end
@interface Person : NSObject <Person>
@end
使用する:
NSArray<Person>* personArray;
NSMutableArrayのサブクラス化を回避するために私がやったことは次のとおりです。カテゴリを使用します。これにより、必要な引数と戻り値の型を使用できます。命名規則に注意してください。使用する各メソッドのWordの「オブジェクト」を要素クラスの名前に置き換えます。 「objectAtIndex」は「wheelAtIndex」などになります。このように、名前の競合はありません。とてもきれい。
typedef NSMutableArray WheelList;
@interface NSMutableArray (WheelList)
- (wheel *) wheelAtIndex: (NSUInteger) index;
- (void) addWheel: (wheel *) w;
@end
@implementation NSMutableArray (WheelList)
- (wheel *) wheelAtIndex: (NSUInteger) index
{
return (wheel *) [self objectAtIndex: index];
}
- (void) addWheel: (wheel *) w
{
[self addObject: w];
}
@end
@interface Car : NSObject
@property WheelList *wheels;
@end;
@implementation Car
@synthesize wheels;
- (id) init
{
if (self = [super init]) {
wheels = [[WheelList alloc] initWithCapacity: 4];
}
return self;
}
@end
それは可能ではありません; NSArray(可変かどうかに関係なく)は、任意のオブジェクトタイプを保持します。できることは、すでにジムが提案したように、独自のカスタムサブクラスを作成することです。または、配列をフィルター処理して、必要なタイプではないオブジェクトを削除する場合は、次のようにします。
- (void)removeObjectsFromArray:(NSMutableArray *)array otherThanOfType:(Class)type
{
int c = 0;
while(c < [array length])
{
NSObject *object = [array objectAtIndex:c];
if([object isKindOfClass:type])
c++;
else
[array removeObjectAtIndex:c];
}
}
...
[self removeObjectsFromArray:array otherThanOfType:[Car class]];
または、isKindOfClass:の結果に基づいて他の判断を行います。 CarsとWheelsの混合を含む配列を、それぞれが1種類のオブジェクトのみを含む2つの配列に分割します。
これを可能にする1つのヘッダーファイルプロジェクトがあります:Objective-C-Generics
使用法:
ObjectiveCGenerics.hをプロジェクトにコピーします。新しいクラスを定義するときは、GENERICSABLEマクロを使用します。
#import "ObjectiveCGenerics.h"
GENERICSABLE(MyClass)
@interface MyClass : NSObject<MyClass>
@property (nonatomic, strong) NSString* name;
@end
これで、Java、C#などで通常行うように、ジェネリックを配列およびセットで使用できます。
コード:
特定のオブジェクトがない場合は、nsexceptionを使用できます。
for (int i = 0; i<items.count;i++) {
if([[items objectAtIndex:i] isKindOfClass:[Wheel class]])
{
// do something..!
}else{
[NSException raise:@"Invalid value" format:@"Format of %@ is invalid", items];
// do whatever to handle or raise your exception.
}
}