使用Objective-C块

时间:2009-09-22 19:55:36

标签: objective-c coding-style

今天我正在尝试使用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代码的东西,这些块可以清理它并使其比循环版本更清晰(甚至更清晰)?

3 个答案:

答案 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