2009-09-22 16 views
17

Dzisiaj byłem eksperymentowanie z bloków cel C, tak myślałem, że będę sprytny i dodać do NSArray kilka metod gromadzenia funkcjonalnym stylu, które widziałem w innych językach:Korzystanie z Objective-C Bloki

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

Metoda collect: pobiera blok, który jest wywoływany dla każdego elementu w tablicy i oczekuje się, że zwróci wyniki operacji wykonanej przy użyciu tego elementu. Rezultatem jest zbieranie wszystkich tych wyników. (Jeśli blok zwróci zero, nic nie zostanie dodane do zestawu wyników).

Metoda select: zwróci nową tablicę zawierającą tylko elementy z oryginału, które po przekazaniu jako argument do bloku zwrócą blok TAK.

I wreszcie, metoda flattenedArray iteruje po elementach tablicy. Jeśli element jest tablicą, rekursywnie wywołuje na nim wartość spłaszczoną i dodaje wyniki do zestawu wyników. Jeśli element nie jest tablicą, dodaje element do zestawu wyników. Zestaw wyników jest zwracany, gdy wszystko jest gotowe.

Teraz, gdy miałem trochę infrastruktury, potrzebowałem testu. Postanowiłem znaleźć wszystkie pliki pakietów w katalogach aplikacji systemu. Oto, co wymyśliłem:

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

Tak - to wszystko jedna linia i jest okropna. Próbowałem kilku podejść do dodawania nowych linii i wcięć, aby spróbować je wyczyścić, ale wciąż wydaje się, że rzeczywisty algorytm jest tracony w całym hałasie. Nie wiem, czy to tylko kwestia składni, czy moje względne doświadczenie w stosowaniu stylu funkcjonalnego, który jest problemem.

Dla porównania, postanowiłem zrobić „staroświecki sposób” i po prostu użyć pętli:

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 ta wersja była łatwiej napisać i jest bardziej czytelny, aby uruchomić.

Przypuszczam, że to możliwe, że był to zły przykład, ale wydaje mi się to uzasadnionym sposobem wykorzystania bloków. (Czy się mylę?) Czy brakuje mi czegoś o tym, jak napisać lub skonstruować kod Objective-C za pomocą bloków, które oczyściłyby ten tekst i uczyniły go jaśniejszym niż (lub nawet tak czystym, jak) zapętloną wersją?

Odpowiedz

19

Używaj nowych linii i rozbijaj połączenia na wiele linii.

Standardowy wzór stosowany we wszystkich interfejsach API Apple'a polega na tym, że metoda lub funkcja powinna przyjmować tylko jeden argument bloku, a ten argument powinien być zawsze ostatnim argumentem.

Co uczyniliście. Dobry.

Teraz podczas pisania kodu, który wykorzystuje wspomniany API, zrobić coś takiego:

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

Tj wykonaj każdy krok swojego zebrania/wybierz/filtruj/spłaszczaj/mapuj/cokolwiek w oddzielnym kroku. Nie będzie to szybsze/wolniejsze od połączonych wywołań metod.

Jeśli trzeba bloków gniazdo na stronie bloków, a następnie zrobić z pełną wcięcie:

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

Podobnie jak zagnieżdżonych instrukcji if lub podobne. Kiedy staje się zbyt skomplikowana, w razie potrzeby przekształć ją w funkcje lub metody.

+1

Nicea rozwiązanie, choć nie jestem wielkim fanem redefinicji 'paths' kilkakrotnie. Prawdopodobnie skończyłbym nazywaniem kilku wartości na podstawie ich zawartości, a skończyłbym na jednym o nazwie 'paths'. Tylko moje dwa centy. –

+0

Animacja UIView używa 2 bloków. –

+0

Powiedział, że wzór nie jest kanonem. Te metody, które pobierają wiele bloków, mają bloki jako parametry końcowe. Dodatkowo te API pojawiły się w iOS 4, który był długo po tym wpisie. – logancautrell

2

Myślę, że problem polega na tym, że (w przeciwieństwie do tego, co twierdzą krytycy Pythona;) biała przestrzeń ma znaczenie. W bardziej funkcjonalnym stylu wydaje się, że byłoby sensowne kopiowanie stylu innych języków funkcjonalnych. Im więcej LISP-y sposób napisać przykładem może być coś takiego:

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

Nie powiedziałbym, że jest ona bardziej czytelna niż wersja pętlą. Podobnie jak inne narzędzia, bloki są narzędziem i powinny być używane tylko wtedy, gdy są odpowiednim narzędziem do pracy. Jeśli cierpi na to czytelność, powiedziałbym, że nie jest to najlepsze narzędzie do tej pracy. Bloki są ostatecznie dodatkiem do zasadniczo imperatywnego języka. Jeśli naprawdę chcesz zwięzłości języka funkcjonalnego, użyj języka funkcjonalnego.