【iOS】——分类,扩展以及关联对象

1. 分类(Category)和扩展(Extension)

1) 分类(运行期)
  • 分类就是为已经存在的类,在运行时添加新的方法的一种机制。
  • 分类可以添加协议,类方法,实例方法和属性,不能添加实例变量。
  • 添加的属性就不会生成成员变量,也不会生成setter/getter方法,需要手动添加setter/getter方法。
2)扩展(编译期)
  • 扩展和分类很像,扩展只有声明部分,扩展中的定义的方法需要在类的实现部分去实现。

  • 可以定义协议,类方法,实例方法,属性和实例变量,在编译期将其中的定义的数据加到该类的数据类表中。如果不实现其中的方法,就会报错。

  • 由于系统的类的是实现部分不对用户开放,所以不能给系统的类添加扩展。

3)区别
  • 扩展在编译期决定,是类的一部分,和类的声明部分和实现部分共同组成类,伴随着类的产生而产生,消亡而消亡。扩展也可以用来隐藏类的私有部分。
  • 分类是在运行期决议的,扩展可以添加实例变量,分类不能添加实例变量,原因是在运行时,对象的内存布局就已经确定好了,如果添加实例变量,会破坏类的内存布局。

2. 分类的实质

1)分类原理
typedef struct category_t *Category;
struct category_t {
    const char *name; // 分类名
    classref_t cls; // 类
    struct method_list_t *instanceMethods;  // 实例方法
    struct method_list_t *classMethods; // 类方法
    struct protocol_list_t *protocols;  // 协议
    struct property_list_t *instanceProperties; // 实例属性
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;   // 类属性

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

发现里面存在实例方法列表,类方法列表,实例属性列表与类属性列表。

2)分类的加载流程
  1. 在编译阶段将分类中的方法、属性等编译到一个数据结构category_t
  2. 将分类中的方法、属性等合并到一个大数组中,后面参加编译的分类会在数组的前面。
  3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面。

也就是说当分类中的方法与原始类中的方法重名时,会先去调用分类中实现的方法。

2. 关联对象

通过关联对象给分类添加属性

动态添加
 objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

  • 参数一:id object : 给哪个对象添加属性,这里要给自己添加属性,用self

  • 参数二:void * == id key : key值,根据key获取关联对象的属性的值,在objc_getAssociatedObject中通过次key获得属性的值并返回。

  • ** 参数三:**id value : 关联的值,也就是set方法传入的值给属性去保存。

  • 参数四:objc_AssociationPolicy policy : 策略,属性以什么形式保存。

取值
 objc_getAssociatedObject(id object, const void *key);

  • 参数一:id object : 获取哪个对象里面的关联的属性。
  • 参数二:void * == id key : 什么属性,与objc_setAssociatedObject中的key相对应,即通过key值取出valu
移除关联对象
 - (void)removeAssociatedObjects
{
    // 移除关联对象
    objc_removeAssociatedObjects(self);
}

应用
//分类.h文件
#import "LGPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson (cat)
@property (nonatomic, copy) NSString *lg_nickname;
@property (nonatomic, copy) NSString *name;
@end

NS_ASSUME_NONNULL_END

//分类.m文件
#import "LGPerson+cat.h"
#import <objc/runtime.h>
@implementation LGPerson (cat)
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *) name {
    return objc_getAssociatedObject(self, _cmd);
}
@end

  
//main文件
#import <Foundation/Foundation.h>
#import "LGPerson+cat.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LGPerson *t = [[LGPerson alloc] init];
        t.name = @"1";
        NSLog(@"%@", t.name);
        
    }
    return 0;
}

运行结果:
在这里插入图片描述

总结:

  1. 分类中没有成员列表,因此不能添加成员变量。但是有属性列表,可以添加属性的声明,但是不会合成set与get方法,如果要使用分类中的属性,需要使用关联对象。
  2. 分类在运行的时候被整合到类中,扩展在编译的时候被整合到类中,因此分类中的方法不实现不会报错,扩展就会报错。
  3. 扩展用于声明私有属性与方法。
  4. 分类中的方法和类中的方法重名,分类中的方法会代替类中的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值