Antworten:
Sie könnten eine Kategorie mit einer -addSomeClass:
Methode erstellen, um die statische Typprüfung zur Kompilierungszeit zu ermöglichen (der Compiler könnte Sie also informieren, wenn Sie versuchen, ein Objekt hinzuzufügen, von dem er weiß, dass es sich bei dieser Methode um eine andere Klasse handelt), aber es gibt keine echte Möglichkeit, dies zu erzwingen Ein Array enthält nur Objekte einer bestimmten Klasse.
Im Allgemeinen scheint es in Objective-C keine Notwendigkeit für eine solche Einschränkung zu geben. Ich glaube nicht, dass ich jemals einen erfahrenen Cocoa-Programmierer gehört habe, der sich diese Funktion wünscht. Die einzigen Leute, die scheinen, sind Programmierer aus anderen Sprachen, die immer noch in diesen Sprachen denken. Wenn Sie nur Objekte einer bestimmten Klasse in einem Array haben möchten, kleben Sie nur Objekte dieser Klasse dort ein. Wenn Sie testen möchten, ob sich Ihr Code ordnungsgemäß verhält, testen Sie ihn.
Niemand hat das hier bisher aufgestellt, also werde ich es tun!
Dies wird jetzt offiziell in Objective-C unterstützt. Ab Xcode 7 können Sie die folgende Syntax verwenden:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
Hinweis
Es ist wichtig zu beachten, dass dies nur Compiler-Warnungen sind und Sie technisch immer noch jedes Objekt in Ihr Array einfügen können. Es sind Skripte verfügbar, die alle Warnungen in Fehler umwandeln, die das Erstellen verhindern würden.
nonnull
in XCode 6 verwenden und soweit ich mich erinnere, wurden sie gleichzeitig eingeführt. Hängt die Verwendung solcher Konzepte auch von der XCode-Version oder von der iOS-Version ab?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
Sieht etwas klobig aus, macht aber den Trick!
Dies ist eine relativ häufige Frage für Personen, die von stark typisierten Sprachen (wie C ++ oder Java) zu schwach oder dynamisch typisierten Sprachen wie Python, Ruby oder Objective-C wechseln. In Objective-C erben die meisten Objekte von NSObject
(Typ id
) (der Rest erbt von einer anderen Stammklasse wie NSProxy
und kann auch Typ sein id
), und jede Nachricht kann an jedes Objekt gesendet werden. Das Senden einer Nachricht an eine Instanz, die nicht erkannt wird, kann natürlich einen Laufzeitfehler verursachen (und auch eine Compiler- Warnung auslösenmit entsprechenden -W-Flags). Solange eine Instanz auf die von Ihnen gesendete Nachricht antwortet, ist es Ihnen möglicherweise egal, zu welcher Klasse sie gehört. Dies wird oft als "Ententypisierung" bezeichnet, weil "wenn es wie eine Ente quakt [dh auf einen Selektor reagiert], es eine Ente ist [dh es kann mit der Nachricht umgehen; wen interessiert es, welche Klasse es ist]".
Sie können testen, ob eine Instanz zur Laufzeit mit dem auf einen Selektor reagiert -(BOOL)respondsToSelector:(SEL)selector
Methode . Angenommen , Sie sind auf jeden Fall eine Methode aufrufen wollen in einem Array sind aber nicht sicher , dass alle Instanzen die Nachricht verarbeiten kann (so dass Sie nicht nur verwenden können , NSArray
ist -[NSArray makeObjectsPerformSelector:]
, so etwas wie dies funktionieren würde:
for(id o in myArray) {
if([o respondsToSelector:@selector(myMethod)]) {
[o myMethod];
}
}
Wenn Sie den Quellcode für die Instanzen steuern, die die Methode (n) implementieren, die Sie aufrufen möchten, besteht der häufigere Ansatz darin, eine zu definieren @protocol
, die diese Methoden enthält, und zu deklarieren, dass die betreffenden Klassen dieses Protokoll in ihrer Deklaration implementieren. In dieser Verwendung ist a @protocol
analog zu einer Java-Schnittstelle oder einer abstrakten C ++ - Basisklasse. Sie können dann die Konformität mit dem gesamten Protokoll testen, anstatt auf jede Methode zu antworten. Im vorherigen Beispiel würde dies keinen großen Unterschied machen, aber wenn Sie mehrere Methoden aufrufen, könnte dies die Dinge vereinfachen. Das Beispiel wäre dann:
for(id o in myArray) {
if([o conformsToProtocol:@protocol(MyProtocol)]) {
[o myMethod];
}
}
unter der Annahme MyProtocol
erklärt myMethod
. Dieser zweite Ansatz wird bevorzugt, weil er die Absicht des Codes mehr verdeutlicht als der erste.
Oft befreit Sie einer dieser Ansätze davon, sich darum zu kümmern, ob alle Objekte in einem Array von einem bestimmten Typ sind. Wenn Sie sich immer noch darum kümmern, besteht der Standardansatz für dynamische Sprache in Unit-Test, Unit-Test, Unit-Test. Da eine Regression dieser Anforderung zu einem (wahrscheinlich nicht behebbaren) Laufzeitfehler (nicht zur Kompilierungszeit) führt, müssen Sie über eine Testabdeckung verfügen, um das Verhalten zu überprüfen, damit Sie keinen Crasher in die Wildnis entlassen. Führen Sie in diesem Fall eine Operation aus, die das Array ändert, und überprüfen Sie dann, ob alle Instanzen im Array zu einer bestimmten Klasse gehören. Bei ordnungsgemäßer Testabdeckung benötigen Sie nicht einmal den zusätzlichen Laufzeitaufwand für die Überprüfung der Instanzidentität. Sie haben eine gute Unit-Test-Abdeckung, nicht wahr?
id
s verwenden, außer wenn dies erforderlich ist, und nicht mehr als Java-Codierer um Object
s herumgeben . Warum nicht? Benötigen Sie es nicht, wenn Sie Unit-Tests haben? Weil es da ist und Ihren Code wartbarer macht, wie es typisierte Arrays tun würden. Klingt so, als ob Leute, die in die Plattform investiert haben, keinen Punkt einräumen wollen und deshalb Gründe erfinden, warum diese Auslassung tatsächlich ein Vorteil ist.
Sie können eine Unterklasse erstellen NSMutableArray
, um die Typensicherheit zu erzwingen.
NSMutableArray
ist ein Klassencluster , daher ist die Unterklasse nicht trivial. Am Ende habe ich Aufrufe geerbt NSArray
und an ein Array innerhalb dieser Klasse weitergeleitet. Das Ergebnis ist eine Klasse aufgerufen , ConcreteMutableArray
die ist einfach zu Unterklasse. Folgendes habe ich mir ausgedacht:
Update: Lesen Sie diesen Blog-Beitrag von Mike Ash über die Unterklasse eines Klassenclusters.
Fügen Sie diese Dateien in Ihr Projekt ein und generieren Sie dann mithilfe von Makros alle gewünschten Typen:
MyArrayTypes.h
CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)
MyArrayTypes.m
CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)
Verwendung:
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
andere Gedanken
NSArray
, um die Serialisierung / Deserialisierung zu unterstützenAbhängig von Ihrem Geschmack möchten Sie möglicherweise generische Methoden wie überschreiben / ausblenden
- (void) addObject:(id)anObject
Schauen Sie sich https://github.com/tomersh/Objective-C-Generics an , eine Generikimplementierung zur Kompilierungszeit (vom Präprozessor implementiert) für Objective-C. Dieser Blog-Beitrag hat einen schönen Überblick. Grundsätzlich erhalten Sie eine Überprüfung der Kompilierungszeit (Warnungen oder Fehler), aber keine Laufzeitstrafe für Generika.
Dieses Github-Projekt implementiert genau diese Funktionalität.
Sie können dann die verwenden <>
Klammern wie in C # verwenden.
Aus ihren Beispielen:
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
Ein möglicher Weg könnte die Unterklasse von NSArray sein, aber Apple empfiehlt, dies nicht zu tun. Es ist einfacher, zweimal über den tatsächlichen Bedarf an einem typisierten NSArray nachzudenken.
Ich habe eine NSArray-Unterklasse erstellt, die ein NSArray-Objekt als Hintergrund-IVAR verwendet, um Probleme mit der Klassencluster-Natur von NSArray zu vermeiden. Es dauert Blöcke, um das Hinzufügen eines Objekts zu akzeptieren oder abzulehnen.
Um nur NSString-Objekte zuzulassen, können Sie ein AddBlock
als definieren
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
Sie können ein definieren FailBlock
, um zu entscheiden, was zu tun ist, wenn ein Element den Test nicht bestanden hat - das Filtern ordnungsgemäß fehlschlägt, es einem anderen Array hinzufügt oder - dies ist die Standardeinstellung - eine Ausnahme auslöst.
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
Verwenden Sie es wie:
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);
Dies ist nur ein Beispielcode und wurde in realen Anwendungen nie verwendet. Dazu muss wahrscheinlich mehr NSArray-Methode implementiert werden.
Wenn Sie c ++ und objectiv-c mischen (dh den Dateityp mm verwenden), können Sie die Eingabe mithilfe von Paaren oder Tupeln erzwingen. In der folgenden Methode können Sie beispielsweise ein C ++ - Objekt vom Typ std :: pair erstellen, es in ein Objekt vom Typ OC-Wrapper (Wrapper von std :: pair, den Sie definieren müssen) konvertieren und dann an einige übergeben andere OC-Methode, bei der Sie das OC-Objekt zurück in ein C ++ - Objekt konvertieren müssen, um es verwenden zu können. Die OC-Methode akzeptiert nur den OC-Wrapper-Typ, wodurch die Typensicherheit gewährleistet wird. Sie können sogar Tupel, variable Vorlagen und Typelisten verwenden, um erweiterte C ++ - Funktionen zu nutzen und die Typensicherheit zu verbessern.
- (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];
}