运行时(Runtime)篇幅三
“属性”是Objective-C的一想特性,用于封装对象中的数据。Objective-C对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过存取方法来访问即getter方法用来读取变量值;setter用于写入变量值。
但本篇内容并不主要讲解属性
,而是针对性的讲解objc_category *Category
。之所以从属性开始讲起那必然是因为Category
跟属性有着一定的关联性。
在开发过程中类中经常容易填满各种方法
,把代码按逻辑划入几个分区中
正是Category
的用意。通过Category
机制可以把代码分成很多个易于管理的小块,以便单独检视
,更为调试带来便利,可以通过分类名称就能很容易精确定位到类中的方法所属的功能区
。
Category
之所以能分散功能模块代码,主要还是依仗着能创建私有方法
。如此一来就能实现一些在主类中没有实现的功能,来增加主类的功能模块。创建方法还要一个作用就是当访问其他类的私有方法时
,要是该私有方法未实现时必然是会报错的,这时在分类中声明这些方法(不必提供方法实现),则能避免编译器产生的警告。这一功能称为创建私有类方法的前向引用
。
Category
还有一个作用是向对象添加非正式协议。Category被称为非正式协议
(protocol被称为“正式协议”)可以给原有的类增加新的方法而不需要重新建一个类,然后再原有的类的基础上使用这个方法。
虽然Category
在技术上来讲可以实现在分类里声明属性,但这种做法还是要尽量避免,毕竟在一般的项目中也极少会碰。之所以要避免是因为在分类中无法把实现属性所属的实例变量合成出来。这里指的合成即上面提到的生成getter和setter方法。没有getter``setter
方法的支持自然也就无法去设置和读取相关实例变量的值。也可以继续通过objc_category
在Runtime库中的定义来做进一步的分析:
struct objc_category {
char *category_name
char *class_name
struct objc_method_list *instance_methods
struct objc_method_list *class_methods
struct objc_protocol_list *protocols
}
- category_name:分类名称
- class_name:所属类的类名
- instance_methods:实例方法列表
- class_methods:类方法列表
- protocols:协议
跟objc_class
的结构体做比较后可以发现,objc_category
内并没有相关ivars的内容,在篇幅二
中讲解到ivars
跟属性是密切相关的,没有ivars
的支持那自然就跟属性也没有多大的关系了。但是在提及是是这样说的:技术上来讲可以实现在分类里声明属性。接下来就讲下接具体的实现流程。
既然关键因素是不会自动生成getter
和setter
方法,那是否可以通过手动的方式来实现对应的方法的呢?答案肯定是“是”,这也是Runtime的功能之一。还是拿Person类来举例已有分类Person+More以下是类和分类的主要内容:
- 首先是导入Runtime头文件:#import <objc/runtime.h>
- 添加需要的属性 例:@property (nonatomic, copy) NSString *name;
- 实现关键性方法
static void *secName = &secName;
- (void)setSecondName:(NSString *)secondName {
objc_setAssociatedObject(self, secName, secondName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)secondName {
return objc_getAssociatedObject(self, secName);
}
上方提到的不会自动生成getter和setter方法
指的是不会对属性做进一步的实现,但声明的属性系统还是会自动提供对应的set
和get
方法。因此实现部分的内容才是重点:
// set
objc_setAssociatedObject(self, secName, secondName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// get
objc_getAssociatedObject(self, secName);
这两个方法都是Runtime中的方法,因此在Runtime库中也能查找对应的方法内容:
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
-
id _Nonnull object: 表示关联者 关联的对象 一般都是当前对象 self
-
const void * _Nonnull key :给定一个指向字符串的指针-runtime会给该指针赋值,添加额外信息的key,然后用这个key取值
-
id _Nullable value:额外信息的值
-
objc_AssociationPolicy policy:关联策略 相当在声明属性是设置的属性关键字 当该关联的对象被释放时,会根据policy的类型来决定释放释放关联对象
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
}
通过以上步骤即可完成对属性的关联,虽然Runtime还提供了一个objc_removeAssociatedObjects的方法用于移除关联的对象。但是需要注意的是该方法会将全部关联的对象都移除,这就很容易把其他一些本不该移除的对属性给移除了,如果本身就用不到该属性本可不必声明并关联。
该功能是Runtime中比较有特色的一个功能,就如其特性把在分类中声明的属性与Runtime进行关联即-关联对象。
转载自:https://juejin.cn/post/7090150106141294628