Czy w Objective-C są kolekcje o jednoznacznie określonym typie?

140

Jestem nowy w programowaniu na Maca / iPhone'a i Objective-C. W C # i Javie mamy „generics”, klasy kolekcji, których składowe mogą być tylko zadeklarowanego typu. Na przykład w C #

Dictionary<int, MyCustomObject>

może zawierać tylko klucze będące liczbami całkowitymi i wartościami typu MyCustomObject. Czy podobny mechanizm istnieje w Objective-C?

Bogaty
źródło
Właśnie zaczynam się uczyć o ObjC. Może możesz użyć ObjC ++ do podnoszenia ciężarów?
Toybuilder
Możesz być zainteresowany odpowiedziami na to pytanie: Czy istnieje sposób na wymuszenie pisania na NSArray, NSMutableArray itp.? . Podaje się argumenty, dlaczego nie jest to powszechna praktyka w Objective-C / Cocoa.
mouviciel
2
ObjC ++ nie jest tak naprawdę językiem ... po prostu bardziej sposobem na odwołanie się do zdolności ObjC do obsługi C ++ inline, tak samo jak obsługiwałby C.Nie powinieneś tego robić, chyba że musisz (na przykład jeśli potrzebujesz używać biblioteki innej firmy, która została napisana w C ++).
Marc W
Prawie dokładny duplikat stackoverflow.com/questions/649483/ ...
Barry Wark
@ Mark W - „nie powinienem tego robić”, czemu nie? Użyłem ObjC ++ i działa świetnie. Mogę zrobić #import <map> i @property std :: map <int, NSString *> myDict; Mogę używać pełnego interfejsu API Cocoa ORAZ kolekcje z silnie wpisanymi typami. Nie widzę żadnych wad.
John Henckel

Odpowiedzi:

211

W Xcode 7 firma Apple wprowadziła do Objective-C „Lightweight Generics”. W Objective-C będą generować ostrzeżenia kompilatora, jeśli wystąpi niezgodność typu.

NSArray<NSString*>* arr = @[@"str"];

NSString* string = [arr objectAtIndex:0];
NSNumber* number = [arr objectAtIndex:0]; // Warning: Incompatible pointer types initializing 'NSNumber *' with an expression of type 'NSString *'

W kodzie Swift wygenerują błąd kompilatora:

var str: String = arr[0]
var num: Int = arr[0] //Error 'String' is not convertible to 'Int'

Lekkie typy generyczne są przeznaczone do użytku z NSArray, NSDictionary i NSSet, ale możesz także dodać je do swoich własnych klas:

@interface GenericsTest<__covariant T> : NSObject

-(void)genericMethod:(T)object;

@end

@implementation GenericsTest

-(void)genericMethod:(id)object {}

@end

Objective-C będzie zachowywać się tak, jak wcześniej, z ostrzeżeniami kompilatora.

GenericsTest<NSString*>* test = [GenericsTest new];

[test genericMethod:@"string"];
[test genericMethod:@1]; // Warning: Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'

ale Swift całkowicie zignoruje informacje ogólne. (Nie jest już prawdą w Swift 3+.)

var test = GenericsTest<String>() //Error: Cannot specialize non-generic type 'GenericsTest'

Oprócz klas kolekcji Foundation lekkie typy generyczne Objective-C są ignorowane przez język Swift. Wszelkie inne typy używające lekkich typów ogólnych są importowane do Swift, tak jakby były nie sparametryzowane.

Interakcja z API Objective-C

Connor
źródło
Ponieważ mam pytanie dotyczące typów generycznych i typów zwracanych w metodach, zadałem pytanie w innym wątku, aby wszystko było jasne: stackoverflow.com/questions/30828076/ ...
lvp
2
@rizzes. Tak, właśnie to zostało wprowadzone.
Connor
Jedynym zastrzeżeniem jest to, że Swift nie ignoruje całkowicie adnotacji typu w twojej ogólnej klasie ObjC. Jeśli określisz ograniczenia, np. MyClass <Foo: id<Bar>>Twój kod Swift przyjmie, że wartości są typem twojego ograniczenia, co daje ci coś do pracy. Jednak dla wyspecjalizowanych podklas MyClasszignorowano by ich wyspecjalizowane typy (w praktyce wyglądałyby tak samo jak rodzaj ogólny MyClass). Zobacz github.com/bgerstle/LightweightGenericsExample
Brian Gerstle
Czy to kompiluje się dla 10.10, 10.9 i wcześniejszych systemów operacyjnych?
p0lAris
Powinien tak długo, jak długo ustawisz cel wdrożenia, aby je wspierać
Connor
91

Ta odpowiedź jest nieaktualna, ale ma wartość historyczną. Począwszy od Xcode 7, odpowiedź Connora z 8 czerwca 2015 jest dokładniejsza.


Nie, w Objective-C nie ma typów ogólnych, chyba że chcesz używać szablonów C ++ we własnych klasach kolekcji niestandardowych (czego zdecydowanie odradzam).

