小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
前言
这一篇文章应该算是上一篇聊一下Objective-C中的泛型的扩展衍生。
你或许早已意识到id类型它的妙处,在不知不觉中已经将它用的炉火纯青,或许我写的这些内容也不算非常新的发现。
让大家见笑了。
用id去传递参数
不知道大家用过OC的响应式框架没有,没错就是ReactiveCocoa。
虽然现在的ReactiveCocoa已经用Swift重写了,但是在3.0之前的版本,它都是用OC编写的。
我们来看一个比较经典的API对比,就是ReactiveCocoa中subscribe方法和RxSwift中subscribe函数:
ReactiveCocoa中subscribe | RxSwift中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,只是在具体的处理业务时,向下转换即可。
参考文档
总结
Objective-C终究是一门上古语言,我觉得到现在它还能发光发热都是Apple功劳。
虽然它没有泛型,但是灵活运用OC语言的动态性通过id类型去统和对象类型,确实是一种方法,在很多弱语言中都是如此。
如果做iOS开发,目前来看,只要还在用Cocoa框架,OC脱离不了,但还是建议转Swift进行开发。