美文网首页iOS精选博文iOS之OC深入理解移动端开发
iOS runtime 应用之给 NSString 添加对象属性

iOS runtime 应用之给 NSString 添加对象属性

作者: Gadfly_ | 来源:发表于2016-11-26 17:58 被阅读473次

runtimeiOS 的作用和地位在此就无需多费口舌了.

接下来我以添加属性为例, 用 runtimeFoundation 下的 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_getAssociatedObjectgetter方法.

再看看它的声明:

id objc_getAssociatedObject(id object, void *key)

知道了 setter 里面的参数, getter 里面参数就不用解释了.

要注意一点, getter 返回的 id 类型, 要和 settervalueid 类型一致. 说的好拗口, 就是那个意思, 你懂的.

两个关键的 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 文件中声明一对 settergetter 方法:

注意命名规则

- (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

发现编译器并没有任何 errorwarning, 这也表明指针那些头疼的问题, 在这里我们的确不需要去考虑的.

不知道你刚才的疑云还在不在? 不管在不在, 现在回到 .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

替换之后, 编译器并没有任何 errorwarning , 这说明刚才的猜想是正确的.

至此, 一个 NSString * 类型的属性 strFlag 是不是就冠冕堂皇地加到 NSString 类里面去了.

这时, 你一定会迫不及待的去验证这个方法是不是可行:

引入上面创建类别的头文件:

#import "NSString+Ass.h"

创建一个 NSString 对象:

NSString *str = [NSString new];

尝试 . 出刚才添加的属性 strFlag :

str.str

突然发现:


Amazing...

这说明 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);
}

打两个断点查看一下:

key值的详情 Paste_Image.png

strFlag 方法中就没必要在获取自身的函数指针了, SEL key = @selector(strFlag); , _cmd 是就是一个指向方法自身的宏 .

在非属性的方法里如法炮制的替换一下就可以了, 注意要用SEL key = @selector(intFlag);, 不能再用 SEL key = @selector(strFlag);了.


至此, 给已有类添加属性的方法就完美实现了.

如果代码中有什么 bug 或者需要改进的地方, 还望海涵, 同时欢迎在下方留言~

不要吝啬您那宝贵的♥︎&★就好, 您的支持是我分享的动力~☺️

相关文章

网友评论

    本文标题:iOS runtime 应用之给 NSString 添加对象属性

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