接纳objc_msgSend包装AFN请求

2016.5.10 Update

下述的代码在模拟器运行没问题,可是在64位真机上发出了EXC_BAD_ACCESS问题。
这是因为规定必须先定义原型才能应用(原文见附1
苹果给出如下示例

- (int) doSomething:(int) x { ... }
- (void) doSomethingElse {
    int (*action)(id, SEL, int) = (int (*)(id, SEL, int)) objc_msgSend;
    action(self, @selector(doSomething:), 0);
}

故此对于再次回到值为void的下式

objc_msgSend(manager, NSSelectorFromString(selecterStr), url, parmas, nil, successBlock, failureBlock);

我们将之替换为

((void(*)(id, SEL, NSString *, NSDictionary *, void(^successBlock)(NSURLSessionDataTask * _Nonnull, id _Nullable), void(^failureBlock)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull)))objc_msgSend)(manager, NSSelectorFromString(selectorStr), url, parmas, successBlock, failureBlock);

看起来相比较劳碌,不过能缓解问题。

正文

这年头,啥都尊重个二次包装,为了未来的版本迁移/代码库更新/重构简化/三方提拔。
正确,我也这么做了。
在一个XXRequest的类中,二次封装了AFNetworking网络请求。


装进完后意识了部分问题…
后台给回复的接口有一部分是用Get请求的,有一对是用Post请求的。
全方位App并不涉及到上传图片或者文件的机能。
因此出现了一段重复的代码。

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
if (type == XXRequestTypeGet) {
    [manager GET:url parameters:parmas progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
       // 请求成功后的数据处理
       // 隐藏hud、回调Block等
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
           // 请求失败的数据处理
    }];
} else {
    [manager POST:url parameters:parmas progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
       // 请求成功后的数据处理
       // 隐藏hud、回调Block等
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
       // 请求失败的数据处理
    }];

其中type参数是外围传进来判断请求类型的枚举。
上段中蕴含了两有些肉眼可见的重新代码。
这段重复的代码让人越看越不爽…

为此尝试使用runtime,动态调用来加载请求。


动态调用最直白想到的办法自然是下面这多少个方法..

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

不满的是可以传递的参数个数并不切合大家的需求…

据此再往下拨一层,使用objc_msgSend()来实现。
利用该措施首先需要导入<objc/message.h>包。
现实落实代码如下:

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
void(^successBlock)(NSURLSessionDataTask * _Nonnull, id _Nullable) = ^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
   // 请求成功后的数据处理
   // 隐藏hud、回调Block等
};
void(^failureBlock)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull) = ^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
   // 请求失败的数据处理
};
NSString *selecterType = type == XXRequestTypeGet ? @"GET" : @"POST";
NSString *selecterStr = [NSString stringWithFormat:@"%@:parameters:progress:success:failure:", selecterType]
objc_msgSend(manager, NSSelectorFromString(selecterStr), url, parmas, nil, successBlock, failureBlock);

代码写完了,Ctrl+B编译…然后就报错了。

879.jpg

源于出自于LLVM的严加检查。
解决办法就是密闭它。
闭馆措施:

1. 点击项目
2. 点击 Build Settings
3. 在 Basic/All 中选择All
4. 搜索 objc
5. 找到 Apple LLVM x.x - Preprocessing 分组
6. 把 Enable Strict Checking of objc_msgSend Calls 后面的值由 YES 修改为 NO

重复编译,没有报错了。

总结:
三种办法动态调用方法

  1. performSelector
    直接调用,传参个数限制在3个以内。
  2. objc_msgSend
    内需导入message.h头文件,传参个数不限。

附1

Dispatch Objective-C Messages Using the Method Function’s Prototype
An exception to the casting rule described above is when you are
calling the objc_msgSend function or any other similar functions in
the Objective-C runtime that send messages. Although the prototype for
the message functions has a variadic form, the method function that is
called by the Objective-C runtime does not share the same prototype.
The Objective-C runtime directly dispatches to the function that
implements the method, so the calling conventions are mismatched, as
described previously. Therefore you must cast the objc_msgSend
function to a prototype that matches the method function being
called.
Listing 2-14 shows the proper form for dispatching a message to an
object using the low-level message functions. In this example, the
doSomething: method takes a single parameter and does not have a
variadic form. It casts the objc_msgSend function using the prototype
of the method function. Note that a method function always takes an id
variable and a selector as its first two parameters. After the
objc_msgSend function is cast to a function pointer, the call is
dispatched through that same function pointer

相关文章