OC:在分类中添加成员变量,原来帮我们做了那么多事

简介: 本篇文章告诉你,如果在类对象和Category对象中添加一个属性时,底层做了什么。关联对象如何给Category对象添加成员变量,关联对象的实现原理。

一、添加属性


1.1、类中添加属性

在类中添加一个 name 属性时,具体做了哪些事呢?


1、定义一个 _name 成员变量

2、分别生成一个setter 方法和一个 getter 方法的声明

3、生成以上两个方法的具体实现。


如在 Person.h 添加一个 age 属性:


@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

编译后,代码会直接帮我们生成如下结构:

Person.h :


@interface Person : NSObject {
    int _age;
}
- (void)setAge:(int)age;
- (int)age;
@end


Person.m :


#import "Person.h"
@implementation Person
- (void)setAge:(int)age {
    _age = age;
}
- (int)age {
    return _age;
}
@end


1.2、Category对象中添加属性

那么在Category中添加属性呢?他只会帮我们生成方法的声明,类似如下:


- (void)setAge:(int)age;
- (int)age;


没有变量和方法的实现。看来,在Category中是无法直接通过声明属性的方式记录成员变量。


那么就无法在Category中添加属性了吗?

在默认情况下,因为Category底层结构的限制,不能添加成员变量到Category中。但可以通过关联对象来间接实现。


二、关联对象


2.1、关联对象API

关联对象提供了以下API:


// 添加关联对象
// object:对象,即我们要给哪个对象关联属性的对象。
// key:定义的检索键
// value:关联的属性值,即 setter 方法传进来的值
// policy:关联策略,见下表
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)
// 获得关联对象
id objc_getAssociatedObject(id object, const void * key)
// 移除所有的关联对象
void objc_removeAssociatedObjects(id object)

ac5637892a0b4eb7a94dd8026a74460c.png

2.2、key的多种赋值方式

通过关联对象来间接实现Category也能添加成员变量。

key有多种赋值方式,我们来看下key的几种常见用法:


1、赋值变量key为自己的地址值

// static:设置变量的作用域为当前文件
static void *MyKey = &MyKey;// MyKey对象必须赋值,否者默认为0,当设置多个成员变量会有问题。
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

2、定义key为 char 类型(1个字节),减小占用字节

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))
// _cmd:表示当前方法 @selector
// _cmd == @selector(getter)
objc_getAssociatedObject(obj,_cmd)

2.3、实战引用

新建 Person 类 和 Person+Test 分类


1、 Person.h:

#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end


2、 Person.h:


#import "Person.h"
@implementation Person
@end


3、Person+Test.h:


#import "Person.h"
@interface Person (Test)
@property (assign, nonatomic) int weight;
@property (copy, nonatomic) NSString* name;
@end


4、Person+Test.m:


#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
    // 隐式参数
    // _cmd == @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setWeight:(int)weight {
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)weight {
    // _cmd == @selector(weight)
    return [objc_getAssociatedObject(self, _cmd) intValue];
}
@end


5、引用赋值


#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+Test.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 10;
        person.name = @"lisa";
        person.weight = 110;
        Person *person2 = [[Person alloc] init];
        person2.age = 20;
        person2.name = @"tony";
        // person2.name = nil;
        person2.weight = 220;
        NSLog(@"person>>> age:%d, name:%@, weight:%d", person.age, person.name, person.weight);
        NSLog(@"person2>>> age:%d, name:%@, weight:%d", person2.age, person2.name, person2.weight);
    }
    return 0;
}


三、关联对象的原理


实现关联对象技术的核心对象有:


AssociationsManager

AssociationsHashMap

ObjectAssociationMap

ObjcAssociation


objc4源码解读:objc-references.mm,关键代码如下:

720f9b38abdf47b88031e3f4baa2cda4.png

通过源码解析,我们可以知道,在调用关联对象api的:


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


各个核心对象的关系图如下:

48bf3ca94eee4e069b79fbebbf5a7c3f.png



总结:

1、关联对象并不是存储在被关联对象本身内存中。

2、关联对象存储在全局的统一的一个AssociationsManager中。

3、设置关联对象属性为 nil,就相当于是移除 ObjectAssociationMap 中对应属性的关联对象

4、当调用 void objc_removeAssociatedObjects(id object) 时,是移除 object 对象中的所有关联对象。即移除 AssociationsHashMap 对象中,以 DISGUISE(object)

为键的 ObjectAssociationMap 值。


四、拓展


1、Category能否添加成员变量?如果可以,如何给Category添加成员变量?

不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果。

使用关联对象的方式实现给Category添加成员变量。



相关文章
|
存储 编译器 C语言
02-结构体和OC类的内存对齐
02-结构体和OC类的内存对齐
88 0