使用Objective-C Blocks

17
今天我在尝试使用Objective-C的块(Block),所以我想自己写一些函数式集合方法,来扩展NSArray,这些方法在其他语言中都有看到:
@interface NSArray (FunWithBlocks)
- (NSArray *)collect:(id (^)(id obj))block;
- (NSArray *)select:(BOOL (^)(id obj))block;
- (NSArray *)flattenedArray;
@end
collect: 方法以一个 block 作为参数,对数组中的每个元素调用该 block,并期望使用该元素执行某些操作并返回结果。结果是所有这些结果的集合。(如果 block 返回 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];
        }
    }
}

在我看来,这个版本更易于编写,同时也更容易阅读。

我想这可能是一个不好的例子,但在我看来,这似乎是使用块的合法方式。 (我错了吗?) 我是否忽略了如何使用块来编写或组织Objective-C代码以清除并使其比循环版本更清晰的内容?

3个回答

19

使用换行符并将调用跨多行进行拆分。

在所有苹果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
不错的解决方案,虽然我不太喜欢多次重新定义“paths”。我可能会根据它们的内容命名几个值,并最终得到一个叫做“paths”的值。这只是我的个人看法。 - Jonathan Sterling
UIView动画使用2个块。 - Jonathan.
他说模式不是正统。那些需要多个块的方法都将块作为最后一个参数。此外,这些API出现在iOS 4中,这比这篇文章晚得多。 - logancautrell

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

我不认为这比循环版本更清晰。像其他工具一样,块是一种工具,只有在它们是适当的工具时才应该使用它们。如果可读性受到影响,我会说这不是最好的工具。毕竟,块是基本命令式语言的补充。如果你真的想要函数式语言的简洁性,那就使用函数式语言。


0

2
不,HOM与此非常不同。但可能会引起兴趣。 - bbum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接