runtime
在iOS
的作用和地位在此就无需多费口舌了.接下来我以添加属性为例, 用
runtime
给Foundation
下的NSString
类添加两种属性, 对象属性和非对象属性. 初步了解一下runtime
的基本使用方法.
两个重要的 API
首先来介绍 runtime
中的两个 api
:
1. objc_setAssociatedObject
苹果官方给出的解释就是:
Sets an associated value for a given object using a given key and association policy.
稍微翻译一下(btw, 英文不好, 请勿见笑):
用一个关键字和一个关联策略, 给一个已存在的对象设置一个关联值.
它的声明是:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
结合上面的解释, 对应着声明中的参数, 很容易知道四个参数的含义了:
object
: 给哪个对象关联值;
key
: 给 object 关联值, 肯定就是要其他地方使用这个关联值, 不然干嘛关联, 自然联想到 key-value
的取值方法;
value
: 不用说, 自然是关联什么值;
policy
: 这个需要深究一下
接下来深究一下 policy
的类型 objc_AssociationPolicy
:
查看一下官方文档就知道, objc_AssociationPolicy
是 枚举
类型:
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
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. */
};
去掉前缀
OBJC_ASSOCIATION_
剩下的assign
,retain
,copy
,nonatomic
是不是有种熟悉的味道, 不就是我们在定义属性@property
的时候使用的关键词么~ 这下对objc_setAssociatedObject
就完全了解了.
2. objc_getAssociatedObject
Returns the value associated with a given object for a given key.
很明显, 这个上面的 objc_setAssociatedObject
是一对方法, 上面是 setter
方法, objc_getAssociatedObject
是getter
方法.
再看看它的声明:
id objc_getAssociatedObject(id object, void *key)
知道了 setter
里面的参数, getter
里面参数就不用解释了.
要注意一点,
getter
返回的id
类型, 要和setter
的value
的id
类型一致. 说的好拗口, 就是那个意思, 你懂的.
两个关键的 api
介绍掌握清楚了, 接下来就回到之前的主题了, 给 NNString
添加对象属性和非对象属性, 毕竟上面的介绍属于理论层次, 每一个 api
只有在实际中运用自如了, 才能算得上真正的掌握了.
对象属性的添加
1.给 NSString
创建一个 Category
:
这时你可能已经有疑问了,
Category
不是只能添加方法, 不能添加属性吗? 明明说的就是添加属性, 这里怎么还是添加Category
呢 ? 带着你的疑问继续往下看...
File >> New >> File.. >> [select iOS, others are OK too] >> [select Objective-C File] >> File:[enter CategoryName] >> File Type:[select "Category"] >> Class:[enter "NSString"] >> Next >> ...
2.在 .h
文件中声明一对 setter
和 getter
方法:
注意命名规则
- (void)setStrFlag:(NSString *)strFlag;
- (NSString *)strFlag;
3.在 .m
文件中引入 runtime
头文件:
毕竟使用
runtime
机制, 头文件自然不可缺少
#import <objc/runtime.h>
4.在 .m
文件中实现 .h
中的一对方法:
- (void)setStrFlag:(NSString *)strFlag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_setAssociatedObject(self, const void *key, strFlag, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)strFlag {
// id objc_getAssociatedObject(id object, const void *key)
return objc_getAssociatedObject(self, const void *key);
}
现在除了参数 key
没填入, 其他参数都准备好了.
熟悉 C
语言的同学一定对 const void *
类型一定不陌生吧!
没错, void *
就是 C
语言中令人头疼的指针类型, 指向的是某个变量在内存中的首地址, const
是修饰这个指针的内容是常量, 不能修改.
当然, 这里我们没必要思考指针那些头疼的问题了, 只需要知道 key
其实是个指针类型就 OK 了.
那我们就随便定义一个变量, 然后把它的指针获取到, 作为 key 值传进去就 OK 了:
@implementation NSString (Ass)
NSString *strKey;
- (void)setStrFlag:(NSString *)strFlag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
// SEL key = @selector(strFlag);
objc_setAssociatedObject(self, &strKey, strFlag, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)strFlag {
// id objc_getAssociatedObject(id object, const void *key)
return objc_getAssociatedObject(self, &strKey);
}
@end
发现编译器并没有任何 error
或 warning
, 这也表明指针那些头疼的问题, 在这里我们的确不需要去考虑的.
不知道你刚才的疑云还在不在? 不管在不在, 现在回到 .h
文件:
@interface NSString (Ass)
- (void)setStrFlag:(NSString *)strFlag;
- (NSString *)strFlag;
@end
有没有发现, 这对 存取器
(accessor) 就是我们在定义属性的时候, 编译器自动给我们创建的两个方法, 既然我们在 .m
文件中都已经实现了这两个方法, 那么在 .h
中是否可以用一个属性来代替这两个方法呢?
@interface NSString (Ass)
/**
额外增加的属性
*/
@property (nonatomic, copy) NSString *strFlag;
// 对象属性的set和get
//- (void)setStrFlag:(NSString *)strFlag;
//- (NSString *)strFlag;
@end
替换之后, 编译器并没有任何 error
或 warning
, 这说明刚才的猜想是正确的.
至此, 一个 NSString *
类型的属性 strFlag
是不是就冠冕堂皇地加到 NSString
类里面去了.
这时, 你一定会迫不及待的去验证这个方法是不是可行:
引入上面创建类别的头文件:
#import "NSString+Ass.h"
创建一个 NSString
对象:
NSString *str = [NSString new];
尝试 .
出刚才添加的属性 strFlag
:
str.str
突然发现:
这说明
strFlag
属性的确添加进去了!
如果你还不信, 你可以继续复制, 然后打印看看...
至此, 对象属性已经添加成功了, 接下来添加非对象属性了
非对象属性的添加
基本和对象属性的添加差不多
.h
/**
额外增加的属性2
*/
@property (assign) int intFlag;
//// 非对象属性的set和get
//- (void)setIntFlag:(int)intFlag;
//- (int)intFlag;
.m
int intKey;
- (void)setIntFlag:(int)flag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_setAssociatedObject(self, &intKey, @(flag), OBJC_ASSOCIATION_ASSIGN);
}
- (int)intFlag {
// id objc_getAssociatedObject(id object, const void *key)
NSNumber *t = objc_getAssociatedObject(self,&intKey);
return (int)[t integerValue];
}
注意:
objc_setAssociatedObject
函数的第三个参数接受的是id
类型, 而int
不是id
类型, 所有将它转为NSNumber
类型后再传入.
同理,objc_getAssociatedObject
返回的是NSNumber
类型, 转为int
后再返回.
至此, 两种类型的属性已经添加完成了. 其实在 .m
文件中添加的那两个 key
还是可以优化的:
那两个
api
只需要接受void *
类型就行了, 而刚才在.m
文件中 那两个key
值我分别传入的是NSString **
和int *
类型.
学习过C
语言的同学都知道在C
语言中,void *
可以指向任何指针.
在iOS
中, 给一个按钮添加点击事件的时候,action
接受的就是一个SEL
类型, 查看文档不难发现, 系统对SEL
的定义是:
typedef struct objc_selector *SEL;
不难发现,
SEL
其实也是一种指针类型, 那么是不是可以用SEL
类型的值作为key
呢?
马上修改 .m
文件:
- (void)setStrFlag:(NSString *)flag {
// void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
SEL key = @selector(strFlag);
NSLog(@"%p", key);
objc_setAssociatedObject(self, key, flag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)strFlag {
// id objc_getAssociatedObject(id object, const void *key)
return objc_getAssociatedObject(self, _cmd);
}
打两个断点查看一下:
在
strFlag
方法中就没必要在获取自身的函数指针了,SEL key = @selector(strFlag);,_cmd
是就是一个指向方法自身的宏 .
在非属性的方法里如法炮制的替换一下就可以了, 注意要用SEL key = @selector(intFlag);
, 不能再用 SEL key = @selector(strFlag);
了.
至此, 给已有类添加属性的方法就完美实现了.
如果代码中有什么 bug 或者需要改进的地方, 还望海涵, 同时欢迎在下方留言~
不要吝啬您那宝贵的♥︎&★就好, 您的支持是我分享的动力~☺️