正常情况下,我们会通过代码显性调用对应的方法实现,在底层通过消息机制来完成消息的发送,实现方法调用。但是有些情况下,我们通过直接调用msgSend方法可以极大地简化实现,节约开发时间.下面我们举两个简单的例子来进行说明.
统一处理不同方式的请求
AFNetworking是我们经常使用的一个网络请求第三方,由于该类库对于请求方式进行了分开处理,所以如果请求中有多种请求方式并存我们就需要对不同的方式进行分开处理,但是如果使用msgSend来发送消息就可以将多种方式和在一起进行统一处理.
仔细观察一下AFHTTPSessionManager中常用的几种不同请求方式的api:
//GET - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; //POST - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; //PUT - (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //PATCH - (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //DELETE - (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
除了请求方式不同之外,其余的方法体部分部分全部相同,将不同的部分摘出来单独处理,使用字符串进行拼接然后转化为对应的SEL即可.
- (AFHTTPSessionManager *)manager { if (!_manager) { _manager = [AFHTTPSessionManager manager]; _manager.responseSerializer = [AFHTTPResponseSerializer serializer]; //code here for other settingd } return _manager; } - (void)loadRequest:(NSString * _Nonnull)url paramters:(id)parmas method:(NSString * _Nonnull)method result:(void(^ _Nullable)(id, NSError *))result { NSAssert(method, @"method不能为空!"); NSString *requestMethod = [method uppercaseString]; static NSSet *methods; if (methods) { methods = [NSSet setWithObjects:@"GET", @"POST", @"PUT", @"DELETE", @"PATCH", nil] } NSAssert([methods containsObject:requestMethod], @"不支持的请求方式"); typedef void(^SuccessBlock)(NSURLSessionDataTask * _Nonnull , id _Nullable ); typedef void(^FailBlock)(NSURLSessionDataTask * _Nullable , NSError * _Nonnull); SuccessBlock successBlock = ^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if (result) { result(responseObject, nil); } }; FailBlock failBlock = ^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (result) { result(nil, error); } }; NSString *selectorName = [NSString stringWithFormat:@"%@:parameters:success:failure:", requestMethod]; SEL sel = NSSelectorFromString(selectorName); if ([self.manager respondsToSelector:sel]) { ((void(*)(id, SEL, NSString *, id, SuccessBlock, FailBlock))objc_msgSend)(self.manager, sel, url, parmas, successBlock, failBlock); } else { NSLog(@"不支持此方法!"); } }
考虑到GET和POST在AFN版本(实例中使用的是3.2.1版本)的中增加了progress参数,
//新的GET方式api - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //新的POST方式api - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
且已经废弃原始的请求的方法,为防止某个版本中彻底废弃不再支持该方法,同时兼容其他几种请求方式不携带progress参数使用api,可以尝试优先调用包含progress参数的方法,如果不响应再调用不懈怠progress参数的方法:
- (void)loadRequest:(NSString * _Nonnull)url paramters:(id)parmas method:(NSString * _Nonnull)method result:(void(^ _Nullable)(id, NSError *))result { NSAssert(method, @"method不能为空!"); NSString *requestMethod = [method uppercaseString]; static NSSet *methods; if (methods) { methods = [NSSet setWithObjects:@"GET", @"POST", @"PUT", @"DELETE", @"PATCH", nil]; } NSAssert([methods containsObject:requestMethod], @"不支持的请求方式"); typedef void(^SuccessBlock)(NSURLSessionDataTask * _Nonnull , id _Nullable ); typedef void(^FailBlock)(NSURLSessionDataTask * _Nullable , NSError * _Nonnull); typedef void(^ProgressBlock)(NSProgress * _Nonnull); SuccessBlock successBlock = ^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { if (result) { result(responseObject, nil); } }; ProgressBlock progressBlock = ^(NSProgress * _Nonnull downloadProgress){ }; FailBlock failBlock = ^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (result) { result(nil, error); } }; NSString *selectorName = [NSString stringWithFormat:@"%@:parameters:progress:success:failure:", requestMethod];; SEL sel = NSSelectorFromString(selectorName); if ([self.manager respondsToSelector:sel]) { ((void(*)(id, SEL, NSString *, id, ProgressBlock, SuccessBlock, FailBlock))objc_msgSend)(self.manager, sel, url, parmas, progressBlock,successBlock, failBlock); } else { selectorName = [NSString stringWithFormat:@"%@:parameters:success:failure:", requestMethod]; sel = NSSelectorFromString(selectorName); BOOL responseToSelector = [self.manager respondsToSelector:sel]; NSAssert(responseToSelector, @"不支持方法"); if (responseToSelector) { ((void(*)(id, SEL, NSString *, id, SuccessBlock, FailBlock))objc_msgSend)(self.manager, sel, url, parmas, successBlock, failBlock); } } }
这样就可以优先调用带有progress回调的方法,如果不响应就继续尝试调用不带有progress回调的方法,这样就可以同时兼顾GET,POST新的api,又可以兼顾其他几种方式不携带progress回调的api.
统一处理不同的类方法调用
在面向对象的一些类中,使用了类似于工厂模式创建统一的基类方法,然后让各个子类去具体实现多态特性,如果我们需要根据不同的类来调用不同的方法.如果我们需要在不同状态下,使用不同的类进行调用,但是又不想针对不同的类单独编写调用实现,该怎么办呢?
这次以FontAwesomeKit为例.在这个体系中,FAKFontAwesome,FAKFoundationIcons,FAKIonIcons,FAKMaterialIcons,FAKOcticons,FAKZocial都继承自FAKIcon,同时FAKIcon中提供了基类必须实现的方法列表.现在的需求是使用同一套代码来获取不同子类中图标对象.
如果直接使用方法调用,我们就需要根据不同的类来做不同的实现
FAKIcon *icon = nil; if ([className isEqualToString:@"FAKFontAwesome"]) { icon = [FAKFontAwesome iconWithCode:fontCode size:size]; } else if ([className isEqualToString:@"FAKFoundationIcons"]){ icon = [FAKFoundationIcons iconWithCode:fontCode size:size]; } else if ([className isEqualToString:@"FAKIonIcons"]) { icon = [FAKIonIcons iconWithCode:fontCode size:size]; } else if ([className isEqualToString:@"FAKMaterialIcons"]) { icon = [FAKMaterialIcons iconWithCode:fontCode size:size]; } else if ([className isEqualToString:@"FAKOcticons"]) { icon = [FAKOcticons iconWithCode:fontCode size:size]; } else if ([className isEqualToString:@"FAKZocial"]) { icon = [FAKZocial iconWithCode:fontCode size:size]; } ...
如果有更多的子类,你可能需要写更多这样的判断结构,没有多大实际意义的重复意义的实现代码,却不得不写,如果使用msgSend,就可以简化这一过程:
FAKIcon *icon = nil; Class class = NSClassFromString(className); SEL sel = @selector(iconWithCode:size:); if ([object_getClass(class) instancesRespondToSelector:sel]) { ((FAKIcon *(*)(id, SEL, NSString *, CGFloat))objc_msgSend)(class, sel, fontCode, size); }
这样就可以大大节约时间,避免无用代码的书写,同时使代码结构更加清晰易读.
OK,打完收工,欢迎留言交流.