Czy istnieje sposób na wymuszenie pisania na NSArray, NSMutableArray itp.?

Odpowiedzi:

35

Możesz utworzyć kategorię za pomocą -addSomeClass:metody umożliwiającej sprawdzanie typu statycznego w czasie kompilacji (aby kompilator mógł Cię poinformować, jeśli spróbujesz dodać obiekt, o którym wie, że jest inną klasą za pomocą tej metody), ale nie ma rzeczywistego sposobu, aby to wymusić tablica zawiera tylko obiekty danej klasy.

Ogólnie rzecz biorąc, wydaje się, że nie ma potrzeby stosowania takiego ograniczenia w Objective-C. Chyba nigdy nie słyszałem, żeby doświadczony programista Cocoa chciał mieć taką funkcję. Jedynymi osobami, które wydają się być programiści z innych języków, którzy wciąż myślą w tych językach. Jeśli chcesz, aby w tablicy były tylko obiekty danej klasy, umieszczaj w niej tylko obiekty tej klasy. Jeśli chcesz sprawdzić, czy kod zachowuje się poprawnie, przetestuj go.

Gdakanie
źródło
137
Myślę, że „doświadczeni programiści Cocoa” po prostu nie wiedzą, czego im brakuje - doświadczenie z Javą pokazuje, że zmienne typu poprawiają zrozumienie kodu i umożliwiają więcej refaktoryzacji.
tgdavies
12
Cóż, obsługa Generics w Javie jest mocno zepsuta, ponieważ nie została wprowadzona od samego początku ...
dertoni,
28
Zgadzam się z @tgdavies. Brakuje mi możliwości Intellisense i refaktoryzacji, które miałem w C #. Kiedy chcę dynamicznego pisania, mogę to uzyskać w C # 4.0. Kiedy chcę mocno typować rzeczy, też to mogę mieć. Odkryłem, że jest czas i miejsce na obie te rzeczy.
Steve
18
@charkrit Co jest takiego w Objective-C, że „nie jest to konieczne”? Czy uważasz, że było to konieczne, gdy używasz C #? Słyszę wiele osób, które mówią, że nie potrzebujesz tego w Objective-C, ale myślę, że ci sami ludzie uważają, że nie potrzebujesz go w żadnym języku, co sprawia, że ​​jest to kwestia preferencji / stylu, a nie konieczności.
Bacar
17
Czy nie chodzi o to, aby pozwolić kompilatorowi faktycznie pomóc Ci znaleźć problemy. Jasne, że możesz powiedzieć „Jeśli chcesz, aby w tablicy były tylko obiekty danej klasy, umieszczaj tam tylko obiekty tej klasy”. Ale jeśli testy są jedynym sposobem, aby to wymusić, jesteś w niekorzystnej sytuacji. Im dalej od napisania kodu znajdujesz problem, tym bardziej jest on kosztowny.
GreenKiwi
145

Nikt jeszcze tego tutaj nie umieścił, więc zrobię to!

Jest to teraz oficjalnie obsługiwane w Objective-C. Począwszy od Xcode 7, możesz użyć następującej składni:

NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];

Uwaga

Należy zauważyć, że są to tylko ostrzeżenia kompilatora i technicznie nadal można wstawić dowolny obiekt do tablicy. Dostępne są skrypty, które zamieniają wszystkie ostrzeżenia w błędy, które uniemożliwiają budowanie.

Logan
źródło
Jestem tu leniwy, ale dlaczego jest to dostępne tylko w XCode 7? Możemy użyć nonnullw XCode 6 i o ile pamiętam, zostały one wprowadzone w tym samym czasie. Ponadto, czy użycie takich koncepcji zależy od wersji XCode lub wersji iOS?
Guven
@Guven - wartość zerowa pojawiła się w 6, masz rację, ale typy generyczne ObjC zostały wprowadzone dopiero w Xcode 7.
Logan
Jestem prawie pewien, że zależy to tylko od wersji Xcode. Ogólne są tylko ostrzeżeniami kompilatora i nie są wskazywane w czasie wykonywania. Jestem prawie pewien, że możesz skompilować do dowolnego systemu OS.
Logan,
2
@DeanKelly - Możesz to zrobić w ten sposób: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; Wygląda trochę niezgrabnie, ale załatwia sprawę !
Logan
1
@Logan to nie tylko zestaw skryptów, które uniemożliwiają budowanie w przypadku wykrycia jakiegokolwiek ostrzeżenia. Xcode posiada doskonały mechanizm o nazwie „Konfiguracja”. Sprawdź to boredzo.org/blog/archives/2009-11-07/warnings
adnako
53

