美文网首页
runtime - 关联属性

runtime - 关联属性

作者: 啊啊啊啊锋 | 来源:发表于2016-07-05 15:52 被阅读68次

    我们知道苹果不允许我们自己给已经存在的类通过分类添加方法的,
    但是有时候我们确实需要给某个类从而分类添加属性,那么我们该怎么办呢?
    通过这一节学习,我们也许就会有思路了!

    为了引出今天要学习的东西,假设我们的项目有这样一个需求,我们要给所有继承自NSObjct的类增加一个objName的属性,而且这个属性必须卸载分类里边,好了那么在我们还不是太了解runtime的情况下,我们可能会这么做:
    新建一个NSObjct的分类,命名为ObjConnect,在.h文件里边,我们声明一个属性:

    /** 我么知道如果我们给自定义的分类增加了某个属性是没问题的,但是如果是给某个分类增加一个属性那么会有什么问题呢?
     *  在这里我们只在NSObjct的分类里边增加一个objName的属性,而不做任何其他事情,那么我们看看会有什么问题 
     */
    @property (nonatomic, copy) NSString *objName;
    

    然后我们再新建一个Dog类,继承自NSObjct,除此之外我们不做任何其他事情。

    在我们的控制器里边,初始化一个Dog对象,然后给objName初始化一个值并打印之,代码如下:

    Dog *dog = [Dog new];
    dog.objName = @"旺财";
    NSLog(@"%@", dog.objName);
    

    好了,让我们运行程序:

    2016-07-05 14:44:06.699 ZFRuntime[1314:135570] -[Dog setObjName:]: unrecognized selector sent to instance 0x7fbed341c7b0
    2016-07-05 14:44:06.703 ZFRuntime[1314:135570] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Dog setObjName:]: unrecognized selector sent to instance 0x7fbed341c7b0'
    

    这时候我们看到程序崩溃了,并且控制台输出了如上的错误,提示[Dog setObjName:]这个方法系统并不认识,因为程序不允许我们给分类添加这样的属性。

    下边我们在思考,既然提示[Dog setObjName:]这个方法认识,那么我们自己写objName的setget方法行不行呢?按照这个思路我们再来试一下,在NSObjct这个分类里边增如下几行代码:

    /** 我们看看自己来写`objName`的`set`和`get`方法 */
    - (void)setObjName:(NSString *)objName
    {
        self.objName = objName;
    }
    
    - (NSString *)objName
    {
        return self.objName;
    }
    

    然后我们再次运行程序:这个时候程序出现了一个更严重的问题,提示内存出现泄漏,看来这样也打不到我们的目的。

    既然上边两个思路都行不通,那么让我们用运行时的方式看看能不能解决这个问题呢?

    首先在NSObjct分类的.m文件里边定义一个关联值:

    /** 系统将会通过这个关联值来获取属性值,注意必须保证这个值唯一,通常使用`static const void *`方式来声明 */
    static const void *TagSetObjNameKey;
    

    然后实现在两个方法:

    /** 设置关联的值,也就是设置`objName`属性的值,可以理解为这个属性的`setter`方法 */
    - (void)setObjName:(NSString *)objName
    {
        objc_setAssociatedObject(self, TagSetObjNameKey, objName, OBJC_ASSOCIATION_COPY);
    }
    
    /** 获取关联的值,也就是获取`objName`,可以理解为这个属性的`getter`方法 */
    - (NSString *)objName
    {
        return objc_getAssociatedObject(self, TagSetObjNameKey);
    }
    

    好了让我们再次运行程序,这是口可以看到控制台打印信息:

    2016-07-05 15:01:26.036 ZFRuntime[1424:145877] 旺财
    

    说明我们的目的已经实现了,通过runtime的方法,我们成功地给分类增加了一个属性!

    既然运行时的这个API这么方便,那么再来分析下这几个方法到底什么意思,我们以后要是使用的话应该传递什么参数呢?

    苹果官方文档里边,和属性关联相关的API有三个,这三个API分别是:

    /**
    *  定义:通过给定关联的`类`和`关联策略`等,为某个类增加关联(属性)
    *
    *  @param object 要关联的(要增加属性的)那个类
    *  @param key    关联的键,例如上边例子中我们传递的是 `TagSetObjNameKey`
    *  @param value  通过`键`要关联的那个值,我们简单可以理解为我们自定义的这个属性。(如果要清除某个关联,这个值可以设置为nil,但是很少这么做)
    *  @param policy 关联策略(关于这个参数下边我们会详解)
    */
    void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
    
    /**
    *  定义:通过给定`类`和`关联键`获取关联(属性)
    *
    *  @param object 同上
    *  @param key    同上
    *
    *  @return 关联的值(属性)
    */
    id objc_getAssociatedObject(id object, void *key)
    
    /**
    *  定义:给定关联的`类`,移除所有关联(这个方法)
    *  备注:这个方法很少用
    *  @param object 同上
    */
    void objc_removeAssociatedObjects(id object)
    

    这里有几点我们有必要说明一下:

    • 关于void objc_setAssociatedObjectobjc_getAssociatedObjectvoid *key这个参数,其实这个参数有很多种写法,比较常见的有以下几种:

      • 示例程序中的写法,即 static const void *关联键;,这中写法在SDWebImage中比较常见
      • _cmd的方式(_cmd代表本方法的名称),关于这个东西可以参考这里,笔者第一次见到这个用法是在这个demo里边
      • 还有一种方法是@selector(方法名)的方式
        其实这几种写法都大同小异,根据个人爱好来就可以了
    • 关于objc_setAssociatedObject方法中objc_AssociationPolicy policy这个参数,苹果官方是这样定义的,简单理解就是引用方式,相当于我们在声明属性是()里边写的那几个东西:

        enum {
            OBJC_ASSOCIATION_ASSIGN = 0, // (assign)
            OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, (retain, nonactomic)
            OBJC_ASSOCIATION_COPY_NONATOMIC = 3, (copy, nonactomic)
            OBJC_ASSOCIATION_RETAIN = 01401, (retain)
            OBJC_ASSOCIATION_COPY = 01403 (copy)
        };

    相关文章

      网友评论

          本文标题:runtime - 关联属性

          本文链接:https://www.haomeiwen.com/subject/huihjttx.html