小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
前言
在iOS中聊泛型,一般情况下大家想到的肯定是Swift中的泛型,但是实际上Objective-C中也有泛型,只是一般情况下我们往往忽略了OC中泛型的特性。
今天要了解的就是我所理解的OC中的泛型。
NSArray中的泛型
因为工作的缘故,我还是必须跟进一些老项目的,我经常在老项目中看见这样一些代码:
@property (nonatomic, strong) NSMutableArray *dataSource;
NSMutableArray *mutableArray = [NSMutableArray array];
写的挺正常的,使用strong修饰可变数组,初始化数组也是使用的工厂方法。
其实上面的这种写法可以理解为:
@property (nonatomic, strong) NSMutableArray<id> *dataSource;
NSMutableArray<id> *mutableArray = [NSMutableArray array];
只要是一个对象类型,都可以被塞进数组中去。
这对于我们开发而言不是什么好事,一般情况下我们数组中只保存一种数据类型,最好是能够明确数组的类型:
/// 明确数据源是ResultItem类型
@property (nonatomic, strong) NSMutableArray<ResultItem *> *dataSource;
/// 明确数组里面是NSString类型
NSMutableArray<NSString *> *mutableArray = [NSMutableArray array];
这样我就能更好的理解数组中存储的数据类型了,虽然代码量增加了,但是对于维护代码的人,和对数组赋值的人,锁定了数组的数据类型,就告诉了编译器,这数组是不能随便被赋值的,需要匹配好类型。
我们看看NSArray的一个方法,感觉这个泛型是不是和Swift中定义的有那么点类似呢。
@interface NSArray<ObjectType> (NSExtendedArray)
- (NSArray<ObjectType> *)arrayByAddingObject:(ObjectType)anObject;
@end
NSDictionary中的泛型
NSDictionary中的泛型定义和NSArray十分相似,这里就做过多的展示,大家看看这个例子就可以了:
NSDictionary<NSString *, NSValue *>
记住,OC语言不要嫌弃多写点代码,虽然代码太长不是什么好事,但是代码写清楚很有必要。
定义代理时,使用的泛型
在OC中我们定义一个代理时,一般都会用到泛型,比如有下面这段代码:
#import <UIKit/UIKit.h>
@class CommentModel;
@protocol CommentCellDelegate <NSObject>
- (void)deleteButtonAction:(UIButton *)button commentModel:(CommentModel *)model;
@end
@interface CommentCell : UITableViewCell
@property (nonatomic, strong) CommentModel *model;
@property (nonatomic, weak) id<CommentCellDelegate> delegate;
@end
我们在声明delegate这个属性的时候,用的是id<CommentCellDelegate>,它说明什么:
首先delegate它是一个对象类型,其次这个对象类型必须遵守CommentDelegate协议。
于是我们看到了遵守CommentDelegate协议的类型一般都是这么写的:
@interface CommentListController ()<CommentCellDelegate, UITableViewDataSource, UITableViewDelegate>
尝试在OC中使用泛型做JSON转模型
我在OC中尝试通过泛型去编写网络请求响应的基类,给大家现个丑:
.h文件:
#import <Foundation/Foundation.h>
#import <YYModel.h>
NS_ASSUME_NONNULL_BEGIN
@interface ListItem: NSObject<YYModel>
@property (nonatomic , copy) NSString *topicTittle;
@property (nonatomic , copy) NSString *upTime;
@property (nonatomic , copy) NSString *topicDesc;
@property (nonatomic , assign) NSInteger numId;
@property (nonatomic , copy) NSString *topicImageUrl;
@property (nonatomic , assign) NSInteger topicStatus;
@property (nonatomic , assign) NSInteger topicOrder;
@end
@interface BaseResponse<T: NSObject<YYModel> *> : NSObject <YYModel>
@property (nonatomic , assign) NSInteger code;
@property (nonatomic, strong) NSArray<T> *list;
@end
NS_ASSUME_NONNULL_END
.m文件:
@implementation ListItem
+ (NSDictionary<NSString *,id> *)modelCustomPropertyMapper {
return @{
@"numId": @"id"
};
}
@end
@implementation BaseResponse
+ (NSDictionary<NSString *,id> *)modelContainerPropertyGenericClass {
return @{
@"list": [NSObject<YYModel> class]
};
}
@end
明眼人都看得出我想干什么。
BaseResponse类型和其定义的list类型的数组元素,通过泛型约束遵守YYModel协议。
每次请求的时候都使用BaseResponse去接收,然后一步到位进行YYModel协议的转换。
是不是有点Codable协议的想法?
于是我接着写网络请求:
- (void)request {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:@"" parameters:nil headers:nil progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
BaseResponse<ListItem *>* response = [BaseResponse<ListItem *> yy_modelWithJSON:responseObject];
NSLog(@"response: %@", response);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
}
那么激动人心的时候到了,可以转换成为BaseResponse<ListItem *>吗?
答案是遗憾的:
list被转成了NSDictionary,而不是ListItem。
出问题的地方也非常显而易见,就是这里:
+ (NSDictionary<NSString *,id> *)modelContainerPropertyGenericClass {
return @{
@"list": [NSObject<YYModel> class]
};
}
我在这里将list对应的是[NSObject<YYModel> class],而实际上我想对应的是在@interface BaseResponse<T: NSObject<YYModel> *> : NSObject <YYModel>声明的<T: NSObject<YYModel> *>,但是你是没办法在.m中这么写的:
使用了没有定义的T类型
走到这一步,通过泛型进行通用的模型转换就破灭了。
这也是为何,OC语言中对于网络请求一直都是一个请求,做一个返回模型的原因,而模型的外层总是相同的,因为无法通过泛型做统合,OC中的泛型,一般当作一个占位符看待。
OC的更多是使用其动态性
之前看过一个为NSArray扩展map函数的分类,其实现如下
@implementation NSArray (Map)
- (NSArray *)_map:(id(^)(id))handle {
if (!handle || !self) return self;
NSMutableArray *arr = NSMutableArray.array;
for (id obj in self) {
id new = handle(obj);
[arr addObject:new];
}
return arr.copy;
}
@end
map后的block是id(^)(id obj)这么一个类型,数组的元素是个id类型,而返回类型也是id类型,不是它不想用泛型约束,而是没有办法用泛型约束。
在使用这种NSArra的map函数中,我们只能通过运行时对数组类型的具体定义进而定义其具体类型。
id类型
在我写这些例子的时候,经常使用的是OC的id类型
id是一个一个比较灵活的对象指针,并且是一个指向任何一个继承了Object(或者NSObject)类的对象。而在cocoa的开发环境里,NSObject是所有类的根类。所以id可以指向任何一个cocoa的合法对象。
它与NSObject有相似的地方,也有不同的地方:
-
id是一种通用的对象类型,她可以用类存储属于任何类的对象,可以理解为万能指针
-
在id的定义中,已经包装好了*号,id指针只能指向os的对象
-
NSObject 和id都可以指向任何对象
-
NSObject对象会惊醒编译时检查(需要强制类型转换)
-
id不需要强制类型转换,id可以直接使用
-
编译器看到id以后,认为是动态类型,不在检查类型
参考文档
iOS OC实现map、filter、reduce等高阶函数
总结
我很难得了写一篇关于OC的技术文章,主要是我在写Swift的时候,想通过Swift去倒推OC中的泛型用法和尝试。
整体而言OC的泛型非常羸弱,不过在对于NSArray、NSDictionary使用上,通过声明泛型,可以让我们更加清晰的理解数据结构和类型。
而Delegate中的泛型使用,可以认为是对类型的约束。
在Swift中经常使用的泛型统和编写基类响应体,但是OC中对此支持并不友好,OC毕竟更多的特性是语言的动态性。
如果大家对OC中代码的泛型有兴趣,可以看看这个Demo,因为是我随意写的,记得切换到fastlane_auto分支去查看代码。
我们下期见。