NSMutableArray - Erzwingt, dass das Array nur einen bestimmten Objekttyp enthält

68

Gibt es eine Möglichkeit, NSMutableArray zu zwingen, nur einen bestimmten Objekttyp zu halten?

Ich habe Klassendefinitionen wie folgt:

@interface Wheel:NSObject  
{    
  int size;  
  float diameter;  
}  
@end  


@interface Car:NSObject  
{  
   NSString *model;  
   NSString *make;  
   NSMutableArray *wheels;  
}  
@end

Wie kann ich das Radarray zwingen , Radobjekte nur mit Code zu halten ? (und absolut keine anderen Objekte)

Tuyen Nguyen
quelle

Antworten:

96

Update im Jahr 2015

Diese Antwort wurde erstmals Anfang 2011 geschrieben und begann:

Was wir wirklich wollen, ist parametrischer Polymorphismus, damit Sie sagen können NSMutableArray<NSString>: aber leider ist solche nicht verfügbar.

Im Jahr 2015 hat Apple dies offenbar mit der Einführung von "Lightweight Generics" in Objective-C geändert. Jetzt können Sie Folgendes deklarieren:

NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];

Aber alles ist nicht ganz so, wie es scheint. Beachten Sie das "Leichtgewicht" ... Dann beachten Sie, dass der Initialisierungsteil der obigen Deklaration keine generische Notation enthält. Während Apple parametrische Sammlungen eingeführt und dem obigen Array eine Nicht-Zeichenfolge direkt hinzugefügt hat onlyStrings, wie in:

[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...

wird die Warnung wie angegeben unzulässig, die Typensicherheit ist kaum hauttief. Betrachten Sie die Methode:

- (void) push:(id)obj onto:(NSMutableArray *)array
{
   [array addObject:obj];
}

und das Codefragment in einer anderen Methode derselben Klasse:

NSMutableArray<NSString *> *oops = [NSMutableArray new];
[self push:@"asda" onto:oops]; // add a string, fine
[self push:@42 onto:oops];     // add a number, no warnings...

Was Apple implementiert hat, ist im Wesentlichen ein Hinweissystem zur Unterstützung der automatischen Interaktion mit Swift, das eine Art typsicherer Generika aufweist. Auf der Objective-C-Seite ist das System zwar "leichtgewichtig", während der Compiler einige zusätzliche Hinweise liefert, und die Typintegrität liegt letztendlich immer noch beim Programmierer - ebenso wie die Objective-C-Methode.

Also was solltest du verwenden? Die neuen Lightweight- / Pseudo-Generika oder eigene Muster für Ihren Code? Es gibt wirklich keine richtige Antwort. Finden Sie heraus, was in Ihrem Szenario Sinn macht, und verwenden Sie es.

Zum Beispiel: Wenn Sie auf eine Interaktion mit Swift abzielen, sollten Sie die leichten Generika verwenden! Wenn jedoch die Typintegrität einer Sammlung in Ihrem Szenario wichtig ist, können Sie die Lightweight-Generika mit Ihrem eigenen Code auf der Objective-C-Seite kombinieren, wodurch die Typintegrität erzwungen wird, die Swift auf seiner Seite hat.

Der Rest der Antwort von 2011

Als weitere Option finden Sie hier eine kurze allgemeine Unterklasse von NSMutableArray, die Sie mit der Art von Objekt initiieren, die Sie in Ihrem monomorphen Array haben möchten. Mit dieser Option können Sie keine statische Typprüfung durchführen (so viel Sie jemals in Obj-C erhalten haben). Sie erhalten Laufzeitausnahmen beim Einfügen des falschen Typs, genauso wie Sie Laufzeitausnahmen für Index außerhalb der Grenzen usw. erhalten.

Dies wird nicht gründlich getestet und setzt voraus, dass die Dokumentation zum Überschreiben von NSMutableArray korrekt ist ...

@interface MonomorphicArray : NSMutableArray
{
    Class elementClass;
    NSMutableArray *realArray;
}

- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems;
- (id) initWithClass:(Class)element;

@end

Und die Umsetzung:

@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

Benutzen als:

MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3];

