美文网首页
OC底层原理(七):Category添加属性

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

作者: 跳跳跳跳跳跳跳 | 来源:发表于2021-01-21 21:59 被阅读0次

    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<objc_object> 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添加属性

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