Jest to stosunkowo częste pytanie dla osób przechodzących z języków silnie typowanych (takich jak C ++ lub Java) na języki słabiej lub dynamicznie typowane, takie jak Python, Ruby lub Objective-C. W Objective-C większość obiektów dziedziczy po NSObject(typ id) (reszta dziedziczy z innej klasy głównej, takiej jak NSProxyi może być typem id), a każda wiadomość może zostać wysłana do dowolnego obiektu. Oczywiście wysłanie wiadomości do instancji, której ona nie rozpoznaje, może spowodować błąd w czasie wykonywania (a także spowoduje ostrzeżenie kompilatoraz odpowiednimi flagami -W). Dopóki instancja odpowie na wysłaną wiadomość, możesz nie przejmować się, do jakiej klasy należy. Jest to często nazywane „pisaniem typu kaczka”, ponieważ „jeśli kwacze jak kaczka [tj. Odpowiada na selektor], to jest to kaczka [tj. Może obsłużyć wiadomość; kogo to obchodzi, jaka to klasa]”.

Za pomocą -(BOOL)respondsToSelector:(SEL)selectormetody można sprawdzić, czy wystąpienie odpowiada na selektor w czasie wykonywania . Zakładając, że chcesz wywołać metodę na każdej instancji w tablicy, ale nie jesteś pewien, czy wszystkie instancje mogą obsłużyć wiadomość (więc nie możesz po prostu użyć NSArray's -[NSArray makeObjectsPerformSelector:], coś takiego zadziała:

for(id o in myArray) {
  if([o respondsToSelector:@selector(myMethod)]) {
    [o myMethod];
  }
}

Jeśli kontrolujesz kod źródłowy dla instancji, które implementują metody, które chcesz wywołać, bardziej powszechnym podejściem byłoby zdefiniowanie a, @protocolktóra zawiera te metody i zadeklarowanie, że omawiane klasy implementują ten protokół w swojej deklaracji. W tym zastosowaniu a @protocoljest analogiczne do interfejsu Java lub abstrakcyjnej klasy bazowej C ++. Następnie możesz przetestować zgodność z całym protokołem zamiast odpowiedzi na każdą metodę. W poprzednim przykładzie nie zrobiłoby to dużej różnicy, ale gdybyś wywoływał wiele metod, może to uprościć sprawę. Przykładem byłoby wtedy:

for(id o in myArray) {
  if([o conformsToProtocol:@protocol(MyProtocol)]) {
    [o myMethod];
  }
}

zakładając MyProtocoldeklaruje myMethod. To drugie podejście jest preferowane, ponieważ wyjaśnia intencję kodu bardziej niż pierwsze.

Często jedno z tych podejść uwalnia Cię od dbania o to, czy wszystkie obiekty w tablicy są danego typu. Jeśli nadal Ci zależy, standardowym podejściem do języka dynamicznego jest test jednostkowy, test jednostkowy i test jednostkowy. Ponieważ regresja w tym wymaganiu spowoduje (prawdopodobnie nieodwracalny) błąd w czasie wykonywania (nie czas kompilacji), musisz mieć pokrycie testowe, aby zweryfikować zachowanie, aby nie wypuszczać crashera na wolność. W takim przypadku wykonaj operację modyfikującą tablicę, a następnie sprawdź, czy wszystkie instancje w tablicy należą do danej klasy. Przy odpowiednim pokryciu testów nie potrzebujesz nawet dodatkowego obciążenia środowiska wykonawczego związanego z weryfikacją tożsamości instancji. Masz dobre pokrycie testów jednostkowych, prawda?

Barry Wark
źródło
35
Testy jednostkowe nie zastępują przyzwoitego systemu typów.
TBA
8
Tak, kto potrzebuje narzędzi, na które mogłyby pozwolić typowane tablice. Jestem pewien, że @BarryWark (i każdy, kto dotknął jakiejkolwiek bazy kodu, której potrzebuje, aby użyć, przeczytać, zrozumieć i wesprzeć) ma 100% pokrycie kodu. Jednak założę się, że nie używasz surowych plików ids, z wyjątkiem przypadków, gdy jest to konieczne, tak samo jak programiści Java nie przekazują więcej niż Objects. Dlaczego nie? Nie potrzebujesz tego, jeśli masz testy jednostkowe? Ponieważ jest tam i sprawia, że ​​twój kod jest łatwiejszy w utrzymaniu, podobnie jak tablice typowane. Wygląda na to, że ludzie zainwestowali w platformę, nie chcąc przyznać racji, i dlatego wymyślają powody, dla których to zaniedbanie jest w rzeczywistości korzyścią.
funkybro
„Pisząca kaczka” ?? to przezabawne! nigdy wcześniej tego nie słyszałem.
John Henckel
11

Możesz utworzyć podklasę, NSMutableArrayaby wymusić bezpieczeństwo typu.

NSMutableArrayjest klastrem klas , więc tworzenie podklas nie jest trywialne. Skończyło się na dziedziczeniu NSArrayi przekazywaniu wywołań do tablicy wewnątrz tej klasy. Rezultatem jest klasa o nazwie, ConcreteMutableArrayktórą można łatwo podklasować. Oto, co wymyśliłem:

Aktualizacja: sprawdź ten post na blogu Mike'a Asha dotyczący tworzenia podklas klastra klas.

Uwzględnij te pliki w swoim projekcie, a następnie wygeneruj dowolne typy za pomocą makr:

MyArrayTypes.h

CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)

