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.
Ich denke, dass 'erfahrene Cocoa-Programmierer' einfach nicht wissen, was sie vermissen - die Erfahrung mit Java zeigt, dass Typvariablen das Codeverständnis verbessern und mehr Refactorings ermöglichen.
Tgdavies
11
Nun, Javas Generics-Unterstützung ist an sich schon stark kaputt, weil sie nicht von Anfang an eingesetzt wurde ...
dertoni
28
Ich muss @tgdavies zustimmen. Ich vermisse die Intellisense- und Refactoring-Funktionen, die ich mit C # hatte. Wenn ich dynamisch tippen möchte, kann ich es in C # 4.0 erhalten. Wenn ich stark typisieren will, kann ich das auch haben. Ich habe festgestellt, dass es für beide Dinge eine Zeit und einen Ort gibt.
Steve
18
@charkrit Was macht Objective-C "nicht notwendig"? Hielten Sie es für notwendig, als Sie C # verwendeten? Ich höre viele Leute sagen, dass Sie es in Objective-C nicht brauchen, aber ich denke, dass dieselben Leute denken, dass Sie es in keiner Sprache brauchen, was es zu einer Frage der Präferenz / des Stils macht, nicht der Notwendigkeit.
Bacar
17
Geht es nicht darum, dass Ihr Compiler Ihnen tatsächlich hilft, Probleme zu finden? Sicher können Sie sagen: "Wenn Sie nur Objekte einer bestimmten Klasse in einem Array haben möchten, stecken Sie nur Objekte dieser Klasse dort hinein." Aber wenn Tests der einzige Weg sind, dies durchzusetzen, sind Sie im Nachteil. Je weiter Sie vom Schreiben des Codes entfernt sind, desto teurer wird dieses Problem.
GreenKiwi
145
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:
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.
Ich bin hier faul, aber warum ist dies nur in XCode 7 verfügbar? Wir können den nonnullin 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?
Guven
@ Guven - Nullability kam in 6, Sie sind richtig, aber ObjC Generika wurden erst mit Xcode 7 eingeführt.
Logan
Ich bin mir ziemlich sicher, dass es nur von der Xcode-Version abhängt. Die Generika sind nur Compiler-Warnungen und werden zur Laufzeit nicht angezeigt. Ich bin mir ziemlich sicher, dass Sie zu jedem gewünschten Os kompilieren können.
Logan
2
@ DeanKelly - Das könnte man so machen: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; Sieht etwas klobig aus, macht aber den Trick!
Logan
1
@Logan, es gibt nicht nur eine Reihe von Skripten, die das Erstellen verhindern, wenn eine Warnung erkannt wird. Xcode hat einen perfekten Mechanismus namens "Konfiguration". Überprüfen Sie dies aus gelangzozo.org/blog/archives/2009-11-07/warnings
adnako
53
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 NSProxyund 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 , NSArrayist -[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 @protocolanalog 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 MyProtocolerklä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?
Unit-Tests sind kein Ersatz für ein anständiges System.
bis
8
Ja, wer braucht das Werkzeug, das sich typisierte Arrays leisten würden? Ich bin sicher, dass @BarryWark (und jeder andere, der eine Codebasis berührt hat, die er verwenden, lesen, verstehen und unterstützen muss) eine 100% ige Codeabdeckung aufweist. Ich wette jedoch, dass Sie keine rohen ids verwenden, außer wenn dies erforderlich ist, und nicht mehr als Java-Codierer um Objects 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.
Funkybro
"Ente tippen" ?? das ist komisch! habe das noch nie gehört.
John Henckel
11
Sie können eine Unterklasse erstellen NSMutableArray, um die Typensicherheit zu erzwingen.
NSMutableArrayist ein Klassencluster , daher ist die Unterklasse nicht trivial. Am Ende habe ich Aufrufe geerbt NSArrayund an ein Array innerhalb dieser Klasse weitergeleitet. Das Ergebnis ist eine Klasse aufgerufen , ConcreteMutableArraydie ist einfach zu Unterklasse. Folgendes habe ich mir ausgedacht:
Schön, aber im Moment fehlt es an starker Schreibweise, indem einige Methoden überschrieben werden. Derzeit ist es nur schwach tippen.
Cœur
7
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.
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.
Es spart Zeit, die statische Typprüfung beim Kompilieren durchzuführen. Die Bearbeitung ist sogar noch besser. Besonders hilfreich, wenn Sie lib für den Langzeitgebrauch schreiben.
Pinxue
0
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 AddBlockals definieren
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*stringArray =[[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element){return[element isKindOfClass:[NSStringclass]];}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:[NSNumberclass]];}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.
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.
quelle
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:
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.
quelle
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
(Typid
) (der Rest erbt von einer anderen Stammklasse wieNSProxy
und kann auch Typ seinid
), 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: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:unter der Annahme
MyProtocol
erklärtmyMethod
. 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?
quelle
id
s verwenden, außer wenn dies erforderlich ist, und nicht mehr als Java-Codierer umObject
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 geerbtNSArray
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
MyArrayTypes.m
Verwendung:
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
quelle
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.
quelle
Dieses Github-Projekt implementiert genau diese Funktionalität.
Sie können dann die verwenden
<>
Klammern wie in C # verwenden.Aus ihren Beispielen:
quelle
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.
quelle
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 definierenSie 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
VSBlockTestedObjectArray.m
Verwenden Sie es wie:
Dies ist nur ein Beispielcode und wurde in realen Anwendungen nie verwendet. Dazu muss wahrscheinlich mehr NSArray-Methode implementiert werden.
quelle
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.
quelle
meine zwei Cent, um ein bisschen "sauberer" zu sein:
benutze typedefs:
im Code können wir tun:
quelle