美文网首页
第二篇:runtime的实际应用场景——给分类里的属性绑定关联对

第二篇:runtime的实际应用场景——给分类里的属性绑定关联对

作者: 意一ineyee | 来源:发表于2017-08-16 16:55 被阅读26次

目录

一、分类里不能添加成员变量,但能添加属性
二、给分类里的属性绑定关联对象

本篇主要讲解runtime的实际应用场景:给分类里的属性绑定关联对象。是对成员变量和属性应用的一个例子。

一、分类里不能添加成员变量,但能添加属性


我们知道,分类一般是用来给一个类(无论是系统的类、三方的类还是自己写的类)扩展方法和扩展遵守协议用的,但是不能给类扩展属性

其实准确的说,分类不能给类扩展成员变量,但可以给类扩展属性,只不过编译器没有为扩展的属性自动生成与之相关的成员变量,也没有自动生成相应settergetter方法的实现,但是自动生成了settergetter方法的声明。(和@dynamic的功能比较像是吧)

我们举例来验证一下上面这个说法:

  • 分类不能给类扩展成员变量

我们在PersonCategory分类里添加一个成员变量_name,结果编译器直接报错Instance variables may not be placed in categories

可以得出结论:分类里不能添加成员变量

  • 分类能给类扩展属性

然后我们在PersonCategory分类里添加一个属性name,发现编译器并没有报错,仅仅是给了一个警告Property 'name' requires method 'name' and 'setName:' to be defined - use @dynamic or provide a method implementation in this category

可以得出结论:分类里能添加属性,但是没有自动生成相应settergetter方法的实现,编译器提示我们要自己实现

此时,我们打印一下Person类的成员变量列表、属性列表、实例方法列表。如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 获取Person类的成员变量列表
    NSLog(@"===========Person类的成员变量列表===========");
    unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList([Person class], &ivarCount);
    for (int i = 0; i < ivarCount; i ++) {
        
        Ivar ivar = ivars[I];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        NSLog(@"%@", ivarName);
    }
    free(ivars);

    
    // 获取Person类的属性列表
    NSLog(@"===========Person类的属性列表===========");
    unsigned int propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList([Person class], &propertyCount);
    for (int i = 0; i < propertyCount; i ++) {
        
        objc_property_t property = properties[I];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        
        NSLog(@"%@", propertyName);
    }
    free(properties);
    
    
    // 获取Person类的实例方法列表
    NSLog(@"===========Person类的实例方法列表===========");
    unsigned int methodCount = 0;
    Method *methods = class_copyMethodList([Person class], &methodCount);
    for (int i = 0; i < methodCount; i ++) {
        
        Method method = methods[I];
        NSString *methodName = NSStringFromSelector(method_getName(method));
        
        NSLog(@"%@", methodName);
    }
    free(methods);
}

打印结果:

2018-10-09 14:52:26.883166+0800 Test[3029:468752] ===========Person类的成员变量列表===========
2018-10-09 14:52:26.883317+0800 Test[3029:468752] ===========Person类的属性列表===========
2018-10-09 14:52:26.883400+0800 Test[3029:468752] name
2018-10-09 14:52:26.883477+0800 Test[3029:468752] ===========Person类的实例方法列表===========

发现Person类的成员变量列表为空,属性列表中有一个name,实例方法列表也为空(一个类的实例方法只有实现了才会被放入它的methodLists中,只声明是不会被放进去的)。

可以得出结论:分类里能添加属性,但是编译器没有为添加的属性自动生成与之相关的成员变量,也没有自动生成相应settergetter方法的实现

接着,我们去Person+Category.m文件中试图自己实现一下namesettergetter方法,发现方法名敲了一半它们竟然自动弹出来了,这就说明namesettergetter方法是被声明了的。

可以得出结论:分类里能添加属性,但是编译器没有为添加的属性自动生成与之相关的成员变量,也没有自动生成相应settergetter方法的实现,但是自动生成了settergetter方法的声明

  • 那为什么分类里不能添加成员变量,但能添加属性?

我们看下runtime库对分类(Category)的定义:

struct objc_category {
    
    // 分类名
    char *category_name                          OBJC2_UNAVAILABLE;
    // 分类所属的类名
    char *class_name                             OBJC2_UNAVAILABLE;
    // 分类扩展的实例方法列表
    struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE;
    // 分类扩展的类方法列表
    struct objc_method_list *class_methods       OBJC2_UNAVAILABLE;
    // 分类扩展的协议列表
    struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE;
}

可见分类结构体内部并没有用来存储扩展的成员变量的成员变量,只有用来扩展方法和遵守的协议的成员变量,所以分类不能添加成员变量。但是分类内部也没看到什么成员变量能用来存储属性啊,那为什么分类里能添加属性呢?

下载runtime的源码,找到objc-runtime-new.h文件,搜索category_t,我们可以看到分类一个更底层的实现:

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);
};

可以看到分类内部其实是有一个成员变量instanceProperties用来存储扩展的属性。

二、给分类里的属性绑定关联对象


  • 为什么要给分类里的属性动态绑定关联对象?

实际开发中,我们确实会遇到一些场景需要给分类添加属性,但是上面我们也知道了虽然分类里能添加属性,但是编译器没有为添加的属性自动生成与之相关的成员变量,也没有自动生成相应settergetter方法的实现,但是自动生成了settergetter方法的声明,所以如果我们给分类添加了属性就去访问的话,程序是会崩掉的,因为找不到settergetter方法的实现

例如:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    person.name = @"11";
    NSLog(@"%@", person.name);
}

崩溃信息:

2018-10-10 13:40:06.250510+0800 Test[1190:228046] -[Person setName:]: unrecognized selector sent to instance 0x600001dbef20

于是我们按照上面提到的编译器报的警告Property 'name' requires method 'name' and 'setName:' to be defined - use @dynamic or provide a method implementation in this category。考虑自己实现一下setter、getter方法:

#import "Person+Category.h"

@implementation Person (Category)

- (void)setName:(NSString *)name {
    
    
}

- (NSString *)name {
    
    
}

@end

但是当我们实现这两个方法的时候才发现根本无从实现啊,因为编译器根本没有为我们自动生成与属性相关的成员变量啊,方法的实现怎么写嘛。这个时候关联对象就派上用场了,我们可以用它来代替成员变量绑定到指定的属性上,借它来存取数据。

  • 什么是关联对象?

关联对象(Associated Object)和成员变量其实没什么本质上的区别,都是一个对象,可以用来存取数据。但是成员变量是直接存在于类的ivars中,而关联对象则存在于一个单独的哈希表中;成员变量在编译时就创建好,而关联对象则在运行时调用settergetter方法的时候才创建的;它没那么神秘,因此我们可以用它来代替成员变量绑定到指定的属性上,借它来存取数据。

  • 怎么给分类的属性动态绑定关联对象

很简单,就用到runtime的两个方法而已:

#import "Person+Category.h"
#import <objc/runtime.h>

@implementation Person (Category)

- (void)setName:(NSString *)name {
    
    // object:想要把关联对象动态绑定到哪个对象的属性上,一般填self
    // key:想要把关联对象动态绑定到哪个属性上,此处为@"name"
    // value:关联对象,调用setter方法时才动态生成的一个对象,此处为name
    // policy:关联对象的内存管理策略
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name {
    
    // object,key:想要获取哪个对象的哪个属性动态绑定的关联对象
    return objc_getAssociatedObject(self, @"name");
}

@end

好了,只要我们给分类的属性绑定了关联对象,就可以向平常的属性那样访问它们了。此外,如果我们给分类添加的属性不想让外界看到,也可以把属性添加在分类的延展中:

-----------Person+Category.m-----------

#import "Person+Category.h"
#import <objc/runtime.h>

@interface Person ()

@property (nonatomic, assign) NSInteger age;

@end

@implementation Person (Category)


@end

相关文章

网友评论

      本文标题:第二篇:runtime的实际应用场景——给分类里的属性绑定关联对

      本文链接:https://www.haomeiwen.com/subject/hmqrrxtx.html