MyArrayTypes.m

CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)

Stosowanie:

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

inne przemyślenia

  • Dziedziczy z NSArraydo obsługi serializacji / deserializacji
  • W zależności od upodobań możesz zastąpić / ukryć ogólne metody, takie jak

    - (void) addObject:(id)anObject

bendytree
źródło
Fajnie, ale na razie brakuje silnego pisania, zastępując niektóre metody. Obecnie jest to tylko słabe pisanie.
Cœur,
7

Spójrz na https://github.com/tomersh/Objective-C-Generics , implementację generyczną w czasie kompilacji (implementowaną przez preprocesor) dla Objective-C. Ten post na blogu ma ładny przegląd. Zasadniczo otrzymujesz sprawdzanie czasu kompilacji (ostrzeżenia lub błędy), ale nie ma żadnych kar w czasie wykonywania dla typów ogólnych.

Barry Wark
źródło
1
Wypróbowałem to, bardzo dobry pomysł, ale niestety zawiera błędy i nie sprawdza dodanych elementów.
Binarian,
4

Ten projekt Github implementuje dokładnie tę funkcjonalność.

Następnie możesz użyć <>nawiasów, tak jak w C #.

Z ich przykładów:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
IluTov
źródło
0

Możliwym sposobem może być podklasa NSArray, ale Apple odradza tego. Łatwiej jest pomyśleć dwa razy o rzeczywistym zapotrzebowaniu na typ NSArray.

mouviciel
źródło
1
Oszczędza czas statyczne sprawdzanie typów podczas kompilacji, edycja jest jeszcze lepsza. Szczególnie przydatne, gdy piszesz lib do długotrwałego użytku.
pinxue
0

Utworzyłem podklasę NSArray, która używa obiektu NSArray jako zaplecza ivar, aby uniknąć problemów z klastrową naturą NSArray. Potrzeba bloków, aby zaakceptować lub odmówić dodania obiektu.

aby zezwolić tylko na obiekty NSString, możesz zdefiniować AddBlockas

^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
}

Możesz zdefiniować a, FailBlockaby zdecydować, co zrobić, jeśli element nie przejdzie testu - z wdziękiem nie przejdzie filtrowania, dodaj go do innej tablicy lub - jest to domyślne - zgłoś wyjątek.

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

Użyj go jak:

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);

To jest tylko przykładowy kod i nigdy nie był używany w rzeczywistych aplikacjach. aby to zrobić, prawdopodobnie wymaga zaimplementowania większej metody NSArray.

vikingosegundo
źródło
0

Jeśli łączysz C ++ i Objective-c (np. Używając typu pliku mm), możesz wymusić wpisywanie przy użyciu pary lub krotki. Na przykład w poniższej metodzie można utworzyć obiekt C ++ typu std :: pair, przekonwertować go na obiekt typu wrapper OC (opakowanie std :: pair, które należy zdefiniować), a następnie przekazać inna metoda OC, w ramach której musisz przekonwertować obiekt OC z powrotem na obiekt C ++, aby z niego skorzystać. Metoda OC akceptuje tylko typ owijki OC, zapewniając w ten sposób bezpieczeństwo typu. Możesz nawet użyć krotki, szablonu wariadycznego, listy typów, aby wykorzystać bardziej zaawansowane funkcje C ++, aby ułatwić bezpieczeństwo typów.

- (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];
}
colin
źródło
0

moje dwa centy za trochę „czystsze”:

użyj czcionek typu:

typedef NSArray<NSString *> StringArray;

w kodzie możemy zrobić:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
ingconti
źródło
0

2020, prosta odpowiedź. Tak się złożyło, że potrzebuję mutowalnej tablicy z typem NSString.

Składnia:

Type<ArrayElementType *> *objectName;

Przykład:

@property(nonatomic, strong) NSMutableArray<NSString *> *buttonInputCellValues;
Glenn Posadas
źródło