iOS关于动态执行方法的探索-performSelect-NSInvocation-objc_msgSend

我为什么会写这篇文章

今天在写Bugly上报错误的时候遇到了一个问题,由于项目使用组件化开发思想,在上报错误组件里需要知道当前是否导入了Bugly组件,如果没有Bugly组件,则不进行Bugly上报,我第一个想到了用NSClassFromString来判断是否存在Bugly,如果存在,再使用performSelector:withObjectruntime调用Bugly上报方法,但是Bugly输出日志的方法BuglyLog level:<#(BuglyLogLevel)#> log:<#(NSString *), ...#>第一个参数需要传入NSUInterger类型的基本数据,但是performSelector:withObject方法只能传入OC对象,所以我在解决问题的同时顺带研究了一下OC执行方法,消息传递的几种常见方法。

第一种方式

performSelector方式

这应该是开发过程中动态执行方法最常用的方式了吧,最常见的用法就是performSelector:withObject,或者可以再带一个参数performSelector:withObject:withObject:,就像这样调用:

id BuglyClass = (id)NSClassFromString(@"Bugly");
NSError *error = [NSError errorWithDomain:errorTitle code:-1 userInfo:@{NSLocalizedDescriptionKey:@""}];
[BuglyClass performSelector:@selector(reportError:) withObject:error];
复制代码

也有延时触发的用法

performSelector:withObject:afterDelay:
复制代码

在某个线程中执行的用法,当然这些不是本文讨论的重点,所以一笔带过

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
复制代码
其实不难发现performSelector方式有一些弊端

1.不能传超过三个参数 2.参数只能为OC对象

第二种方式

NSInvocation方式

先不说啥,直接上代码

NSString *string = @"test";
NSNumber *number = [NSNumber numberWithInt:1];

SEL selector = @selector(function2:count:);
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

invocation.target = self;
invocation.selector = selector;
// 第一和第二个参数是target和selector
[invocation setArgument:&string atIndex:2];
[invocation setArgument:&number atIndex:3];
//执行
[invocation invoke];
复制代码
  • NSMethodSignature: 方法签名,如果方法不存在或者参数对不上,会直接crach,其实我也不是很理解这个签名的意思,可能是代码写得不够多吧,按照我的理解,这个数字签名就像是找一下这个target里面是否有这个方法,参数是否正确,如果都对,则签名生效,否则就可能奔溃
  • NSInvocation: 包装了一次消息传递的所有内容,包括target,select,argument等等,会在运行时找到目标发送消息
  • 注意这里传的参数是地址,所以还不不满足我们的需求
为了解决今天上报Bugly的问题,我还得继续探索

搜索中... 搜索中... 搜索中... 找到方案了!

第三种方式

函数指针方式

上代码

id target = self;
SEL selector = @selector(function:count:);
// 第一个第二个参数是self和selector
typedef void (*function)(id, SEL, NSString *, int);
function methodToCall = (function)[target methodForSelector:selector];
methodToCall(target, selector, @"string",1);
复制代码

可能有些小伙伴没有见过这种用法,我其实也是解决Bugly问题的时候才了解到的,不过看代码应该是能看懂的,首先用selector找到要调用的方法,再定义一个函数指针,指向selector选择的方法,然后调用这个函数执行,我试了下,能完美解决文章开头我遇到的问题

继续探索

第四种方式

最底层的消息传递方式

上代码

id BuglyLogClass = (id)NSClassFromString(@"BuglyLog");
SEL selector = @selector(level:log:);
void (*logAction)(id, SEL, NSUInteger,NSString *) = (void (*)(id, SEL, NSUInteger,NSString *))objc_msgSend;
logAction(BuglyLogClass, selector, 1,@"description");
复制代码

这是我最后使用的方法。所有target执行方法在底层都是通过objc_msgSend消息传递实现的,要执行的方法归根结底就是一条消息,发送给目标target,我们来看一下Apple官方的解释

/* Basic Messaging Primitives
 *
 * On some architectures, use objc_msgSend_stret for some struct return types.
 * On some architectures, use objc_msgSend_fpret for some float return types.
 * On some architectures, use objc_msgSend_fp2ret for some float return types.
 *
 * These functions must be cast to an appropriate function pointer type 
 * before being called. 
 */
void objc_msgSend(void /* id self, SEL op, ... */ )
复制代码

其实performSelector的底层实现也是调用了objc_msgSend实现消息发送

- (id)performSelector:(SEL)sel {
    if (!sel) [self doesNotRecognizeSelector:sel];
    return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}
复制代码

研究到这里,我已经解决了我的Bugly传参数上报日志的问题,在此期间学到了很多知识,可能有些写的不是很正确,望大牛指正。

转载于:https://juejin.im/post/5d1e9f7de51d45777621bbed

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
帮我找出一下代码的错误,“/程序”应用程序中的服务器错误。 “gvProducts”上同时定义了 DataSource 和 DataSourceID。请移除一个定义。 说明: 执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。 异常详细信息: System.InvalidOperationException: “gvProducts”上同时定义了 DataSource 和 DataSourceID。请移除一个定义。 源错误: 行 39: this.gvProducts.DataSource = ds; 行 40: this.gvProducts.DataKeyNames = new string[] { "id" }; 行 41: this.gvProducts.DataBind(); 行 42: da.Dispose(); 行 43: conn.Dispose(); 源文件: c:\Users\86136\Desktop\第2组-网上书店系统\网上书店系统\程序\Myorderlist.aspx.cs 行: 41 堆栈跟踪: [InvalidOperationException: “gvProducts”上同时定义了 DataSource 和 DataSourceID。请移除一个定义。] System.Web.UI.WebControls.DataBoundControl.ConnectToDataSourceView() +8658325 System.Web.UI.WebControls.DataBoundControl.GetData() +4 System.Web.UI.WebControls.DataBoundControl.PerformSelect() +60 System.Web.UI.WebControls.BaseDataBoundControl.DataBind() +73 System.Web.UI.WebControls.GridView.DataBind() +4 Myorderlist.bind_ordertables() in c:\Users\86136\Desktop\第2组-网上书店系统\网上书店系统\程序\Myorderlist.aspx.cs:41 Myorderlist.BindShopBasket() in c:\Users\86136\Desktop\第2组-网上书店系统\网上书店系统\程序\Myorderlist.aspx.cs:71 Myorderlist.Page_Load(Object sender, EventArgs e) in c:\Users\86136\Desktop\第2组-网上书店系统\网上书店系统\程序\Myorderlist.aspx.cs:26 System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14 System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35 System.Web.UI.Control.OnLoad(EventArgs e) +99 System.Web.UI.Control.LoadRecursive() +50 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627
06-09

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值