美文网首页iOS模块详解iOS_CornBallastObjC Runtime
Runtime奇技淫巧之objc_setAssociatedOb

Runtime奇技淫巧之objc_setAssociatedOb

作者: 穿山甲救蛇精 | 来源:发表于2017-05-25 19:15 被阅读604次

    讲交换方法时,我们说过下节课讲给分类加属性,我猜你一点都不期待,因为你肯定会。

    引言

    关于分类加属性我们尝试这种方式:

    • 创建分类,直接添加,引入文件,直接调用,一步到位,双击666!!!

    创建分类:

    @interface Person (Character)
    @property(nonatomic,strong)NSString* name;
    @end
    @implementation Person (Character)
    @end
    

    潇洒调用:

    Person* person = [[Person alloc] init];
    person.name = @"伍丽娟";
    

    结果爆炸:

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person setName:]: unrecognized selector sent to instance 0x60000000c6f0'
    

    原理分析

    其实作为一个饱受套路的👨‍💻‍来说,肯定不会像上面这样天真烂漫的写代码。可能很多人也都知道用 objc_setAssociatedObjectobjc_getAssociatedObject进行添加,我知道你不关心其中的原理,但是我偏要说!


    我们作如下操作:
    • 我们创建一个分类,并在分类中声明一个实例变量,如图:

      你会发现报错了,系统告诉你在分类中不能添加实例变量,也就是说,Person类的结构体中的实例变量链表(ivars)不可扩展,Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。有人说我这是扯淡,系统明明有class_addIvar这个方法啊!

      这个方法的用处是:我们通过运行时来创建一个类时,我们才可以使用class_addIvar函数添加实例变量。但是这个方法也只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。

      现在我们知道分类中添加实例变量是不可能的了,于是我们还是沿着属性这条路发展才有一线生机,按照引言中的方法直接声明属性,到.m中查看,你就会发现Xcode的良心所在:

    哎呦喂,它告诉你你要实现settergetter方法,虽然ivars链表不能扩展,但是methodLists可以啊。但是之前的settergetter都是结合实例变量实现的,现在该怎么办呢?
    现在我们的主角才是时候闪亮登场,下面两个方法用来把一个对象与另一个对象进行关联,并不会像属性自动生成的实例变量一样在当前类开辟空间。
    /*
    * object  源对象
    * key  关键字
    * value  被关联的对象
    * policy  关联策略
    */
    OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
    OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
        OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
    

    于是,我们的settergetter方法可以这么写:

    -(NSString *)name {
        return objc_getAssociatedObject(self, @"name");
    }
    
    -(void)setName:(NSString *)name {
        objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    

    你会发现,完美解决,但是这样的属性不会生成实例变量,不要总是妄想用_name取到对应实例变量,它只是有对应的settergetter方法而已。

    伍丽娟是这样理解的:

    前几回我们说到,你找到了伍丽娟,并且顺利搞走了你们之间的绊脚石无敌三道杠,你觉得你的幸福生活即将开始,但是,不幸的事又发生了:
    当初上帝在造人时,都有特定的特性,比如你的性别(readonly),你的朋友(readwrite),你的事业(readwrite),你的老师同学(readwrite),等等... ... ,只要属于你的,你都可以随心所欲尝试你可以自主改变的部分。但是,上帝并没有给你可以拥有女票的特性,也就说你一辈子都是单身狗,你和吴丽娟根本就不可能,因为你根本就没这个功能!
    你颓废,你沮丧,你的生活没有了方向,你没有办法和她有半点联系,也没有办法让别人通过你的女票就想到她,在被人眼里,你的生命中就没有女票这个概念。但是,你发现你的思想,你的行动是独立的,你可以做自己想做的任何事,但这并不能改变你是单身狗的事实。于是你决定,哪怕不做情侣,能默默关注也好,能让别人看到你们之间还是有关系你也就心满意足了,于是你主动出击搭建你们之间的某种联系,但是她永远都不属于你,她永远无法走入你的生命!你只能单纯的关注她,你的心永远无法为他开辟出一席之地。人们也只能间接的通过你侧面的了解伍丽娟,但是在伍丽娟的角度,你啥么都不是。

    代码封装

    感谢上一位选手的情感真挚的发言!如果我们每一个都去像上面这样写,你自己都会觉得自己是个逗逼吧(但很多人乐此不疲)。封装一下,内附用法:

    //
    //  YSAssociated.h
    //  RuntimeSkill
    //
    //  Created by ys on 2016/5/11.
    //  Copyright © 2016年 ys. All rights reserved.
    //
    
    #import <objc/runtime.h>
    // 添加id类型属性
    #define ASSOCIATED(propertyName, setter, type, objc_AssociationPolicy)\
    - (type)propertyName {\
    return objc_getAssociatedObject(self, _cmd);\
    }\
    \
    - (void)setter:(type)object\
    {\
    objc_setAssociatedObject(self, @selector(propertyName), object, objc_AssociationPolicy);\
    }
    
    // 添加BOOL类型属性
    #define ASSOCIATED_BOOL(propertyName, setter)\
    - (BOOL)propertyName {\
    NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.boolValue;\
    }\
    \
    - (void)setter:(BOOL)object\
    {\
    objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
    }
    
    // 添加NSInteger类型属性
    #define ASSOCIATED_NSInteger(propertyName, setter)\
    - (NSInteger)propertyName {\
    NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.integerValue;\
    }\
    \
    - (void)setter:(NSInteger)object\
    {\
    objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
    }
    
    // 添加float类型属性
    #define ASSOCIATED_float(propertyName, setter)\
    - (float)propertyName {\
    NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.floatValue;\
    }\
    \
    - (void)setter:(float)object\
    {\
    objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
    }
    
    // 添加double类型属性
    #define ASSOCIATED_double(propertyName, setter)\
    - (double)propertyName {\
    NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.doubleValue;\
    }\
    \
    - (void)setter:(double)object\
    {\
    objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
    }
    
    // 添加long long类型属性
    #define ASSOCIATED_longlong(propertyName, setter)\
    - (long long)propertyName {\
    NSNumber *value = objc_getAssociatedObject(self, _cmd); return value.longLongValue;\
    }\
    \
    - (void)setter:(long long)object\
    {\
    objc_setAssociatedObject(self, @selector(propertyName), @(object), OBJC_ASSOCIATION_RETAIN_NONATOMIC);\
    }
    
    //在类别中添加属性
    //使用方法如下:
    /**
     .h
     #import <Foundation/Foundation.h>
     @interface Person (AssociatedTest)
     
     @property (nonatomic, strong) NSString *name;
     @property (nonatomic, weak) id delegate;
     @property (nonatomic, assign) BOOL isOK;
     
     @end
     
    .m
     #import "Person + AssociatedTest.h"
     
     @implementation NSObject (AssociatedTest)
     
     ASSOCIATED(name, setName, NSString *, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
     ASSOCIATED(delegate, setDelegate, id, OBJC_ASSOCIATION_ASSIGN)
     ASSOCIATED_BOOL(isOK, setIsOK)
     
     @end
     
     */
    

    根据我上面的使用方法,你应该能够更加快速稳定的为扩展类添加属性了(提供一种思路:很多时候,不要只局限于你理解中的对象,你可以给系统类添加Block类型属性,把代码块作为参数进行操作),但是在有些情况下,我们并不需要如此复杂的添加一个属性,临时链接一下就可以,因为我发现这样的死心眼还挺多。理解原理,理性封装之后你用起来是不是更放得开手脚了,如果你有感到一点点爽,请给伍丽娟点个赞!!!

    相关文章

      网友评论

      本文标题:Runtime奇技淫巧之objc_setAssociatedOb

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