JoshPell

人生只有必然,没有偶然

欢迎来到我的个人网站~


RunTime动态添加方法和属性

动态添加方法

面试的时候面试官可能会问用过performSelector方法么,那就请注意了,他百分之百问的是跟runtime有关的知识,不要傻乎乎的以为他就是问你有没有用过这个方法了,然后你还跟他扯可以延迟调用方法啥的,这些都是OC封装好的,相信我他不是在考你这个。

performSelector方法区别于直接调用,直接调用时假如你方法没实现,编译直接报错,而performSelector只会报警告,所以人家是问你runtime动态添加方法的东西。

动态添加方法是一个很有意义的事情,因为程序在编译的时候,会把所有的方法加到一个方法列表中,但是我们并不是所有的方法都会使用到,耗时耗力。我们应该多利用懒加载的方式,用到某个方法,再添加,不用的方法就不用管它。

首先我们需要导入#import <objc/message.h>,然后写下下面2个方法:

// 当某个类方法只声明,没有实现的时候,会执行下面的方法
+ (BOOL)resolveClassMethod:(SEL)sel;
// 当某个对象方法没有只声明,没有实现的时候,会执行下面的方法。
+ (BOOL)resolveInstanceMethod:(SEL)sel ;

我们先创建一个函数:

void eat(id self, SEL _cmd,id param1){
    NSLog(@"调用%@---%@---%@",self,NSStringFromSelector(_cmd),param1);
}

讲解一下,每一个函数,都有2个默认的隐式参数,一个是谁调用了自己,一个是SEL,SEL就是对方法的一种包装。包装的SEL类型数据它对应相应的方法地址,找到方法地址就可以调用方法。后面的id类型的param1是我写的一个参数,因为是C语言的函数,我们无法创建NS之类的类型,这里我就用id类型来接参数。

接下来,根据官方文档我们可以添加下面的代码做判断,使得在找不到eat方法的时候,可以执行我们动态添加的eat方法,注意,上面的函数名可以随意写,只需要在下面添加方法的时候做好关联就好:

// 外界调用一个没有实现的对象方法-
// resolveInstanceMethod中sel是没有实现的参数
+ (BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"%@",NSStringFromSelector(sel));
//    if (sel == @selector(eat)) {}这句话等同下面的判断

    if ([NSStringFromSelector(sel) isEqualToString:@"eat:"] ) {
        // 这里添加方法
        // 给哪个类
        //SEL:方法名
        //IMP:方法的实现(函数的入口-函数的指针-函数的名)
        //type :方法类型
        class_addMethod(self, sel, (IMP)eat, "v@:@"); 
        returnYES;
    }
    return [super resolveInstanceMethod:sel];
}

这里介绍一下OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)这个函数的参数意义:

Class cls:就是你给哪个类添加的这个方法

SEL name:就是方法名字是啥,默认进入方法的时候,肯定是方法上带的参数sel没有,所以我们这里传入的是sel。

(IMP)eat:这里需要我们传入一个IMP,啥实IMP,IMP就是方法的实现(函数的入口-函数的指针-函数的名)大家这里意会下

const char types,这里我们可以好好说一说。我先说下意思,v@:@*的意思就是,返回类型是void,参数是id,SEL,id。具体大家参考上面我写的函数以及函数说明。

如果想查到这些代表啥意思,可以打开苹果文档,输入Type Encodings,选择我箭头所指,查询。

Thumper

动态添加属性

通过运行时添加属性,使用面还是比较广的。比如想给button类绑定一个属性,大家肯定会继承button来操作。其实通过运行时添加属性,我们就可以实现给系统button添加一个属性的需求。

默认我们在创建分类的时候,添加一个成员属性后,大家往往会发现,直接调用这个类的点语法,我们获取不了属性,为什么呢?

因为默认分类创建的属性,不会执行set和get方法。如果我们一定要获取到这个属性,我们应该怎么做呢?这里有2种方法,一种就是添加一个静态变量,重写它的set和get方法.另外一种就是通过运行时,添加这个属性。这里我只讲第二种,代码如下:

// 声明一个char型的key
static char nameKey;

- (void)setName:(NSString *)name
{
    // 属性跟对象有关联-就是添加属性

    // object:对象
    // key:属性名,根据key去获取到值
    // value:值
    // policy:策略
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

这里我讲一下·OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)·这个方法。 这个方法的字面意思就是把一个值,通过一个key绑定到一个类中,最后设置一下保存的策略。代码的意思是,把name的值,通过nameKey绑定到当前类,保存的是nonatomic的copy类型。 补充一下最后一个参数(策略):

// assign类型
   OBJC_ASSOCIATION_ASSIGN = 0,
// 非原子性Retain-->相当于Strong
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
// 非原子性 copy-
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
// 原子性Retain
    OBJC_ASSOCIATION_RETAIN = 01401,
// 原子性copy
    OBJC_ASSOCIATION_COPY

取得动态绑定属性的方法如下:

- (NSString *)name
{
    return objc_getAssociatedObject(self, &nameKey);
}

简单的解释就是通过self这个类的,nameKey这个key,我们就可以取到nameKey相对应的值了。这就是如何通过runtime来动态的给类添加属性了,帅哥,你可以试试了!

加餐:引用博客

1,关联对象与被关联对象本身的存储并没有直接的关系,它是存储在单独的哈希表中的;

2,关联对象的五种关联策略与属性的限定符非常类似,在绝大多数情况下,我们都会使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC 的关联策略,这可以保证我们持有关联对象; 3,关联对象的释放时机与移除时机并不总是一致,比如实验中用关联策略 OBJC_ASSOCIATION_ASSIGN 进行关联的对象,很早就已经被释放了,但是并没有被移除,而再使用这个关联对象时就会造成 Crash 。


打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

Powered by 谭健,分享从这里开始,精彩与您同在


正在加载中……