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

请添加图片描述

✅作者简介:大家好我是瓜子三百克,一个非科班出身的技术程序员,还是喜欢在学习和开发中记录笔记的博主小白!
📃个人主页:瓜子三百克的主页
🔥系列专栏: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)

在这里插入图片描述

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,关键代码如下:
在这里插入图片描述通过源码解析,我们可以知道,在调用关联对象api的:

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

各个核心对象的关系图如下:
在这里插入图片描述总结:
1、关联对象并不是存储在被关联对象本身内存中。
2、关联对象存储在全局的统一的一个AssociationsManager中。
3、设置关联对象属性为 nil,就相当于是移除 ObjectAssociationMap 中对应属性的关联对象
4、当调用 void objc_removeAssociatedObjects(id object) 时,是移除 object 对象中的所有关联对象。即移除 AssociationsHashMap 对象中,以 DISGUISE(object)
为键的 ObjectAssociationMap 值。

四、拓展

1、Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果。
使用关联对象的方式实现给Category添加成员变量。


**🏆结束语🏆 **

最后如果觉得我写的文章对您有帮助的话,欢迎点赞✌,收藏✌,加关注✌哦,谢谢谢谢!!

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 34
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瓜子三百克

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值