美文网首页
笔记-关联对象

笔记-关联对象

作者: 强子ly | 来源:发表于2019-07-26 19:28 被阅读0次

目录

  • 面试题
  • 伪代码实现
  • Category添加属性的几种用法
  • Category为什么不能添加成员变量
  • 底层源码解读

1、面试题

面试题一、Category能否添加成员变量?如果可以,如何给Category添加成员变量?

- 不能直接给Category添加成员变量
  因为Category是在运行时决定的。编译时,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局。
  Category中使用@property只会生成setter和getter方法的声明,并不会自动生成实例变量以及存取方法

- 可以间接实现Category有成员变量的效果
  可以通过RunTime中关联对象在Category中为类添加成员变量

2、伪代码实现

我们知道,如果我们自己手动实现setter/getter方法,剩下的就是一个成员变量的存取了,下面用两种方法实现一下,看看是否存在瑕疵

  • 创建全局对象(❌)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LYPerson *p1 = [[LYPerson alloc] init];
        p1.name = @"1";

        LYPerson *p2 = [[LYPerson alloc] init];
        p2.name = @"2";

        NSLog(@"p1 name = %@ ;  p2 name = %@", p1.name, p2.name);
    }
}
@implementation LYPerson (Test)

NSString *name_;

- (void)setName:(NSString *)name {
    name_ = name;
}

- (NSString *)name {
    return name_;
}

@end

2019-07-24 22:17:32.291885+0800 test[88511:509458] p1 name = 2 ;  p2 name = 2

结论:如果创建多个实例对象,那么这些对象将公用一个全局对象,后面的将会把前面的值覆盖掉
  • 创建全局字典
@implementation LYPerson (Test)

NSMutableDictionary *names_;

+ (void)load {
    names_ = [NSMutableDictionary dictionary];
}

- (void)setName:(NSString *)name {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    weights_[key] = key;
}

- (NSString *)name {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    return weights_[key];
}

@end

- 能实现,但会存在问题,例如线程安全,比较麻烦

3、基本用法

  • 用法一
#import "LYPerson+Test.h"
#import <objc/runtime.h>

@implementation LYPerson (Test)

static const char LYNameKey;

- (void)setName:(NSString *)name {
    /**
     对象关联

     @param object 关联对象
     @param key
     @param value  关联值
     @param policy 关联策略
               objc_AssociationPolicy                   对应修饰符
            - OBJC_ASSOCIATION_ASSIGN = 0,              assign
            - OBJC_ASSOCIATION_RETAIN_NONATOMIC         strong, nonatomic
            - OBJC_ASSOCIATION_COPY_NONATOMIC           copy, nonatomic
            - OBJC_ASSOCIATION_RETAIN                   strong, atomic
            - OBJC_ASSOCIATION_COPY                     copy, atomic
     @return
     */
    objc_setAssociatedObject(self, &LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, &LYNameKey);
}

@end
  • 用法二
#define MJNameKey @"name"

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, LYNameKey);
}

// @"name" 直接写出来的字符串,放在常量区
  • 用法三
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

//隐式参数:get方法可以用_cmd代替;_cmd = @selector(name)

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

- 优点:1、可读性更高,2、有编译器提示

5、为什么不能添加成员变量

// Category在内存中的结构
struct _category_t {
    const char *name;      // 类名
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; // 实例方法列表
    const struct _method_list_t *class_methods;    // 类方法列表
    const struct _protocol_list_t *protocols;      // 协议列表 (分类里面也能遵守协议)
    const struct _prop_list_t *properties;         // 属性列表 (分类里能实现属性)
};

- 分类底层结构决定了他不能添加成员变量,没有数组是用来存放成员变量的
- 用关联对象的方式给分类添加属性
  不是存储在类对象、实例对象中,所以不会影响类对象、实例对象在内存中的存储结构

6、底层源码解读(待完善)

实现关联对象技术的核心对象有

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation
总结:
- 关联对象并不是存储在被关联对象本身中
- 关联对象存储在全局的统一的一个AssociationManager中
- 设置关联对象为nil,就相当于移除关联对象
// 添加
objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
// 获取
objc_getAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>)
// 移除
objc_removeAssociatedObjects(<#id  _Nonnull object#>)

objc_setAssociatedObject

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


void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

通过上图我们可以总结为:一个实例对象就对应一个ObjectAssociationMap,而ObjectAssociationMap中存储着多个此实例对象的关联对象的key以及ObjcAssociation,为ObjcAssociation中存储着关联对象的value和policy策略。
由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。

通过分析可以得出:一个实例对象会有一个 AssociationsManager 操作类和一个存储Map ObjectAssociationMap


objc_getAssociatedObject

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}


id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}

objc_removeAssociatedObjects

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}


void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

相关文章

  • 笔记-关联对象

    目录 面试题 伪代码实现 Category添加属性的几种用法 Category为什么不能添加成员变量 底层源码解读...

  • iOS 关联对象笔记

    方法 三个方法的作用分别是: 以键值对形式添加关联对象 根据 key 获取关联对象 移除所有关联对象 例子 下面A...

  • Swift 为分类增加属性objc_getAssociated

    OC 获取关联对象 Swift 获取关联对象——错误的写法 Swift 获取关联对象——正确的写法 设置关联对象 ...

  • iOS runtime关联对象 objc_setAssociat

    关联对象的作用: 关联对象可以给某个对象关联一个或者多个其他对象,这些对象通过健来区分。 创建存储关联对象objc...

  • RunTime学习笔记——关联对象

    现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。 这种情况的一般解决办法就是继承...

  • 关联对象

    关联对象的方式 关联对象源码基本思路 关联对象的结构:AssociationsHashManager // Ass...

  • 关联对象

    关联对象原理 关联对象并不是存储在被关联对象本身内存中,关联对象存储在全局的统一的一个AssociationsMa...

  • iOS 关联对象

    概述 关联对象顾名思义,就是给对象关联对象的意思,一个对象可以关联多个其他对象,这些对象通过key来区分,存储对象...

  • 关联对象

    关联对象会用被关联对象作为key,将关联对象存储到全局的哈希表里。 AssociationHashMap Asso...

  • iOS关联对象技术原理

    iOS关联对象技术原理 iOS关联对象技术原理

网友评论

      本文标题:笔记-关联对象

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