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

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 或者需要改进的地方, 还望海涵, 同时欢迎在下方留言~

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,692评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,550评论 33 466
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,701评论 7 64
  • 住的地方是一片老的居民区,没错,就是这种一抬头就能看到错综复杂交汇着的电线的地方。原来是闸北区,现在洋气地变成了静...
    Luna_Lu阅读 348评论 0 0
  • 一天早上,和几个朋友围一起聊天, 突然一个小男孩来到我面前, 说:“阿姨,送给你。” 那是两朵小小的花,真真小,估...
    兜兜转转123阅读 194评论 0 0