iOS开发分享

【iOS】SDK开发之自定义反射实现不定参数、不定数据类型方法的

2018-12-19  本文已影响67人  子天々君

前言

一般的开发者对“反射”这个词很陌生,事实上他们自己用过了都不知道。但是在做聚合SDK开发当中,用得最多的就是反射了。
现在我想实现反射调用该方法:

+ (NSString *)test:(id)test p1:(NSString *)p1 p2:(unsigned int)p2 p3:(double)p3 p4:(CGSize)p4 p5:(NSNull *)p5 p6:(void(^)(void))p6 p7:(BOOL)p7 p8:(char)p8 p9:(char *)p9 p10:(YY)p10 p11:(SEL)p11 p12:(void **)p12 p13:(void ***)p13 p14:(Class)p14 p15:(IMP)p15; // YY是自定义结构体;p9是C字符串

然而遗憾的是,系统提供的反射最多只能带2个参数,而且参数只能是对象,但事实上并不是所有的参数都是对象的(比如 void **)。
虽然网上有很多的资料,但我都试过,都不咋地,总有缺陷。没办法,只能自己动手撸一个了。
实现多参数反射目前我只知道有2种方式:

实现方式的选择

虽然runtime号称是万能的,但事实上在64位里并不能直接使用objc_msgSend,必须强制转换成对应类型。可是,既然是不定参数、不定数据类型,所以我们根本无法去确定类型,换句话说,objc_msgSend是不可行的。
所以我们只能选择NSInvocation了。

一些坑和需要解决的问题

众所周知,OC是基于C的,事实上OC只是C的一层封装,所以我们使用的东西也是C的东西。
而不定参数的实现,也是用C的va_list。但va_list在取值之前是需要知道数据类型的,因为每次取值都要给个数据类型,然后va_list会根据该类型的长度去截取数据,如果给的数据类型不对,那截取的长度也会不对,就会导致之后的数据都不对了。
当然有人会说,那全部转成一个类型不就好了?
我开始也是这么想的,事实上网上大多数人的做法也是这样子。
但是,并不是所有类型都能转的。比如全部用对象,那么void **和C字符串怎么转呢?也有人会说C字符串包装成NSString啊,嗯,这是没问题的,可是你怎么还原呢?人家参数需要的是C字符串啊,你给个OC字符串??
另外,void *是一种类型,void **也是一种类型,char *也不一样,怎么处理呢?
对于结构体也是,每个自定义的结构体的类型都是不一致的。
此外,NSInvocation是不支持自动解包的。也就是说,如果你传了NSNumber对象当做参数,而调用的方法需求参数是int,你不自己解包成int的话就会数据不对。
所以,当下我们需要解决的问题就是数据类型的问题。

网上的一些实现方法

我的实现思路

我观察了NSString的声明,得到了一些启发:

+ (instancetype)stringWithFormat:(NSString *)format, ...

在NSString的创建方法里,系统使用了format来提供信息,也就是说,当匹配到%d的时候,就会认为是int,这个时候在va_list里取值int不就好了?
不过我并不打算这样子做,因为不单自己搞得麻烦,调用者也麻烦,每次都要写一串的%d%s什么的。
那么,有没有别的方法可以预知所需要的参数类型呢?
当然有了,NSMethodSignature里就有一个getArgumentTypeAtIndex:方法,该方法可以获取到index位置参数的数据类型。到了这里,数据类型的问题我们就解决了一半了,也不需要想着怎么包装成对象了,直接按照实际类型传参岂不是更舒服?


接下来我们来解决void *、void **的问题。
对于指针类型,从一开始我就掉进了一个坑。众所周知,指针是一种派生类型,只要在某种类型后面加个*号,就能派生出该类型的指针。换句话说,指针的类型的无数的,所以我们不可能是判断无数的类型,但是,指针的储存空间是固定的,也就是说我们取值只需要取指针的长度即可。


剩下的就是结构体的问题了。
结构体是基本数据类型的一个包装,能实现无数的结构体类型。所以除了内置的常用结构体,其它自定义的是无法识别的,因此,可以通过包装成NAValue解决,而取值的时候,使用指针去取值就好了。

代码就不贴了,这里只说实现的思路,想看具体的实现请到GitHub上下载源码。
获取源码请点击这里:ZTReflectionDemo
造个轮子不易,对你有用的请给个星星,谢谢了😄


因为最近比较忙,没办法细说,只能粗略说一些大体上的思路,看不懂的可以到我的QQ群(139322447)找我

上一篇下一篇

猜你喜欢

热点阅读