为Category添加属性

使用Runtime给Category添加属性。

Category 与 property

我为类ViewController添加了一个Category,然后我想给他添加一个属性,vcName

于是我在ViewController+Category中声明了一个属性

1
@property (nonatomic ,copy) NSString *vcName;

我们知道在类目的接口中是可以声明属性的。但是在他的实现部分是不允许包含@synthesize的。 下面安利一下两个关键字@synthesize@dynamic

  • @synthesize的语义是如果我们没有手动的实现propertysettergetter方法,编译器会自动的添加上这两个方法。

  • @dynamic告诉编译器,属性的settergetter方法由用户自己实现,不自动生成。

    在平常我们都习惯了不写这两个关键词,这个时候其实默认是@synthesize var = _var

回到刚才的地方,如果我手动加上@synthesize vcName = _vcName;是无法通过编译的。

1
@synthesize not allowed in a category's implementation//这是编译器报的错误消息

如果我不写的话编译器会有警告让你加上@dynamic语句。这个当然也是不行的。因为这个时候我自己写setter和getter.

1
2
3
4
5
6
- (NSString *)vcName{
return _vcName;
}
- (void)setVcName:(NSString *)vcName {
_vcName = vcName;
}

这时候编译器会报错

1
use of undeclared identifier '_vcName'

结论

Categoryde 的接口中可以包含属性声明,但是实现部分不能包含@synthesis

给Category 添加 Property

已经有了上面的结论了,但是我们在具体开发过程中确实会遇到很多需要在类别中添加使用Property的情况。这个时候应该怎么办呢?我们知道Obj-c是一门动态语言,强大的运行时(Runtime)机制能够为类关联引用,然后通过这种方法来实现给类别添加属性。

关联引用指的是借助运行时功能,为已存在的对象增加实例变量。

通过关联引用就算时同一个类的不同对象也可能添加关联或者添加不同种类和数量的关联。另外添加了的关联也是可以被删除的。

添加和检索关联

既然说到了需要使用runtime,那我们首先需要导入Runtime相关的头文件

1
#import <objc/runtime.h>
1
2
3
4
//这个方法时为对象object添加key指定的地址作为关键字,以value为值关联引用,第四个参数pilicy指的是关联引用的存储策略。
//通过将value设置为nil 可以删除key的关联
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);
1
2
3
//返回object以key为关键字关联的对象,如果没有,则反火nil
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

这一个的原理还是比较简单的。

首先,我们需要为一个类别专家多个关联引用,所以我们需要用不同的key值来区别。

其次,键值必须要使用一个确定且不可变的地址。所以选择定义在实现文件的static静态局部变量的地址。

policy策略就跟定义property的时候才用的存储关键字相似了。

存储关键字

第四个参数objc_AssociationPolicy policy其实是一个枚举,点看之后我们可以看到

1
2
3
4
5
6
7
8
9
10
11
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};

有这5个值

下面就分别的介绍一下几种。看注释也知道:

OBJC_ASSOCIATION_ASSIGN weak

不给关联对象发送retain消息,引用计数不会增加。

OBJC_ASSOCIATION_RETAIN_NONATOMIC strong

发送retain消息,引用计数加1. 如果给同样的key关联到了其他对象,那么会给其他对象发送一个release消息。释放关联对象的所有者的时候,会给所有关联的对象发送release消息。

OBJC_ASSOCIATION_COPY_NONATOMIC copy

会将该对象复制一份,并且用新复制的对象进行关联操作。

OBJC_ASSOCIATION_RETAIN strongatomic

OBJC_ASSOCIATION_COPY copyatomic

断开关联

runtime 也贴心的提供了断开关联的函数。但是可惜,我并不经常用,因为使用上面的方法并且传入nil来断开关联,会更佳安全,毕竟,我不敢保证是不是有其他地方会使用到已经关联的对象。

1
2
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

代码

扯了这么多有的没得,还是talk is cheap, show me the code 比较好一点。

第一步 导入头文件

1
#import <objc/runtime.h>

第二步 定义用作键值的静态变量。

1
2
3
4
5
6
7
8
9
10
11
```
// static char *vcNameKey;
/*
IMPORTANT!!!
这样写可能会有bug, 笔者已经踩坑。直接下面那样写!!!
*/
```
~~或者

1
static const char *vcNameKey = "vcNameKey";

第三步 定义存取方法

1
2
3
4
5
6
- (NSString *)vcName{
return objc_getAssociatedObject(self, vcNameKey);
}
- (void)setVcName:(NSString *)vcName {
objc_setAssociatedObject(self, vcNameKey, vcName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

试一下断开关联

首先在category中定义两个关联属性

在vievcontroller中给两个属性赋值,并且给self.title赋值

然后用objc_removeAssociatedObjects(self);断开关联

然后输出结果,category中关联的属性两个都输出null而本身的属性title正常输出

结论

很好说了,objc_removeAssociatedObjects(self);的作用是断开所有关联。

而且也更简单的验证了,使用runtime给对象关联的属性跟本身的属性本质是是不一样的。

CepheusSun wechat
订阅我的公众号,每次更新我都不一定会告诉你!
坚持原创技术分享,您的支持将鼓励我继续创作!
0%