likes
comments
collection
share

运行时(Runtime)篇幅三

作者站长头像
站长
· 阅读数 27

“属性”是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的支持那自然就跟属性也没有多大的关系了。但是在提及是是这样说的:技术上来讲可以实现在分类里声明属性。接下来就讲下接具体的实现流程。

既然关键因素是不会自动生成gettersetter方法,那是否可以通过手动的方式来实现对应的方法的呢?答案肯定是“是”,这也是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方法指的是不会对属性做进一步的实现,但声明的属性系统还是会自动提供对应的setget方法。因此实现部分的内容才是重点:

// 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
评论
请登录