2009-09-22 13 views
17

Heute habe ich mit Objective-C Blöcke experimentierte so dass ich dachte, dass ich klug wäre, und fügen Sie ein paar funktionalen Stil Sammelmethoden NSArray, die ich in anderen Sprachen gesehen habe:mit Objective-C-Blöcke

@interface NSArray (FunWithBlocks) 
- (NSArray *)collect:(id (^)(id obj))block; 
- (NSArray *)select:(BOOL (^)(id obj))block; 
- (NSArray *)flattenedArray; 
@end 

Die Methode collect: nimmt einen Block, der für jedes Element im Array aufgerufen wird und erwartet, dass die Ergebnisse einer Operation zurückgegeben werden, die dieses Element verwendet. Das Ergebnis ist die Sammlung all dieser Ergebnisse. (Wenn der Block null zurückgibt, wird der Ergebnismenge nichts hinzugefügt.)

Die select: -Methode gibt ein neues Array mit nur den Elementen aus dem Original zurück, das zurückgegeben wurde, wenn es als Argument an den Block übergeben wurde JA.

Und schließlich, die flattenedArray-Methode iteriert über die Elemente des Arrays. Wenn ein Element ein Array ist, ruft es flatterndArray rekursiv darauf auf und fügt die Ergebnisse zur Ergebnismenge hinzu. Wenn das Element kein Array ist, fügt es das Element zur Ergebnismenge hinzu. Die Ergebnismenge wird zurückgegeben, wenn alles fertig ist.

Jetzt, wo ich eine Infrastruktur hatte, brauchte ich einen Testfall. Ich beschloss, alle Paketdateien in den Anwendungsverzeichnissen des Systems zu finden. Dies ist, was ich mit kam:

Ja - das ist alles eine Zeile und es ist schrecklich. Ich versuchte ein paar Ansätze, Zeilenumbrüche und Einrückungen hinzuzufügen, um zu versuchen, es zu bereinigen, aber es fühlt sich immer noch so an, als ob der tatsächliche Algorithmus in all dem Rauschen verloren gegangen wäre. Ich weiß nicht, ob es nur eine Syntax-Sache oder meine relative In-Erfahrung mit der Verwendung eines funktionalen Stils ist, aber das ist das Problem.

Zum Vergleich habe ich beschlossen, es „auf die altmodische Art und Weise“ zu tun und nur Loops verwenden:

NSMutableArray *packagePaths = [NSMutableArray new]; 
for (NSString *searchPath in NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES)) { 
    for (NSString *file in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:searchPath error:nil]) { 
     NSString *packagePath = [searchPath stringByAppendingPathComponent:file]; 
     if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:packagePath]) { 
      [packagePaths addObject:packagePath]; 
     } 
    } 
} 

IMO diese Version war einfacher zu schreiben und ist besser lesbar zu booten.

Ich nehme an, es ist möglich, dass dies ein schlechtes Beispiel war, aber es scheint eine legitime Möglichkeit zu sein, Blöcke für mich zu verwenden. (Bin ich falsch?) Fehle ich etwas über das Schreiben oder Strukturieren von Objective-C-Code mit Blöcken, die dies aufklären und es klarer machen würden (oder auch nur so klar wie) die geloopte Version?

Antwort

19

Verwenden Sie Zeilenumbrüche und brechen Sie Ihren Anruf über mehrere Leitungen auf.

Das Standardmuster, das in allen APIs von Apple verwendet wird, ist, dass eine Methode oder Funktion nur ein Blockargument verwenden sollte und dieses Argument immer das letzte Argument sein sollte.

Was Sie getan haben. Gut.

Nun, wenn Sie den Code zu schreiben, die API sagte verwendet, so etwas wie:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES); 
paths = [paths collect: ^(id path) { 
    ... 
}]; 
paths = [paths collect: ^(id path) { 
    ... 
}]; 
paths = [paths select: ^(id path) { 
    ... 
}]; 

D.h. Mache jeden Schritt deines Sammelns/Auswählens/Filterns/Abflachens/Planens/was auch immer als separaten Schritt. Dies ist nicht schneller/langsamer als verkettete Methodenaufrufe.

Wenn Sie nisten Blöcke in der Seite des Blocks brauchen, dann tun dies mit voller inden:

paths = [paths collect: ^(id path) { 
    ... 
    [someArray select:^(id path) { 
     ... 
    }]; 
}]; 

Genau wie verschachtelte if-Anweisungen oder dergleichen. Wenn es zu komplex wird, refaktorieren Sie es nach Bedarf in Funktionen oder Methoden.

+1

Nizza Lösung, obwohl ich bin kein großer Fan von Neudefinition 'paths' mehrmals. Ich würde wahrscheinlich am Ende mehrere Werte basierend auf ihrem Inhalt benennen und am Ende mit einem namens "Pfade" enden. Nur meine zwei Cent. –

+0

UIView-Animation verwendet 2 Blöcke. –

+0

Er sagte Muster nicht Kanon. Diese Methoden, die mehrere Blöcke verwenden, haben alle Blöcke als letzte Parameter. Darüber hinaus erschienen diese APIs in iOS 4, das lange nach diesem Post war. – logancautrell

2

Ich denke, das Problem ist, dass (im Gegensatz zu dem, was Kritiker von Python behaupten;) Leerraum zählt. In einem funktionaleren Stil scheint es sinnvoll, den Stil anderer funktionaler Sprachen zu kopieren. Je mehr LISP-y Weg, um Ihr Beispiel schreiben könnte etwas sein wie:

NSArray *packagePaths = [[[NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES) 
          collect:^(id path) { 
             return [[[NSFileManager defaultManager] 
               contentsOfDirectoryAtPath:path 
                    error:nil] 
               collect:^(id file) { 
                 return [path stringByAppendingPathComponent:file]; 
                 } 
              ]; 
            } 
          ] 
          flattenedArray 
          ] 

          select:^(id fullPath) { 
            return [[NSWorkspace sharedWorkspace] isFilePackageAtPath:fullPath]; 
           } 
         ]; 

würde ich nicht sagen, dass dies klarer als die geschlungenen Version. Wie jedes andere Werkzeug sind Blöcke ein Werkzeug, und sie sollten nur verwendet werden, wenn sie das geeignete Werkzeug für den Job sind. Wenn Lesbarkeit leidet, würde ich sagen, dass es nicht das beste Werkzeug für den Job ist. Blöcke sind schließlich eine Ergänzung zu einer fundamental imperativen Sprache. Wenn Sie die Prägnanz einer funktionalen Sprache wirklich wollen, verwenden Sie eine funktionale Sprache.