Runtime奇技淫巧之objc_setAssociatedObject,objc_getAssociatedObject

96
CornBallast
2017.05.25 19:15* 字数 1293

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

引言

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

  • 创建分类,直接添加,引入文件,直接调用,一步到位,双击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类型属性,把代码块作为参数进行操作),但是在有些情况下,我们并不需要如此复杂的添加一个属性,临时链接一下就可以,因为我发现这样的死心眼还挺多。理解原理,理性封装之后你用起来是不是更放得开手脚了,如果你有感到一点点爽,请给伍丽娟点个赞!!!

iOS奇技淫巧
Web note ad 1