C'è un modo per imporre la digitazione su NSArray, NSMutableArray, ecc.?


Risposte:


35

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.


136
Penso che i "programmatori Cocoa esperti" non sappiano cosa si stanno perdendo: l'esperienza con Java mostra che le variabili di tipo migliorano la comprensione del codice e rendono possibili più refactoring.
tgdavies

11
Bene, il supporto di Java Generics è pesantemente rotto di per sé, perché non lo hanno inserito dall'inizio ...
dertoni

28
Devo essere d'accordo con @tgdavies. Mi mancano le capacità intellisense e di refactoring che avevo con C #. Quando voglio la digitazione dinamica, posso ottenerla in C # 4.0. Quando voglio qualcosa di fortemente tipografico posso avere anche quello. Ho scoperto che c'è un tempo e un luogo per entrambe queste cose.
Steve

18
@charkrit Di cosa si tratta Objective-C che lo rende "non necessario"? Hai sentito che era necessario quando stavi usando C #? Ho sentito molte persone dire che non ne hai bisogno in Objective-C ma penso che queste stesse persone pensano che non ne hai bisogno in nessuna lingua, il che lo rende una questione di preferenza / stile, non di necessità.
bacar

17
Non si tratta di consentire al compilatore di aiutarti effettivamente a trovare i problemi. Sicuramente puoi dire "Se vuoi solo oggetti di una data classe in un array, incolla lì solo oggetti di quella classe". Ma se i test sono l'unico modo per imporlo, sei in svantaggio. Più ci si allontana dalla scrittura del codice per trovare un problema, più il problema è costoso.
GreenKiwi

145

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.


Sono pigro qui, ma perché è disponibile solo in XCode 7? Possiamo usare nonnullin 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?
Guven

@Guven - nullability è arrivato in 6, hai ragione, ma i generici ObjC non sono stati introdotti fino a Xcode 7.
Logan

Sono abbastanza sicuro che dipenda solo dalla versione di Xcode. I generici sono solo avvisi del compilatore e non vengono indicati in fase di esecuzione. Sono abbastanza sicuro che potresti compilare tutto ciò che vuoi.
Logan

2
@ DeanKelly - Potresti farlo in questo modo: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; sembra un po 'goffo, ma fa il trucco!
Logan

1
@Logan, non c'è solo il set di script, che impediscono la creazione in caso di avviso rilevato. Xcode ha un meccanismo perfetto chiamato "Configurazione". Dai
adnako

53

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 NSProxye 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)selectormetodo. 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 @protocolche 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 MyProtocoldichiara 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?


35
I test unitari non sostituiscono un sistema di tipi decente.
confermare il

8
Sì, chi ha bisogno degli strumenti che gli array digitati si permetterebbero. Sono sicuro che @BarryWark (e chiunque altro abbia toccato qualsiasi base di codice che ha bisogno di usare, leggere, comprendere e supportare) ha una copertura del codice al 100%. Tuttavia scommetto che non usi i raw ideccetto dove necessario, non più di quanto i programmatori Java passino intorno a Objects. 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.
funkybro

"Duck typing" ?? è divertente! non lo avevo mai sentito prima.
John Henckel

11

È possibile creare NSMutableArrayuna sottoclasse per applicare l'indipendenza dai tipi.

NSMutableArrayè un cluster di classi , quindi la sottoclasse non è banale. Ho finito per ereditare NSArraye inoltrare invocazioni a un array all'interno di quella classe. Il risultato è una classe chiamata ConcreteMutableArrayche è 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

  • Eredita da NSArrayper supportare la serializzazione / deserializzazione
  • A seconda dei tuoi gusti, potresti voler sovrascrivere / nascondere metodi generici come

    - (void) addObject:(id)anObject


Bello ma per ora manca una digitazione forte sovrascrivendo alcuni metodi. Attualmente è solo una digitazione debole.
Cœur

7

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.


1
L'ho provato, ottima idea, ma purtroppo buggy e non controlla gli elementi aggiunti.
Binarian

4

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

0

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.


1
Risparmia tempo per avere il controllo statico del tipo in fase di compilazione, l'editing è ancora meglio. Particolarmente utile quando si scrive lib per un utilizzo a lungo termine.
pinxue

0

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 AddBlockas

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

È possibile definire a FailBlockper 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.


0

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];
}

0

i miei due centesimi per essere un po '"puliti":

usa typedef:

typedef NSArray<NSString *> StringArray;

in codice possiamo fare:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.