Czy mogę utworzyć NSMutableArray
instancję, w której wszystkie elementy są typu SomeClass
?
Odpowiedzi:
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.
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.
nonnull
w 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?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
Wygląda trochę niezgrabnie, ale załatwia sprawę !
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 NSProxy
i 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)selector
metody 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, @protocol
która zawiera te metody i zadeklarowanie, że omawiane klasy implementują ten protokół w swojej deklaracji. W tym zastosowaniu a @protocol
jest 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 MyProtocol
deklaruje 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?
id
s, z wyjątkiem przypadków, gdy jest to konieczne, tak samo jak programiści Java nie przekazują więcej niż Object
s. 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ą.
Możesz utworzyć podklasę, NSMutableArray
aby wymusić bezpieczeństwo typu.
NSMutableArray
jest klastrem klas , więc tworzenie podklas nie jest trywialne. Skończyło się na dziedziczeniu NSArray
i przekazywaniu wywołań do tablicy wewnątrz tej klasy. Rezultatem jest klasa o nazwie, ConcreteMutableArray
któ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
NSArray
do obsługi serializacji / deserializacjiW zależności od upodobań możesz zastąpić / ukryć ogólne metody, takie jak
- (void) addObject:(id)anObject
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.
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
Możliwym sposobem może być podklasa NSArray, ale Apple odradza tego. Łatwiej jest pomyśleć dwa razy o rzeczywistym zapotrzebowaniu na typ NSArray.
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ć AddBlock
as
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
Możesz zdefiniować a, FailBlock
aby 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.
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];
}
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;