Objective-C ma funkcję dynamicznego typowania, co oznacza, że ​​środowisko wykonawcze nie dba o typ obiektu, ponieważ wszystkie obiekty mogą odbierać komunikaty. Po dodaniu obiektu do kolekcji wbudowanej są one traktowane tak, jakby były typem id. Ale nie martw się, po prostu wysyłaj wiadomości do tych obiektów jak zwykle; będzie działać dobrze (chyba że jeden lub więcej obiektów w kolekcji nie odpowie na wysyłaną wiadomość) .

Typy generyczne są potrzebne w językach takich jak Java i C #, ponieważ są to silne języki z typami statycznymi. Zupełnie inna gra w piłkę niż funkcja dynamicznego pisania w Objective-C.

Marc W.
źródło
88
Nie zgadzam się na „nie martw się, po prostu wyślij wiadomości do tych obiektów”. Jeśli umieścisz w kolekcji niewłaściwy typ obiektów, które nie odpowiadają na te komunikaty, spowoduje to błędy w czasie wykonywania. Używanie typów ogólnych w innych językach pozwala uniknąć tego problemu przy sprawdzaniu czasu kompilacji.
henning77
8
@ henning77 Tak, ale Objective-C jest bardziej dynamicznym językiem niż te języki. Jeśli chcesz silnego bezpieczeństwa typów, użyj tych języków.
Raffi Khatchadourian
36
Nie zgadzam się również z filozofią „nie martw się” - na przykład, jeśli wyciągniesz pierwszy element z NSArray i rzucisz go na numer NS, ale ten element był naprawdę NSString, masz
przerąbane
13
@RaffiKhatchadourian - nie ma wielkiego wyboru, jeśli piszesz aplikację na iOS. Gdyby łatwo było napisać taką aplikację w Javie i uzyskać wszystkie korzyści płynące z napisania natywnej aplikacji, uwierz mi: zrobiłbym to.
ericsoco
11
Największy zarzut, jaki mam na ten temat, nie dotyczy języków dynamicznych w porównaniu ze sprawdzaniem czasu kompilacji, ale prostą komunikacją programistów. Nie mogę po prostu spojrzeć na deklarację właściwości i wiedzieć, jakiego typu obiekty ona zwróci, chyba że jest gdzieś udokumentowana.
devios1
11

Nie, ale żeby było jaśniej, możesz skomentować to z typem obiektu, który chcesz przechowywać, widziałem to już kilka razy, kiedy musisz napisać coś w Javie 1.4) np:

NSMutableArray* /*<TypeA>*/ arrayName = ....

lub

NSDictionary* /*<TypeA, TypeB>*/ dictionaryName = ...
Mark Rhodes
źródło
Myślę, że to dobry sposób na udokumentowanie tego, na wypadek, gdyby ktoś inny przeczytał Twój kod. W każdym razie nazwa zmiennej powinna być tak jasna, jak to tylko możliwe, aby wiedzieć, jakie obiekty zawiera.
htafoya
6

W Objective-C nie ma leków generycznych.

Z Dokumentów

Tablice to uporządkowane zbiory obiektów. Cocoa udostępnia kilka klas tablic, NSArray, NSMutableArray (podklasa NSArray) i NSPointerArray.

Matthew Vines
źródło
Link do dokumentu w odpowiedzi nie działa - „Przepraszamy, nie można znaleźć tej strony” .
Pang
5

To zostało wydane w Xcode 7 (w końcu!)

Zauważ, że w kodzie Objective C jest to po prostu sprawdzenie kompilacji; nie wystąpi błąd w czasie wykonywania tylko po umieszczeniu niewłaściwego typu w kolekcji lub przypisaniu do wpisanej właściwości.

Ogłosić:

@interface FooClass <T> : NSObject
@property (nonatomic) T prop;
@end

Posługiwać się:

FooClass<NSString *> *foo = [[FooClass alloc] init];
NSArray<FooClass<NSString *> *> *fooAry = [NSArray array];

Uważaj na te *s.

Kevin
źródło
4

Generyczne NSArrays można zrealizować przez podklasy NSArrayi przedefiniowanie wszystkich dostarczonych metod na bardziej restrykcyjne. Na przykład,

- (id)objectAtIndex:(NSUInteger)index

musiałby zostać przedefiniowany w

@interface NSStringArray : NSArray

tak jak

- (NSString *)objectAtIndex:(NSUInteger)index

aby NSArray zawierał tylko NSStrings.

Utworzona podklasa może być używana jako zamiennik typu drop-in i zapewnia wiele przydatnych funkcji: ostrzeżenia kompilatora, dostęp do właściwości, lepsze tworzenie kodu i -kompletność w Xcode. Wszystkie te funkcje są dostępne w czasie kompilacji, nie ma potrzeby ponownego definiowania rzeczywistej implementacji - nadal można używać metod NSArray.

Można to zautomatyzować i sprowadzić tylko do dwóch instrukcji, co zbliża to do języków obsługujących typy generyczne. Stworzyłem automatyzację za pomocą WMGenericCollection , w której szablony są dostarczane jako makra C Preprocessor.

