2011-01-02 29 views
9

Il mio obiettivo è disporre di un array che contenga tutti i nomi di file di un'estensione specifica, ma senza l'estensione.Esiste un algoritmo Objective-C come `transform` di C++ STL?

C'è un elegant solution to get all filenames of a specific extension utilizzando un filtro dei predicati e instructions on how to split a path into filename and extension, ma per combinarli dovrei scrivere un ciclo (non terribile, ma non elegante neanche).

C'è un modo con Objective-C (potrebbe essere simile al meccanismo del predicato) per applicare qualche funzione a ogni elemento di un array e mettere i risultati in un secondo array, come l'algoritmo transform del C++ STL?

Quello che mi piacerebbe scrivere:

// let's pretend 'anArray' was filled by querying the filesystem and not hardcoded 
NSArray* anArray = [[NSArray alloc] initWithObjects:@"one.ext", @"two.ext", nil]; 

// that's what I liked to write (pseudo code) 
NSArray* transformed = [anArray transform: stringByDeletingPathExtension]; 

// Yuji's answer below proposes this (which may be as close as you can get 
// to my wish with Objective C) 
NSArray* transformed = [anArray my_arrayByApplyingBlock:^(id x){ 
          return [x stringByDeletingPathExtension]; 
         }]; 
+2

considerando tutti 'transform' davvero è loop sugli elementi, non sto davvero vedendo l'ineleganza di farlo da soli ... –

+0

