我们知道苹果不允许我们自己给已经存在的类通过分类添加方法的,
但是有时候我们确实需要给某个类从而分类添加属性,那么我们该怎么办呢?
通过这一节学习,我们也许就会有思路了!
为了引出今天要学习的东西,假设我们的项目有这样一个需求,我们要给所有继承自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的set
和get
方法行不行呢?按照这个思路我们再来试一下,在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_setAssociatedObject
和objc_getAssociatedObject
中void *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) };
网友评论