OC底层原理(七):Category添加属性

category添加属性基本用法

新创建一个命令行项目,创建ZJPerson类和ZJPerson(Study)分类

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

@implementation ZJPerson

@end
@interface ZJPerson (Study)
@property (nonatomic, copy) NSString *bookName;
@end

@implementation ZJPerson (Study)
- (void)setBookName:(NSString *)bookName {
    objc_setAssociatedObject(self, @selector(bookName), bookName, OBJC_ASSOCIATION_COPY);
}

- (NSString *)bookName {
    return objc_getAssociatedObject(self, @selector(bookName));
}
@end

这样分类就算间接完成添加属性的功能,我们在main函数中使用一下

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZJPerson *person = [[ZJPerson alloc]init];
        person.age = 10;
        person.bookName = @"How to study";
        NSLog(@"age is: %@ \n bookName is: %@", @(person.age), person.bookName);
    }
    return 0;
}
截屏2021-01-18 21.27.01.png

可以看到分类添加的属性使用效果和在类里直接添加的属性效果一样


category添加属性的底层原理

我们打开源码,搜索出objc_setAssociatedObject(, 找到这个方法的源码

void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    _object_set_associative_reference(object, key, value, policy);
}

点击进入_object_set_associative_reference方法

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;

    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    //类似于当前对象的内存地址
    DisguisedPtr disguised{(objc_object *)object};
    //创建ObjcAssociation实例
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        AssociationsManager manager;
        //获取AssociationsManager实例下的AssociationsHashMap实例
        AssociationsHashMap &associations(manager.get());

        if (value) {
            //判定AssociationsHashMap是否有当前对象的数据
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            //根据key来寻找ObjcAssociation实例,没有的话直接添加属性的值
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                //替换属性的值
                association.swap(result.first->second);
            }
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

上面这段源码怎么理解呢
大概意思就是有AssociationsManager这么一个类,它的内部维护了一个全局的字典AssociationsHashMap
AssociationsHashMap字典的key对应的是disguised(object),类似于当前对象的内存地址,而value存储的是ObjectAssociationMap字典
ObjectAssociationMap字典的key对应的是添加的属性的名字,value呢,则对应的是ObjcAssociation实例
ObjcAssociation实例则存储着添加属性的值和策略

结构如下图所示

截屏2021-01-21 21.22.27.png

我们以前面的代码来举个例子

@interface ZJPerson (Study)
@property (nonatomic, copy) NSString *bookName;
@end

@implementation ZJPerson (Study)
- (void)setBookName:(NSString *)bookName {
    objc_setAssociatedObject(self, @selector(bookName), bookName, OBJC_ASSOCIATION_COPY);
}

- (NSString *)bookName {
    return objc_getAssociatedObject(self, @selector(bookName));
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZJPerson *person = [[ZJPerson alloc]init];
        person.bookName = @"How to study";
    }
    return 0;
}

ZJPerson在study分类里添加了一个属性bookName,在main函数中给person实例的bookName属性赋值了@"How to study",那么系统是怎么存储这个属性的值呢?

  • 首先找到AssociationsManager实例里的AssociationsHashMap字典
  • 通过disguised(person)用person的地址做一次运算计算出key,来获取person对象的ObjectAssociationMap字典
  • 然后在ObjectAssociationMap字典里通过,bookName来取出ObjcAssociation值对象
  • 将@"How to study"和OBJC_ASSOCIATION_COPY存入ObjcAssociation对象里
    至此,就将值存储起来了


    截屏2021-01-21 21.35.47.png

    比如我再给study分类添加一个属性

@interface ZJPerson (Study)
@property (nonatomic, copy) NSString *bookName;
@property (nonatomic, assign) CGFloat studyTime;
@end

@implementation ZJPerson (Study)
- (void)setBookName:(NSString *)bookName {
    objc_setAssociatedObject(self, @selector(bookName), bookName, OBJC_ASSOCIATION_COPY);
}

- (NSString *)bookName {
    return objc_getAssociatedObject(self, @selector(bookName));
}

- (void)setStudyTime:(CGFloat)studyTime {
    objc_setAssociatedObject(self, @selector(studyTime), @(studyTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (CGFloat)studyTime {
    return [objc_getAssociatedObject(self, @selector(studyTime)) floatValue];
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZJPerson *person = [[ZJPerson alloc]init];
        person.bookName = @"How to study";
        person.studyTime = 2.0;
    }
    return 0;
}

其存储结构如下


截屏2021-01-21 21.45.17.png

在例如,我们在main函数中创建两个person对象

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        ZJPerson *person1 = [[ZJPerson alloc]init];
        person1.bookName = @"How to study";
        
        ZJPerson *person2 = [[ZJPerson alloc]init];
        person2.bookName = @"How to read";
    }
    return 0;
}

则其存储结构如下


截屏2021-01-21 21.51.24.png

面试题

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

  • category不能直接添加成员变量,但是可以间接实现添加的效果
  • 通过objc_setAssociatedObject和objc_getAssociatedObject两个方法来实现添加的效果
  • 其底层原理参见上面的分析

你可能感兴趣的:(OC底层原理(七):Category添加属性)