0
点赞
收藏
分享

微信扫一扫

Objective-C Associated Objects的知识点

云上笔记 2021-09-29 阅读 27

一、前言

日常开发中,用runtime的在分类中添加属性可以说是最常见的操作了。但是往往越常用的东西,你约容易忽视一些细节。
关于category关联对象的技术干货网上比比皆是,我这里也给大家推荐一篇:iOS底层原理总结 - 关联对象实现原理
这篇文章只是把相关的知识点列出来,或者说是容易疏忽的地方,给大家分享一下。

二、正文

1 protocol 和 category 中使用@property的区别?

1.1 protocol中添加属性

平时我们常用的是在协议中添加方法进行反向传指等灯,但是很少见到在协议中添加属性。
首先明确一点,协议中是可以添加属性的,网上很多错误的资料可能会误导大家说“协议中不能定义属性和成员变量,只能添加方法”,这是不对的。

在protocol中使用@property只会生成setter/getter的方法声明,在实现协议的类中要加入协议中property的实现:
1 自动实现:@synthesize name;  手动实现:-(void)setName: 和 -(NSString *)name

但是在协议中定义属性时,涉及到另外一个知识点:autosynthesis(自动合成)

例:声明一个协议,并在协议中定义一个属性name

自动实现:在ViewController的@implementation中添加@synthesize name;即可。

#import "ViewController.h"
@protocol testDelegate <NSObject>
@property (nonatomic, strong) NSString *name;
@end

@interface ViewController () <testDelegate>
@end

@implementation ViewController
@synthesize name; // 自动实现
@end

2 手动实现:这里涉及到自动合成成员变量,首先明确一下什么情况下系统不会autosynthesis(自动合成)

1. 同时重写了 setter 和 getter 时
2. 重写了只读属性的 getter 时
3. 使用了 @dynamic 时
4. 在 @protocol 中定义的所有属性
5. 在 category 中定义的所有属性
6. 重载的属性

在这里因为手动实现时要同时重写setter和getter方法,所以我们要自己声明成员变量@synthesize name

#import "ViewController.h"
@protocol testDelegate <NSObject>
@property (nonatomic, strong) NSString *name;
@end

@interface ViewController () <testDelegate>
@end

@implementation ViewController
@synthesize name = _name;
- (void)setName:(NSString *)name {
    _name = name;
}
- (NSString *)name {
    return _name;
}
@end

否则系统会报错:Use of undeclared identifier '_name'

1.2 category中添加属性

在分类中添加属性同样也有两种方法:1 使用静态全局变量 2 动态关联对象

例:给Baby类扩展一个字符串属性idz

#import "Baby.h"
@interface Baby (Extension)
@property (nonatomic, copy) NSString *idz;
@end

方法1:使用静态全局变量

#import <objc/runtime.h>
static NSString *_idz;
@implementation Baby (Extension)
- (NSString *)idz {
    return _idz;
}
-(void)setIdz:(NSString *)idz {
    _idz = [idz copy];
}

这样_idz静态全局变量与类并没有关联,无论对象创建与销毁,只要程序在运行_idz变量就存在,并不是真正意义上的属性。

方法2:runtime动态关联对象

#import <objc/runtime.h>
@implementation Baby (Extension)
- (NSString *)idz {
    NSString *idz = objc_getAssociatedObject(self, @selector(idz));
    return idz;
}
-(void)setIdz:(NSString *)idz {
    objc_setAssociatedObject(self, @selector(idz), idz, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

2 key关键字的写法

objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

id object : 给哪个对象添加属性,这里要给自己添加属性,用self。
id *key : 属性名,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。
id value : 关联的值,也就是set方法传入的值给属性去保存。
objc_AssociationPolicy policy : 策略,属性以什么形式保存。

其他三个参数并没有太多需要介绍的,除了策略需要了解一下以外,我们往往会忽视key值的写法。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一个弱引用相关联的对象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相关对象的强引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相关的对象被复制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相关对象的强引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相关的对象被复制,原子性   
};

其实在关联对象时,key关键字的写法有很多种:

1 static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

2 static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)

3 使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

4 使用get方法的@selecor作为key
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))

我个人是推荐使用最后一种(getter名)的方式,因为这样你就不用多声明一个变量名,且相对安全。这里给大家例举多种写法是为了在阅读别人代码时不至于看不明白。

3 关联对象的移除

这本不是什么难点,只是大家往往会忽视的一个知识点,关联对象的移除有两种:

// 1 移除所有关联对象
objc_removeAssociatedObjects(self);
// 2 移除某个已有的关联对象
objc_setAssociatedObject 传入 nil 则可以移除已有的关联对象

我们一般不会采用第一种方法去移除所有的关联对象,这样做会造成一些潜在的隐患,尤其是你在维护别人的代码的时候,除非你非常的自信,否则并不建议这样做。

所以一般的做法是通过给 objc_setAssociatedObject 函数传入 nil 来移除某个已有的关联对象。

4 关联对象的原理

这个知识点,大多数人都是只会使用,但是从来没有了解过其中的原理,因为runtime的源码是开源的,所以感兴趣的同学可以去翻看一下源码,了解一下对象和key、policy和value之间到底是怎么联系在一起的?在分类中添加的属性又存放在什么地方?通过runtime获取类中属性的时候是否可以获取到?获取类中方法的时候时候可以找到属性的setter和getter方法?
上面列举的这类问题,都可以提升你对关联对象这个知识点的理解。

这里给大家分享两张图片,如果你去学习了关联对象的原理后,这两张图片会帮助你理解和记忆


举报

相关推荐

0 条评论