[monoString addObject:@"A string"];
[monoString addObject:[NSNumber numberWithInt:42]]; // will throw
[monoString addObject:@"Another string"];
CRD
quelle
Danke, das suche ich.
Tuyen Nguyen
+1 - basierend auf diesem Code kam mir die Idee, Blöcke zu verwenden, um zu testen, ob ein bestimmtes Objekt zu einem Array hinzugefügt werden sollte. Dies kann eine Mitgliedschaft für eine Klasse, ein Wert für eine Eigenschaft des Objekts oder etwas anderes sein. Bitte werfen Sie einen Blick darauf: stackoverflow.com/questions/8190530/…
vikingosegundo
@bendytree - Die Verwendung von Makros zum Generieren einer Klasse pro Typ für die statische Typüberprüfung ist eine interessante Idee. Sie benötigen das Forward-Aufruf-Material jedoch überhaupt nicht. Unterordnen Sie NSMutableArray nur ordnungsgemäß wie im obigen Beispiel, und es ist viel effizienter. Kombinieren Sie also Ihre Makros mit den oben genannten und Sie erhalten das Beste aus beiden.
CRD
3
@PaulPraet - NSArrayund NSMutableArraysind ein Klassencluster und die Art und Weise, wie sie entworfen werden, muss jede Unterklasse einen eigenen Speicher für die Elemente bereitstellen - dies kann eine separate Instanz von NSArray/ sein, NSMutableArraywie hier verwendet. Siehe "Hinweise zur Unterklasse" in der NSArrayDokumentation.
CRD
1
@ BenC.R.Leggiero - Die Antwort ist nicht "falsch". Wenn Sie ein monomorphes Array möchten, erhalten Sie diese Antwort zur Laufzeit. Die neuen leichten Generika bringen Sie zwar zur Kompilierungszeit näher, aber nicht bis dahin. Ja, Sprachänderungen bedeuten, dass es ein bisschen "veraltet" ist, da es keine Dinge abdeckt, die seit dem Schreiben passiert sind. Ich habe es bearbeitet, um einige Details hinzuzufügen (da dies in diesem Fall einige Vorteile hat), aber Sie werden es oft tun finde Sachen auf SO, deren Wahrhaftigkeitszeit sich geändert hat. Wenn Antworten auf Dauer bearbeitet würden, gäbe es weit weniger Antworten! Es kann einen Wert in der Geschichte geben. :-)
CRD
30

Mit XCode 7 sind Generika jetzt in Objective-C verfügbar!

So können Sie Ihre NSMutableArrayals deklarieren :

NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];

Der Compiler gibt eine Warnung aus, wenn Sie versuchen, ein Nicht-Wheel-Objekt in Ihr Array aufzunehmen.

andreacipriani
quelle
Ich denke du hast ein Vorrad vergessen *.
NKorotkov
1
Nicht muss ich sagen [[NSMutableArray<Wheel*> alloc] init...]?
Ben Leggiero
1
Wir möchten vielleicht alle parametrische Typen, leider müssen wir Apples Kleingedrucktes lesen. Dies sind die "leichten" Generika von Apple, die im Wesentlichen nur als Kommentare zur Unterstützung des Swift-Compilers existieren. Sie "geben Ihnen leider nicht immer eine Warnung", wenn Sie versuchen, eine falsch eingegebene Operation auszuführen. Ich habe meine Antwort aktualisiert, um weitere Details bereitzustellen.
CRD
9

Ich könnte mich irren (ich bin ein Noob), aber ich denke, wenn Sie ein benutzerdefiniertes Protokoll erstellen und sicherstellen, dass die Objekte, die Sie dem Array hinzufügen, demselben Protokoll folgen, dann, wenn Sie das verwendete Array deklarieren

NSArray<Protocol Name>

Dies sollte verhindern, dass Objekte hinzugefügt werden, die dem genannten Protokoll nicht folgen.

Gravedigga
quelle
3
Warum antworten Sie, wenn Sie es nicht wissen?
Roel
19
Es ist bekannt als der Versuch, anderen Programmierern zu helfen.
Gravedigga
1
Dies ist falsch. Mit diesem Typ können Sie nur Unterklassen von NSArray zuweisen, die den Anforderungen entsprechen Protocol Name. Es bezieht sich also auf das Array-Objekt selbst und nicht auf dessen Inhalt. Sie sollten schreiben NSArray<id<Protocol Name>>, um Ihr ursprüngliches Ziel zu erreichen.
DanSkeel
5

Wie ich weiß .. Bevor Sie ein Objekt in Rädern mutableArray hinzugefügt haben, müssen Sie ein Häkchen hinzufügen. Ist das Objekt, das ich hinzufüge, Klasse "Rad". wenn es dann hinzugefügt wird, sonst weise nicht.

Beispiel:

if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id) 
}

Etwas wie das. Ich erinnere mich nicht an die genaue Syntax.

hardit2811
quelle
2
Ich bin damit einverstanden - untergeordnete Klasse nsmutablearray nicht - verstecke es einfach in deinem Auto und mache die einzigen zwei Methoden addWheel: (Wheel *) aWheel und deleteWheel:. (zum Beispiel).
Tom Andersen
Wenn ich sage, verstecke das Radarray, meine ich, mache es privat, mache es nicht zu einer Eigenschaft usw. Möglicherweise brauchst du auch ein - (NSArray *) -Rad; call, der dies zurückgibt [NSArray arrayWithArray: role];
Tom Andersen
3

Ich hoffe das wird helfen (und funktionieren ...: P)

