LXFoundation for Cocoa programmers

做为一个 OSX 和 iOS 软件开发者,使用 Objective-C 和 Cocoa 开发应该算是王道。尽管 Apple 为开发者提供了一系列方便的类和函数,但是绝对所有 Cocoa 开发者都遇到过这样让人头痛的问题:

*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
*** setObjectForKey: object cannot be nil (key: username)

Cocoa 自带的容器 NSMutableArray 和 NSMutableDictionary 总是无法强壮的处理一些不正确的值。结果往往是导致程序轻则异常,重则崩溃。而更为难的是,当程序变得庞杂起来的时候,xcode 的 debugger 很难定位这样一个错误。

下面我在 GitHub 上发布一个我自己写的开源库 LXFoundation 目标就是解决这类问题。我已经把它用在了自己的软件中,目前表现稳定。

项目地址

https://github.com/keefo/LXFoundation

详细介绍

从 Class Clusters 说起

在 Cocoa 中有一种奇葩的类存在,有程序员抨击它是 OOP 模式的破坏者,这就是 Class Clusters。面向对象的编程教育我们:“类可以继承,子类具有父类的方法”。而 Cocoa 中的 Class Clusters 虽然平时表现的像普通类一样,但子类却没法继承父类的方法。而 NSMutableArray, NSMutableDictionary 就是这样一个玩意。

为何如此?因为 Class Clusters 内部其实是由多个私有的类和方法组成。虽然它有这样的弊端,但是好处还是不言而喻的。例如,NSNumber 其实也是这种类,这样一个类可以把各种不同的原始类型封装到一个类下面,提供统一的接口。这正设计模式中的抽象工厂模式。

正因为它的特殊性,所以你经常会在 Cocoa 开发论坛之类的地方,看到很多程序员建议,最好不要自己 subclass NSMutableDictionary 或者 NSMutableArray,而尽量使用 Category。但是实际上 category 并不能处理本身类方法的异常抛出。并且除了增加功能外,无法带来调试方面的优势。 这就是为何我也写 LXFoundation 的原因。写自己的容器可以强壮代码,同时便于调试。

如何 Subclass Class Clusters

说了上面这么多,那么到底要如何 subclass 这种类呢? Apple 文档里指明了,subclass 这种类,程序员需要自己实现一系列称为 primitive methods 的函数。这些函数就是构成这种类的必要单元。例如,NSMutableDictionary 文档提到,它的 primitive methods 包括:

setObject:forKey:
removeObjectForKey:

而 NSMutableArray 的 primitive methods 包括:

insertObject:atIndex:
removeObjectAtIndex:
addObject:
removeLastObject
replaceObjectAtIndex:withObject:

这里我就不再继续写实现细节了,如果你已经感兴趣了,请到github直接阅读源代码和实例。

LXFoundation 的特性

LXFoundation 可以处理异常数据的插入,修改等操作,不会抛出异常 crash 你的程序。并且它在 arc 或者 non-arc 环境下都运行良好。其性能和 NSMutableDictionary, NSMutableArray 基本一致。

更妙的一个好处是,使用 LXFoundation 你不需要修改自己的一行代码。只要在 prefix-pch 里加几行宏指令即可:

#import "LXFoundation.h"
#define NSMutableArray LXMutableArray
#define NSMutableDictionary LXMutableDictionary

这几句宏指令,自动帮你把代码内的容器替换成 LX 前缀,而在你不需要 LXFoundation 的时候,删除掉这几句即可,你的程序就又回到了原来的 NS-Container 模式了。

使用前的注意事项

目前为止,我只能想到1个使用前的注意事项。当你的代码中有地方是需要 NSMutableArray,NSMutableDictionary 抛出异常才能正确工作的时候,用 LXFoundation 就需要更加谨慎。

例如,考虑如下代码,如果你希望有时候 exception 抛出并且要在 catch block 里处理一些重要逻辑,那么 LXFoundation 将无法融入你的代码。

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
@try {
    [dict setObject:nil forKey:@"k1"];
}
@catch (NSException *exception) {
    //did something important
}
@finally {

}

未来计划

目前 LXFoundation 只包含2个最基本的类。今后随着我的程序需要,我会陆续为其增加必要容器实现。