vuoi per farlo sul posto o creare un nuovo array? Hai anche bisogno di rimuovere percorsi per i file? I dati di esempio (che mostrano gli elementi iniziali dell'array e ciò che si desidera siano i risultati) sarebbero utili. La cosa migliore quando si scrive una domanda è di fornire una grande quantità di informazioni, pur restando concise. – outis

+1

@Nicholas: si tratta di livelli di astrazione e manutenibilità più elevati. Quando puoi esprimere la tua intenzione su una riga e non devi scrivere (o copiare/incollare) lo stesso ciclo più volte, il codice è più facile da capire e mantenere. Se la singola linea è sufficientemente avanzata (a.k.a troppo intelligente per il programmatore medio), allora preferirei fare il ciclo anche io. – pesche

risposta

9

questo è un argomento chiamato Higher Order Messaging in Cocoa, e sviluppato da molte persone sul web. Inizia da here e prova a cercare su Google di più. Essi aggiungono un metodo categoria NSArray in modo che si può fare

NSArray*transformed=[[anArray map] stringByDeletingPathExtension]; 

L'idea è la seguente:

  • [anArray map] crea un oggetto temporaneo (diciamo hom)
  • hom riceve il messaggio stringByDeletingPathExtension
  • hom invia nuovamente il messaggio a tutti gli elementi di anArray
  • hom raccoglie i risultati e restituisce l'array risultante.

Se si desidera solo una rapida trasformazione, che definirei un metodo categoria:

@interface NSArray (myTransformingAddition) 
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block; 
@end 

@implementation NSArray (myTransformingAddition) 
-(NSArray*)my_arrayByApplyingBlock:(id(^)(id))block{ 
    NSMutableArray*result=[NSMutableArray array]; 
    for(id x in self){ 
      [result addObject:block(x)]; 
    } 
    return result; 
} 
@end 

allora si può fare

NSArray* transformed=[anArray my_arrayByApplyingBlock:^id(id x){return [x stringByDeletingPathExtension];}]; 

Nota il costrutto ^ return-type (arguments) { ...} che crea un blocco. Il tipo di ritorno può essere omesso e clang è abbastanza intelligente per indovinarlo, ma lo gcc è piuttosto rigido e deve essere specificato qualche volta. (In questo caso, è indovinato dalla dichiarazione return che ha [x stringBy...] che restituisce un NSString*. Così GCC indovina il tipo di ritorno del blocco di essere NSString* invece di id, che GCC pensa incompatibile, quindi arriva l'errore.)

Su OS X Leopard o iOS 3, è possibile utilizzare PLBlocks per supportare i blocchi. La mia personale opinione soggettiva è che le persone che si preoccupano del nuovo software in genere eseguono l'aggiornamento al nuovo sistema operativo, quindi supportare l'ultimo sistema operativo dovrebbe essere perfetto; supportare un vecchio sistema operativo non aumenterà il tuo cliente di un fattore di due ...

CHE HA DATO, c'è già un bel framework open-source che fa tutto ciò che ho detto sopra. Vedere la discussione here e in particolare lo FunctionalKit collegato lì.


Più oltre: è, infatti, facile da realizzare il vostro pseudocodice [array transform:stringByDeletingPathExtension].

@interface NSArray (myTransformingAddition) 
-(NSArray*)my_transformUsingSelector:(SEL)sel; 
@end 

@implementation NSArray (myTransformingAddition) 
-(NSArray*)my_transformUsingSelector:(SEL)sel;{ 
    NSMutableArray*result=[NSMutableArray array]; 
    for(id x in self){ 
      [result addObject:[x performSelector:sel withObject:nil]]; 
    } 
    return result; 
} 
@end 

Quindi è possibile utilizzare come segue:

NSArray*transformed=[array my_transformUsingSelector:@selector(stringByDeletingPathExtension)]; 

Tuttavia non mi piace così tanto; è necessario disporre di un metodo già definito sull'oggetto nell'array per utilizzare questo metodo. Ad esempio, se NSString non ha l'operazione cosa vuoi fare come metodo, cosa faresti in questo caso? È necessario aggiungere un primo momento ad NSString tramite una categoria:

@interface NSString (myHack) 
-(NSString*)my_NiceTransformation; 
@end 

@implementation NSString (myHack) 
-(NSString*)my_NiceTransformation{ 
    ... computes the return value from self ... 
    return something; 
} 
@end 

quindi è possibile utilizzare

NSArray*transformed=[array my_transformUsingSelector:@selector(my_NiceTransformation)]; 

ma tende ad essere molto prolisso, perché è necessario definire il metodo in altri posti prima. Io preferisco fornendo quello che voglio operare direttamente presso il sito di chiamata, come in

NSArray*transformed=[array my_arrayByApplyingBlock:^id(id x){ 
    ... computes the return value from x ... 
    return something;  
}]; 

Infine, i metodi categoria mai aggiuntivi che non iniziano con un prefisso come my_ o qualsiasi altra cosa. Ad esempio, in futuro Apple potrebbe fornire un buon metodo chiamato transform che fa esattamente quello che vuoi. Ma se hai già un metodo chiamato transform nella categoria, questo porterà a un comportamento indefinito. In effetti, può succedere che ci sia già un metodo privato di Apple nella classe.

+0

Grazie per avermi presentato ai blocchi Objective-C. Non ho mai visto questa faccenda fino ad ora ... Quando provo ad usare il tuo codice (aggiunto come modifica alla domanda), ottengo il seguente errore del compilatore: 'Tipi di puntatori a blocchi incompatibili che inizializzano 'struct NSString * (^) (struct objc_object *) ', previsto' struct objc_object * (^) (struct objc_object *) ''. Non ho ancora imparato i blocchi abbastanza da sapere cosa c'è che non va. – pesche

+0

La [documentazione di Apple sui blocchi] (http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html) afferma che i blocchi possono essere utilizzati su iOS 4 e versioni successive. Cosa succede quando provo a eseguire l'app con iOS 3.1.x? – pesche

+0

Ah, mi dispiace per quello. Vedi la risposta editata. – Yuji

11

In realtà, esiste un modo molto semplice. È in circolazione dal 2003 ed è mal chiamato.

NSArray *array = [NSArray arrayWithObjects:@"one.ext", @"two.ext", nil]; 

// short solution 
NSArray *transformed = [array valueForKey:@"stringByDeletingPathExtension"]; 

// long solution (more robust against refactoring) 
NSString *key = NSStringFromSelector(@selector(stringByDeletingPathExtension)); 
NSArray *transformed = [array valueForKey:key]; 

Entrambi producono l'output:

(
    one, 
    two 
) 
Problemi correlati