今天我正在尝试使用Objective-C的块,所以我觉得我很聪明,并在NSArray中添加了一些我在其他语言中看到的功能样式的收集方法:
@interface NSArray (FunWithBlocks)
- (NSArray *)collect:(id (^)(id obj))block;
- (NSArray *)select:(BOOL (^)(id obj))block;
- (NSArray *)flattenedArray;
@end
collect:方法接受一个为数组中的每个项调用的块,并期望使用该项返回某些操作的结果。结果是收集所有这些结果。 (如果块返回nil,则不会向结果集添加任何内容。)
select:方法将返回一个新数组,其中只包含原始项目,当作为参数传递给块时,块返回YES。
最后,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代码的东西,这些块可以清理它并使其比循环版本更清晰(甚至更清晰)?
答案 0 :(得分:19)
使用换行符并在多行中分手。
所有Apple的API使用的标准模式是方法或函数应该只接受一个块参数,并且该参数应该始终是最后一个参数。
你做过的。好。
现在,在编写使用所述API的代码时,请执行以下操作:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSAllApplicationsDirectory, NSAllDomainsMask, YES);
paths = [paths collect: ^(id path) {
...
}];
paths = [paths collect: ^(id path) {
...
}];
paths = [paths select: ^(id path) {
...
}];
即。将collect / select / filter / flatten / map /的每一步做为单独的步骤。这将不会比链式方法调用更快/更慢。
如果你确实需要在块的一侧嵌套块,那么在完全缩进的情况下这样做:
paths = [paths collect: ^(id path) {
...
[someArray select:^(id path) {
...
}];
}];
就像嵌套的if语句之类。当它变得太复杂时,根据需要将其重构为函数或方法。
答案 1 :(得分: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];
}
];
我不会说这比循环版本更清晰。与任何其他工具一样,块是一种工具,只有在它们是适合工作的工具时才应使用它们。如果可读性受到影响,我会说这不是最好的工具。块,毕竟是对基本命令语言的补充。如果您真的想要功能语言的简洁性,请使用函数式语言。
答案 2 :(得分:0)
看起来你正在重新发明高阶信息。 Marcel Weiher在Objective-C上做了很多关于HOM的工作,你可以在这里找到:
http://www.metaobject.com/blog/labels/Higher%20Order%20Messaging.html