美文网首页
iOS底层之关联对象

iOS底层之关联对象

作者: 大橘猪猪侠 | 来源:发表于2020-10-21 17:54 被阅读0次

首先我们来简单的描述一下分类的一些基本概念:
1、用来给类添加新方法
2、不能给类添加成员属性,添加了成员变量,也无法取到
3、注意:其实可以通过runtime给分类添加属性
4、分类中用@property定义变量,只会生成变量的getter,setter方法声明,不能生成方法实现和带下划线的成员变量。

那么问题来了, 分类是不能直接添加属性的,那么要给分类添加属性,那就需添加关联对象或者重写,在.m文件添加setter方法的实现。

请看下面代码,在main.m中添加了对Person类的分类,在分类中有两个属性,且在main函数中创建Person对象并对分类的属性赋值,其中,分类的属性利用runtime来实现了gettersetter方法。

#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "Person+LGEXT.h"

@interface Person (LG)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end

@implementation Person (LG)
- (void)cate_instanceMethod1{
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
    NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
    NSLog(@"%s",__func__);
}

- (void)setCate_name:(NSString *)cate_name{
/**
 1: 对象
 2: 标识符
 3: value
 4: 策略
 */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        person.cate_name = @"OC";
        NSLog(@"%p",person);
    }
    return 0;
}

我们首先在setter方法出打上断点,来看看它是否会执行这个方法:

iShot2020-10-21 16.00.06.png

可以看到,会执行这个方法,而当我们点击方法进去之后,你会发现,他们的整个方法实现都在一块,首先SetAssocHook,然后_base_objc_setAssociatedObject,之后就会执行_object_set_associative_reference方法。

iShot2020-10-21 16.02.12.png

_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};
    // 包装一下 policy - value
    ObjcAssociation association{policy, value};
    // retain the new value (if any) outside the lock.
    association.acquireValue();
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                object->setHasAssociatedObjects();
            }
            /* establish or replace the association */
            auto &refs = refs_result.first->second; // 空的桶子
            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);
                    }
                }
            }
        }
    }
    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

上面的代码很多,将部分代码收起来,可以看到一共就这么几行代码:


iShot2020-10-21 16.06.38.png

从下图可以看到,167行代码中的DisguisedPtr是对对象进行包装,

iShot2020-10-21 16.17.14.png

ObjcAssociation的作用同样是对policyvalue进行包装,其中value的值是上面代码对cate_name赋的值OC

iShot2020-10-21 16.18.48.png

看下图,acquireValue()的作用其实是对_valuepolicy的判断;

iShot2020-10-21 16.21.47.png

接下来就是172-201行的局部作用空间代码,如下图:


iShot2020-10-21 16.23.30.png iShot2020-10-21 16.24.16.png

首先看到AssociationsManager,看上图,它的重要的地方其实是方框中的c++代码。至于它的作用,下面我用一个例子来表示:
main.m改成main.mm,在main.m中写一个结构体:

struct Objc {
    Objc()   { printf("来了");}
    ~Objc()  {  printf("走了"); }
};

然后在函数中调用:

iShot2020-10-21 16.27.50.png

看最终执行完毕的结果:


iShot2020-10-21 16.28.09.png

这个函数的作用其实就是在程序开始时执行一次,在函数结束完毕后,再执行一次。而程序中是对代码进行加锁和解锁;它可以有多个这样的AssociationsManager

AssociationsHashMap从名字看,像一个哈希表,里面有很多关联对象,里买呢的类型类似于key-value健值对的形式存在;继续执行内部代码块:
当执行到if (refs_result.second) {行时,我们在控制台来看一下一些属性的值:

(lldb) p disguised
(DisguisedPtr<objc_object>) $0 = (value = 18446744069408092480)
(lldb) p association
(objc::ObjcAssociation) $1 = {
  _policy = 3
  _value = 0x0000000100004038 "OC"
}
(lldb) p manager
(objc::AssociationsManager) $2 = {}
(lldb) p associations
(objc::AssociationsHashMap) $3 = {
  Buckets = 0x0000000000000000
  NumEntries = 0
  NumTombstones = 0
  NumBuckets = 0
}
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $4 = {
  first = {
    Ptr = 0x0000000101006810
    End = 0x0000000101006850
  }
  second = true
}
(lldb) 

看到refs_result的时候,可以看到$4的类型很长,我们不用去管它,去看看给它赋值的函数try_emplace
看下图,它其实是分两部分,一部分是false,一部分是true,从判断中可以看出来,当TheBucket为空时,就会执行InsertIntoBucket,并返回true,当TheBucket有值之后,就会返回false

iShot2020-10-21 16.42.19.png

下图是当value有值的时候,程序执行的代码块:

iShot2020-10-21 17.17.46.png

根据代码执行的流程分析的结果,程序第一次进入try_emplace走的是InsertIntoBucket,而在第二次执行try_emplace走的同样是InsertIntoBucket方法:
也就是说,第一次是从哈希表读取refs_result,而disguised是传入的值,接着判断refs_result.second是不是第一次来,当结果为真,调用setHasAssociatedObjects()函数。
接着引用refs_result.firstsecond值,对引用的refs进行第二次调用try_emplace,接着插入ObjcAssociation的值。

下面是分别进入两次后的TheBucket的值:

(lldb) p TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $0 = 0x000000010068b5b0
(lldb) p TheBucket
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $1 = 0x0000000102014cd8
(lldb) 

接下来看一下当value为空时,程序执行else部分:

iShot2020-10-21 16.50.53.png

上图部分代码的作用其实就是将关联对象进行消除;

总结: 其实就是两层哈希map , 存取的时候两层处理(类似二位数组)

相关文章

  • iOS底层原理总结 - 关联对象实现原理

    iOS底层原理总结 - 关联对象实现原理 iOS底层原理总结 - 关联对象实现原理

  • iOS底层之关联对象

    首先我们来简单的描述一下分类的一些基本概念:1、用来给类添加新方法2、不能给类添加成员属性,添加了成员变量,也无法...

  • iOS底层之关联对象

    首先我们来回忆一个经典的面试题 Category能否添加成员变量?如果可以,如何给Category添加成员变量? ...

  • 探寻block的本质

    转自:探寻block的本质拓展:探寻OC对象的本质iOS底层原理总结 - 关联对象实现原理iOS底层原理总结 - ...

  • iOS底层探索之类的结构(上)

    回顾 在之前的几篇博客里面,我们知道了对象的本质是结构体(iOS底层探索之对象的本质和类的关联特性initIsa(...

  • iOS底层 -- Category本质之关联对象

    通过分类的底层结构我们可以看到,分类中可以存放实例方法,类方法,协议,属性,但是没有存放成员变量的地方。默认情况下...

  • iOS底层原理 - 关联对象

    面试题引发的思考: Q: Category能否添加成员变量?如果可以,如何给Category添加成员变量? 不能直...

  • iOS底层-Categroy、关联对象

    Categroy : 分类,也叫类别(请注意,和拓展是2个东西) Category 的作用 苹果推荐:1、为已存在...

  • iOS-底层-关联对象

    前两篇文章我们学习了关于Category的知识Category分类和load和initialize,现在再看一个问...

  • iOS底层-关联对象探索

    关联对象探索 其底层原理的实现,主要分为两部分: 通过objc_setAssociatedObject设值流程 通...

网友评论

      本文标题:iOS底层之关联对象

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