iOS 底层原理

关联对象

2020-12-23  本文已影响0人  天空像天空一样蓝

一、类添加成员变量与分类添加成员变量的区别

(一)类

// 定义 一个 MyPerson 类
@interface MyPerson : NSObject

@property (nonatomic, assign) int age;

@end

@implementation MJPerson

@end

在 MJPerson 类中,使用 @property (nonatomic, assign) int age 创建一个变量的话, 系统会默认为我们做三件事

1、生成 _name 成员变量
{
    int _age;
}

2、生成 get/set 方法的声明
- (void)setAge:(int)age;
- (int)age;

3、生成 get/set 方法的实现
- (void)setAge:(int)age {
    _age = age;
}
- (int)age {
    return _age;
}

(二)分类


// 定义 一个 MyPerson+Test 分类

@interface MyPerson (Test)

@property (nonatomic, assign) int weight;

@end

@implementation MyPerson (Test)

@end

在 MyPerson+Test 这个分类中,使用@property (nonatomic, assign) int weight; 创建一个变量 weight ,系统只会为我们做一件事情。

1、只会生成 get/set 方法的声明,不会生成 get/set 方法的实现。
- (void)setWeight:(int)weight;
- (int)weight;

那么,我们自己实现 成员变量的 getter和setter 方法呢?

图片.png

上面的代码可以看出,直接报错,提示说,实例变量不能放在分类里面。

(三)手动实现 分类的 setter 和 getter 方法

@interface MyPerson (Test)
@property (nonatomic, assign) int weight;
@end

@implementation MyPerson (Test)
- (void)setWeight:(int)weight {

}
- (int)weight {
    return 0;
}
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {

        MyPerson *person = [[MyPerson alloc] init];
        person.age = 18;
        person.weight = 60;

        NSLog(@"age is %d, weight is %d", person.age, person.weight);

    }
    return 0;
}

运行上面的代码,打印结果为age is 18, weight is 0,为什么我们赋值给 weight = 60 这个不好使,但是 age =18 就能有效果呢?

person.age = 18; 这句代码,相当于把 18 赋值给了_age 这个成员变量。
person.age 就是去getAge 直接返回了 _age 这个变量的值。

person.weight = 60 这句代码,可以看到分类的实现里面,并没有保存 _weight 这个成员变量,
person.weight,分类代码里写死的 返回 0 ,所以直接返回结果 0

二、保存分类的成员变量的值

(一)使用全局变量保存

对于上面的 demo ,不能保存 _weight 的值,我们试想下,可以单独写一个全局变量来保存外面传进来的值

int weight_;

@implementation MyPerson (Test)

- (void)setWeight:(int)weight {
    weight_ = weight;
}

- (int)weight {
    return weight_;
}

@end

运行上面的代码,的确能做到外面的值保存下来,但是存在问题,相当于一个 全局的变量,创建多个对象,会产生多个对象公用一个 weight 变量。

        MyPerson *person = [[MyPerson alloc] init];
        person.age = 18;
        person.weight = 60;
        
        MyPerson *person1 = [[MyPerson alloc] init];
        person1.age = 23;
        person1.weight = 100;
        
        // age is 18, weight is 100
        NSLog(@"age is %d, weight is %d", person.age, person.weight);
        // age is 23, weight is 100
        NSLog(@"age is %d, weight is %d", person1.age, person1.weight);
        

(二)使用dictionary 保存

NSMutableDictionary *weightDic_;

@implementation MyPerson (Test)

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

- (void)setWeight:(int)weight {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    weightDic_[key] = @(weight);
}

- (int)weight {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    return [weightDic_[key] intValue];
}
@end

运行上面的代码打印结果:
2020-12-21 23:31:44.820168+0800 MyTestDemo[29582:5762922] age is 18, weight is 60
2020-12-21 23:31:44.820785+0800 MyTestDemo[29582:5762922] age is 23, weight is 100

看到打印结果给人的感觉像是和系统生成的实现方法一样,但是内部却大有不同

(三)使用关联对象方法

主要使用 Runtime 的 API ,来修改 成员变量 name 的值。

@interface MyPerson (Test)
@property (nonatomic, copy) NSString *name
@end

#import <objc/runtime.h>

@implementation MyPerson (Test)

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

- (NSString *)name {
  return objc_getAssociatedObject(self, @selector(name));
}
@end
MyPerson *person = [[MyPerson alloc] init];
person.age = 18;
person.name = @"tom";

MyPerson *person1 = [[MyPerson alloc] init];
person1.age = 23;
person1.name = @"yang";
        
NSLog(@"age is %d, name is %@", person.age, person.name);
NSLog(@"age is %d, name is %@", person1.age, person1.name);
        
打印结果为:
2020-12-21 23:55:49.461183+0800 MyTestDemo[29679:5770200] age is 18, name is tom
2020-12-21 23:55:49.461634+0800 MyTestDemo[29679:5770200] age is 23, name is yang

三、管理对象剖析

(一)为什么要使用关联对象

在上面的例子中可以有多种方法实现:

// 1、使用 static const void * 作为key
static const void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)

// 2、使用 static char 作为key
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)

// 3、直接使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(obj, @"property");

// 4、使用get方法的@selecor作为key,或者在get方法中使用_cmd,objc_getAssociatedObject(obj,_cmd);
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))

上面的几种方法,我们使用最多和最方便的就是第4种方案。

(二)关联对象 API

1、添加关联对象 objc_setAssociatedObject

底层源码

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

objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)

策略主要对应的使用方法

修饰符 objc_AssociationPolicy
assign OBJC_ASSOCIATION_ASSIGN
strong,nonatomic OBJC_ASSOCIATION_RETAIN_NONATOMIC
copy,nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC
strong,atomic OBJC_ASSOCIATION_RETAIN
copy,atomic OBJC_ASSOCIATION_COPY

2.获取关联对象objc_getAssociatedObject

objc_getAssociatedObject(id _Nonnull object_, const void * _Nonnull 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) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}

3、移除关联对象 objc_removeAssociatedObjects

objc_removeAssociatedObjects(id _Nonnull 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());
}

(三) 关联对象的原理

分析上面的源码,得到实现关联对象技术的核心对象有下面几个

1、AssociationsManager

//  AssociationsManager
class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

2、AssociationsHashMap

//  AssociationsHashMap
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

3、ObjectAssociationMap

// ObjectAssociationMap
    class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

4、ObjcAssociation

class ObjcAssociation {
        uintptr_t _policy;
        id _value;
    public:
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        ObjcAssociation() : _policy(0), _value(nil) {}

        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }
        
        bool hasValue() { return _value != nil; }
    };

这四个函数对应代码的关系如下

图片.png

思考

思考:关联对象的成员变量存放在哪里

上一篇下一篇

猜你喜欢

热点阅读