Po zaimportowaniu pliku nagłówkowego zawierającego makro, można utworzyć ogólny NSArray z dwoma instrukcjami: jedną dla interfejsu i jedną dla implementacji. Musisz tylko podać typ danych, które chcesz przechowywać, i nazwy podklas. WMGenericCollection dostarcza takich szablonów NSArray, NSDictionarya NSSet, jak również ich Zmienne odpowiedniki.

Przykład: List<int>może być zrealizowany przez niestandardową klasę o nazwie NumberArray, która jest tworzona za pomocą następującej instrukcji:

WMGENERICARRAY_INTERFACE(NSNumber *, // type of the value class
                         // generated class names
                         NumberArray, MutableNumberArray)

Po utworzeniu NumberArraymożesz go używać w całym projekcie. Brakuje składni <int>, ale możesz wybrać własny schemat nazewnictwa, aby oznaczyć je jako klasy jako szablony.

wm
źródło
zauważ, że to samo istnieje w CoreLib: github.com/core-code/CoreLib/blob/master/CoreLib/CoreLib.h#L105
user1259710
2

Teraz marzenia się spełniają - od dziś w Objective-C są Generics (dzięki, WWDC). To nie jest żart - na oficjalnej stronie Swift:

Nowe funkcje składni umożliwiają pisanie bardziej wyrazistego kodu, jednocześnie poprawiając spójność w całym języku. Zestawy SDK wykorzystywały nowe funkcje Objective-C, takie jak typy generyczne i adnotacje o wartości null, aby kod Swift był jeszcze czystszy i bezpieczniejszy. Oto kilka przykładów ulepszeń języka Swift 2.0.

I obraz, który to potwierdza:Obiektywne typy generyczne

htzfun
źródło
2

Chcę tylko wskoczyć tutaj. Napisałem tutaj post na blogu o generykach.

Chcę wnieść swój wkład w to, że Generics można dodać do dowolnej klasy , a nie tylko do klas kolekcji, jak wskazuje Apple.

Z powodzeniem dodałem wtedy do różnych klas, ponieważ działają one dokładnie tak samo, jak kolekcje Apple. to znaczy. sprawdzanie czasu kompilacji, uzupełnianie kodu, umożliwianie usuwania rzutów itp.

Cieszyć się.

drekka
źródło
-2

Klasy Collection dostarczane przez platformy Apple i GNUStep są półgeneryczne, ponieważ zakładają, że są to obiekty, które można sortować, a inne odpowiadają na określone wiadomości. W przypadku prymitywów, takich jak floats, ints, itp., Cała struktura tablic w języku C jest nienaruszona i może być używana, a istnieją dla nich specjalne obiekty opakowujące do użytku w ogólnych klasach kolekcji (np. NSNumber). Ponadto klasa Collection może być podzielona na podklasy (lub specjalnie zmodyfikowane za pomocą kategorii), aby akceptować obiekty dowolnego typu, ale cały kod obsługi typów musisz napisać samodzielnie. Wiadomości mogą być wysyłane do dowolnego obiektu, ale powinny zwracać wartość null, jeśli jest nieodpowiednia dla obiektu lub wiadomość powinna zostać przesłana do odpowiedniego obiektu. Błędy typu rzeczywistego powinny być wychwytywane w czasie kompilacji, a nie w czasie wykonywania. W czasie wykonywania powinny być obsługiwane lub ignorowane. Wreszcie, Objc zapewnia narzędzia do refleksji w czasie wykonywania do obsługi trudnych przypadków i odpowiedzi na wiadomość, określonego typu i usług można sprawdzić na obiekcie przed wysłaniem wiadomości lub umieszczeniem w niewłaściwej kolekcji. Uważaj, że różne biblioteki i frameworki przyjmują różne konwencje dotyczące zachowania ich obiektów podczas wysyłania wiadomości, na które nie mają odpowiedzi w kodzie, czyli RTFM. Poza programami zabawkowymi i kompilacjami do debugowania, większość programów nie powinna się zawieszać, chyba że naprawdę schrzanią i spróbują zapisać złe dane w pamięci lub na dysku, wykonać nielegalne operacje (np. Podzielić przez zero, ale to też można złapać) lub uzyskać dostęp niedostępne zasoby systemowe. Dynamika i czas wykonywania Objective-C pozwala na to, że rzeczy zawodzą z wdziękiem i powinny być wbudowane w twój kod. (WSKAZÓWKA) Jeśli masz problem z ogólnością swoich funkcji, spróbuj czegoś konkretnego. Napisz funkcje z określonymi typami i pozwól środowisku wykonawczemu wybrać (dlatego nazywa się je selektorami!) Odpowiednią funkcję składową w czasie wykonywania.

Example:
    -(id) sort (id) obj;  // too generic. catches all.
     // better
    -(id) sort: (EasilySortableCollection*) esc;
    -(id) sort: (HardToSortCollection*) hsc; 
    ...
    [Sorter  sort: MyEasyColl];
    [Sorter  sort: MyHardColl];
Chris Reid
źródło