Risposte:
Potresti creare una categoria con un -addSomeClass:
metodo per consentire il controllo del tipo statico in fase di compilazione (così il compilatore potrebbe farti sapere se provi ad aggiungere un oggetto che sa essere una classe diversa attraverso quel metodo), ma non c'è un modo reale per imporlo un array contiene solo oggetti di una data classe.
In generale, non sembra esserci bisogno di un tale vincolo in Objective-C. Non credo di aver mai sentito un programmatore esperto di Cocoa desiderare questa funzione. Le uniche persone che sembrano essere programmatori di altri linguaggi che stanno ancora pensando in quei linguaggi. Se vuoi solo oggetti di una data classe in un array, inserisci solo oggetti di quella classe lì dentro. Se vuoi verificare che il tuo codice si stia comportando correttamente, provalo.
Nessuno lo ha ancora messo qui, quindi lo farò!
Questo è ora ufficialmente supportato in Objective-C. A partire da Xcode 7, puoi utilizzare la seguente sintassi:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
Nota
È importante notare che questi sono solo avvisi del compilatore e tecnicamente puoi ancora inserire qualsiasi oggetto nel tuo array. Sono disponibili script che trasformano tutti gli avvisi in errori che impedirebbero la creazione.
nonnull
in XCode 6 e, per quanto ricordo, sono stati introdotti allo stesso tempo. Inoltre, l'utilizzo di tali concetti dipende dalla versione XCode o dalla versione iOS?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
sembra un po 'goffo, ma fa il trucco!
Questa è una domanda relativamente comune per le persone che passano da linguaggi fortemente tipizzati (come C ++ o Java) a linguaggi tipizzati più deboli o dinamicamente come Python, Ruby o Objective-C. In Objective-C, la maggior parte degli oggetti eredita da NSObject
(tipo id
) (il resto eredita da un'altra classe radice come NSProxy
e può anche essere tipo id
) e qualsiasi messaggio può essere inviato a qualsiasi oggetto. Ovviamente, l'invio di un messaggio a un'istanza che non riconosce può causare un errore di runtime (e causerà anche un avviso del compilatorecon flag -W appropriati). Finché un'istanza risponde al messaggio che invii, potrebbe non interessarti la classe a cui appartiene. Questo viene spesso definito "dattilografia" perché "se ciarlatano come una papera [cioè risponde a un selettore], è una papera [cioè può gestire il messaggio; chi se ne frega di quale classe sia]".
È possibile verificare se un'istanza risponde a un selettore in fase di esecuzione con il -(BOOL)respondsToSelector:(SEL)selector
metodo. Supponendo che tu voglia chiamare un metodo su ogni istanza in un array ma non sei sicuro che tutte le istanze possano gestire il messaggio (quindi non puoi semplicemente usare NSArray
's -[NSArray makeObjectsPerformSelector:]
, qualcosa del genere funzionerebbe:
for(id o in myArray) {
if([o respondsToSelector:@selector(myMethod)]) {
[o myMethod];
}
}
Se controlli il codice sorgente per le istanze che implementano i metodi che desideri chiamare, l'approccio più comune sarebbe definire un @protocol
che contenga quei metodi e dichiarare che le classi in questione implementano quel protocollo nella loro dichiarazione. In questo utilizzo, a @protocol
è analogo a un'interfaccia Java o una classe base astratta C ++. È quindi possibile verificare la conformità all'intero protocollo anziché la risposta a ciascun metodo. Nell'esempio precedente, non farebbe molta differenza, ma se chiamassi più metodi, potrebbe semplificare le cose. L'esempio sarebbe quindi:
for(id o in myArray) {
if([o conformsToProtocol:@protocol(MyProtocol)]) {
[o myMethod];
}
}
assumendo MyProtocol
dichiara myMethod
. Questo secondo approccio è favorito perché chiarisce l'intento del codice più del primo.
Spesso, uno di questi approcci ti libera dal preoccuparti se tutti gli oggetti in un array sono di un determinato tipo. Se ti interessa ancora, l'approccio standard del linguaggio dinamico è di unit test, unit test, unit test. Poiché una regressione in questo requisito produrrà un errore di runtime (probabilmente non recuperabile) (non in fase di compilazione), è necessario disporre di una copertura di test per verificare il comportamento in modo da non rilasciare un crasher in the wild. In questo caso, eseguire un'operazione che modifica l'array, quindi verificare che tutte le istanze dell'array appartengano a una data classe. Con un'adeguata copertura dei test, non è nemmeno necessario il sovraccarico di runtime aggiuntivo per la verifica dell'identità dell'istanza. Hai una buona copertura dei test unitari, vero?
id
eccetto dove necessario, non più di quanto i programmatori Java passino intorno a Object
s. Perchè no? Non ne hai bisogno se hai unit test? Perché è lì e rende il tuo codice più manutenibile, così come gli array digitati. Sembra che le persone abbiano investito nella piattaforma non volendo concedere un punto, e quindi inventando ragioni per cui questa omissione è in realtà un vantaggio.
È possibile creare NSMutableArray
una sottoclasse per applicare l'indipendenza dai tipi.
NSMutableArray
è un cluster di classi , quindi la sottoclasse non è banale. Ho finito per ereditare NSArray
e inoltrare invocazioni a un array all'interno di quella classe. Il risultato è una classe chiamata ConcreteMutableArray
che è facile da sottoclassare. Ecco cosa mi è venuto in mente:
Aggiornamento: controlla questo post del blog di Mike Ash sulla sottoclasse di un cluster di classi.
Includi quei file nel tuo progetto, quindi genera i tipi che desideri utilizzando le macro:
MyArrayTypes.h
CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)
MyArrayTypes.m
CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)
Utilizzo:
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
Altri pensieri
NSArray
per supportare la serializzazione / deserializzazioneA seconda dei tuoi gusti, potresti voler sovrascrivere / nascondere metodi generici come
- (void) addObject:(id)anObject
Dai un'occhiata a https://github.com/tomersh/Objective-C-Generics , un'implementazione generica in fase di compilazione (implementata dal preprocessore) per Objective-C. Questo post del blog ha una bella panoramica. Fondamentalmente si ottiene un controllo in fase di compilazione (avvisi o errori), ma nessuna penalità di runtime per i generici.
Questo progetto Github implementa esattamente quella funzionalità.
Puoi quindi usare le <>
parentesi, proprio come faresti in C #.
Dai loro esempi:
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
Un modo possibile potrebbe essere la sottoclasse di NSArray, ma Apple consiglia di non farlo. È più semplice pensare due volte alla reale necessità di un NSArray tipizzato.
Ho creato una sottoclasse NSArray che utilizza un oggetto NSArray come supporto ivar per evitare problemi con la natura del cluster di classi di NSArray. Sono necessari blocchi per accettare o rifiutare l'aggiunta di un oggetto.
per consentire solo gli oggetti NSString, è possibile definire un AddBlock
as
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
È possibile definire a FailBlock
per decidere cosa fare, se un elemento ha fallito il test - fallisce regolarmente per il filtraggio, aggiungerlo a un altro array, o - questa è l'impostazione predefinita - sollevare un'eccezione.
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
Usalo come:
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);
Questo è solo un codice di esempio e non è mai stato utilizzato in applicazioni del mondo reale. per farlo è probabilmente necessario implementare il metodo NSArray mor.
Se mescoli c ++ e goal-c (cioè usando il tipo di file mm), puoi forzare la digitazione usando coppia o tupla. Ad esempio, nel seguente metodo, è possibile creare un oggetto C ++ di tipo std :: pair, convertirlo in un oggetto di tipo wrapper OC (wrapper di std :: pair che è necessario definire) e quindi passarlo ad alcuni altro metodo OC, all'interno del quale è necessario riconvertire l'oggetto OC in oggetto C ++ per poterlo utilizzare. Il metodo OC accetta solo il tipo di wrapper OC, garantendo così l'indipendenza dal tipo. Puoi anche usare tuple, modelli variadici, typelist per sfruttare funzionalità C ++ più avanzate per facilitare la sicurezza dei tipi.
- (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];
}