Использование блоков Objective-C
Сегодня я экспериментировал с блоками Objective-C, поэтому я подумал, что буду умным и добавлю в NSArray несколько методов создания в стиле функционального стиля, которые я видел на других языках:
@interface NSArray (FunWithBlocks)
- (NSArray *)collect:(id (^)(id obj))block;
- (NSArray *)select:(BOOL (^)(id obj))block;
- (NSArray *)flattenedArray;
@end
Метод collect: принимает блок, который вызывается для каждого элемента массива, и ожидается, что он вернет результаты некоторой операции с использованием этого элемента. Результатом является сбор всех этих результатов. (Если блок возвращает nil, в результирующий набор ничего не добавляется.)
Метод select: возвращает новый массив только с элементами из оригинала, который, переданный в качестве аргумента блоку, возвращает ответ.
И, наконец, метод flattenedArray выполняет итерацию над элементами массива. Если элемент является массивом, он рекурсивно вызывает flattenedArray и добавляет результаты в результирующий набор. Если элемент не является массивом, он добавляет элемент в результирующий набор. Набор результатов возвращается, когда все будет завершено.
Итак, теперь, когда у меня была какая-то инфраструктура, мне нужен тестовый пример. Я решил найти все файлы пакетов в каталогах системных приложений. Вот что я придумал:
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]; }];
Да, это вся одна линия, и это ужасно. Я попробовал несколько подходов при добавлении новых строк и отступов, чтобы попытаться его очистить, но по-прежнему кажется, что фактический алгоритм теряется во всем шуме. Я не знаю, если это просто синтаксис или мой родственник в опыте с использованием функционального стиля, который проблема, однако.
Для сравнения, я решил сделать это "старомодным способом" и просто использовать циклы:
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, эта версия была легче писать и более читаема для загрузки.
Я полагаю, это возможно, это был как-то плохой пример, но это похоже на законный способ использовать блоки для меня. (Я не прав?) Я что-то пропустил о том, как писать или структурировать код Objective-C с блоками, которые очистят его и станут понятнее, чем (или даже так же ясно, как) зацикленная версия?
Ответы
Ответ 1
Используйте новые строки и разбивайте свой вызов на несколько строк.
Стандартный шаблон, используемый во всех API Apple, заключается в том, что метод или функция должны принимать только один аргумент блока, и этот аргумент всегда должен быть последним аргументом.
Что вы сделали. Хорошо.
Теперь, когда вы пишете код, который использует указанный API, сделайте что-то вроде:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES);
paths = [paths collect: ^(id path) {
...
}];
paths = [paths collect: ^(id path) {
...
}];
paths = [paths select: ^(id path) {
...
}];
т.е. сделайте каждый шаг вашего сбора/выбора/фильтрации/сглаживания/карты/как отдельного шага. Это будет не быстрее/медленнее, чем вызовы с цепочкой вызовов.
Если вам нужно вложить блоки в сторону блоков, сделайте это с полным отступлением:
paths = [paths collect: ^(id path) {
...
[someArray select:^(id path) {
...
}];
}];
Точно так же, как вложенные операторы if или тому подобное. Когда он становится слишком сложным, реорганизуйте его в функции или методы, если это необходимо.
Ответ 2
Я думаю, что проблема в том, что (вопреки тому, что критики претензии Python;) имеет значение пустое пространство. В более функциональном стиле кажется, что имеет смысл копировать стиль других функциональных языков. Более LISP -y способ написать ваш пример может быть примерно так:
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];
}
];
Я бы не сказал, что это яснее, чем зацикленная версия. Как и любой другой инструмент, блоки являются инструментом, и их следует использовать только тогда, когда они являются подходящим инструментом для работы. Если читаемость страдает, я бы сказал, что это не лучший инструмент для работы. Блоки, после всего, являются дополнением к принципиально императивному языку. Если вы действительно хотите краткость функционального языка, используйте функциональный язык.
Ответ 3
Похоже, вы повторно изобретаете сообщения высокого порядка. Марсель Вейер провел обширную работу над HOM в Objective-C, которую вы можете найти здесь:
http://www.metaobject.com/blog/labels/Higher%20Order%20Messaging.html