Wheel.h Datei:

@protocol Wheel
@end

@interface Wheel : NSObject
@property ...
@end

Car.h Datei:

#import "Wheel.h"
@interface Car:NSObject  

{  
   NSString *model;  
   NSString *make;  
   NSMutableArray<Wheel, Optional> *wheels;  
}  
@end

Car.m Datei:

#import "Car.h"
@implementation Car

-(id)init{
   if (self=[super init]){
   self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
   }
return self;
}
@end
Aviram Netanel
quelle
1
Wie initialisiere ich Räder?
Jagveer Singh Rajput
@ JagveerSinghRajput hier gehen Sie - ich habe meine Antwort für Sie bearbeitet :)
Aviram Netanel
2

Mit Xcode 7 können Sie Arrays, Wörterbücher und sogar Ihre eigenen Klassen als generisch definieren. Die Array-Syntax lautet wie folgt:

NSArray<NSString*>* array = @[@"hello world"];
Brian Trzupek
quelle
1

Ich glaube nicht, dass es eine Möglichkeit gibt, dies sofort zu tun NSMutableArray. Sie könnten dies wahrscheinlich erzwingen, indem Sie alle Konstruktoren und Einfügemethoden unterklassifizieren und überschreiben, aber es lohnt sich wahrscheinlich nicht. Was hoffen Sie damit zu erreichen?

Jim
quelle
0

Das ist nicht möglich; Ein NSArray (ob veränderbar oder nicht) enthält einen beliebigen Objekttyp. Was Sie tun können, ist, Ihre eigenen benutzerdefinierten Unterklassen zu erstellen, wie bereits von Jim vorgeschlagen. Wenn Sie alternativ ein Array filtern möchten, um Objekte zu entfernen, die nicht dem gewünschten Typ entsprechen, können Sie Folgendes tun:

- (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]];

Oder treffen Sie andere Beurteilungen basierend auf dem Ergebnis von isKindOfClass:, z. B. um ein Array mit einer Mischung aus Autos und Rädern in zwei Arrays zu unterteilen, die jeweils nur eine Art von Objekt enthalten.

Tommy
quelle
0

Sie können die nsexception verwenden, wenn Sie das spezifische Objekt nicht haben.

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.
 }
}
Lalith B.
quelle
0

Folgendes habe ich getan, um eine Unterklasse von NSMutableArray zu vermeiden: Verwenden Sie eine Kategorie. Auf diese Weise können Sie die gewünschten Argument- und Rückgabetypen haben. Beachten Sie die Namenskonvention: Ersetzen Sie das Wort "Objekt" in jeder der verwendeten Methoden durch den Namen der Elementklasse. "objectAtIndex" wird zu "WheelAtIndex" und so weiter. Auf diese Weise gibt es keinen Namenskonflikt. Sehr ordentlich.

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
Charles Gillingham
quelle
Während dies funktioniert, um Ihnen die Typen zu geben, beschränkt es den Inhalt des Arrays nicht auf den Typ, den Sie einfach addObject:direkt aufrufen können - was bedeutet, dass ein nachfolgender Typ wheelAtIndex:etwas anderes als ein Rad als Rad wirken kann und Chaos entsteht. Sie können Ihren Ansatz mit MonomorphicArrayder anderen Antwort kombinieren . Erweitern Sie einfach MonomorphicArraymit einer Klasse mit getippten Gettern und Setzern.
CRD
0

Protokoll vielleicht eine gute Idee:

@protocol Person <NSObject>
@end

@interface Person : NSObject <Person>
@end

benutzen:

NSArray<Person>*  personArray;
lbsweek
quelle
Hast du deinen Code getestet? Warum kann das NSArray-konforme Personenprotokoll die Überprüfung durchführen?
JohnMa
Der Compiler würde nur dann eine Warnung ausgeben, wenn Ihr Objekt nicht dem Protokoll entspricht.
lbsweek
2
Nein, das wird es nicht. Tatsächlich werden Sie es bekommen Incompatible pointer types initializing 'NSArray<Person> *' with an expression of type 'NSArray *', selbst wenn Sie Person darin einfügen.
JohnMa
0

Es gibt ein Ein-Header-Dateiprojekt, das dies ermöglicht: Objective-C-Generics

Verwendung :

Kopieren Sie ObjectiveCGenerics.h in Ihr Projekt. Verwenden Sie beim Definieren einer neuen Klasse das GENERICSABLE-Makro.

#import "ObjectiveCGenerics.h"

GENERICSABLE(MyClass)

@interface MyClass : NSObject<MyClass>

@property (nonatomic, strong) NSString* name;

@end

Jetzt können Sie Generika mit Arrays und Sets verwenden, wie Sie es normalerweise in Java, C # usw. tun.

Code: Geben Sie hier die Bildbeschreibung ein

Mischa
quelle