聊一下Objective-C中的id类型

2,479 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

这一篇文章应该算是上一篇聊一下Objective-C中的泛型的扩展衍生。

你或许早已意识到id类型它的妙处,在不知不觉中已经将它用的炉火纯青,或许我写的这些内容也不算非常新的发现。

让大家见笑了。

用id去传递参数

不知道大家用过OC的响应式框架没有,没错就是ReactiveCocoa

虽然现在的ReactiveCocoa已经用Swift重写了,但是在3.0之前的版本,它都是用OC编写的。

我们来看一个比较经典的API对比,就是ReactiveCocoa中subscribe方法RxSwift中subscribe函数

ReactiveCocoa中subscribeRxSwift中subscribe函数
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock;public func subscribe(onNext: ((Element) -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil) -> Disposable

这两个方法都是说明序列订阅next、error、completed的处理情况。

其中在ReactiveCocoa中,针对next中的传值使用的是id表示,而在RxSwift中使用的是泛型占位符Element表示。

id表示任何一个动态的NSObject对象,在几乎万物都为对象的Cococa框架中,如此传值,基本上解决了方法中对于参数类型的定义。

框架的API设计原则就是尽量抽象并统和。

我们在实际使用ReactiveCocoa中subscribe函数的时候,一般都会将这个id类型,自行转为具体的对象类型,就像下面这样的伪代码:

[[Http Request] subscribeNext:^(NSString *personId) {

} error:^(NSError *error) {

}];

在上面的[Http Request]方法是一个网络请求返回一个序列,而后被订阅,这个时候subscribeNext:(void (^)(id x))nextBlock就被向下转为subscribeNext:(void (^)(NSString * x))nextBlock,这个就是OC中向下转具体对象的操作。

这里的代码特别向Swift通过as关键词将Any转为具体类型一样:

HttpRequest().subscribe(onNext: { id in

    guard let some = id as? String else {

        return

    }

}, onError: { error in

}).disposed(by: rx.disposeBag)

当然,在Swift中我们有泛型约束,传递的数据类型早就被约定好了。

然而在OC中,id到具体类型的转换,有一定为危险性,一旦类型弄错了,程序在读取值的时候就崩溃了。

这也是我比较讨厌接手OC中使用ReactiveCocoa框架的项目原因:因为不了解业务,所以你可能就是看见漫天的id类型在传过来传过去,就是不知道传递的啥?

其实在上一节的最后,我也举例NSArray中自扩展map函数的例子,其中里面传递的闭包也是id(^)(id)类型,因为NSArray里保存的是对象,map后也应该是个id,只是在具体的处理业务时,向下转换即可。

参考文档

ReactiveCocoa

总结

Objective-C终究是一门上古语言,我觉得到现在它还能发光发热都是Apple功劳。

虽然它没有泛型,但是灵活运用OC语言的动态性通过id类型去统和对象类型,确实是一种方法,在很多弱语言中都是如此。

如果做iOS开发,目前来看,只要还在用Cocoa框架,OC脱离不了,但还是建议转Swift进行开发。