iOS 关联对象

在上一篇文章中我们理解了load&&initialize,Category---为什么只能加方法不能加属性这篇里面我讲到了分类不能添加属性的原因,今天我们来看在分类里面如何去添加属性

  • 全局变量方法

类结构

在TCPerson+TCtest1.h中

#import "TCPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface TCPerson (TCtest1)
@property (nonatomic, strong) NSString *name;
@end

NS_ASSUME_NONNULL_END

.m

#import "TCPerson+TCtest1.h"

@implementation TCPerson (TCtest1)
NSString *name_;

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

在.main中访问

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TCPerson *person = [[TCPerson alloc] init];
        person.name = @"jack";
        NSLog(@"person-----name----%@",person.name);
    }
    return 0;
}

输出结果:

2020-12-08 16:11:53.616007+0800 TCCateogry[1641:91590] person-----name----jack
Program ended with exit code: 0

缺点:当有多个对象时,name这个属性就不知道是哪个的了,举个例子,在main函数中:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TCPerson *person = [[TCPerson alloc] init];
        person.name = @"jack";
        
        TCPerson *person1 = [[TCPerson alloc] init];
        person1.name = @"rose";
        
        NSLog(@"person-----name----%@",person.name);
        NSLog(@"person1----name----%@",person1.name);
    }
    return 0;
}

输出结果变成了:

2020-12-08 16:17:56.286103+0800 TCCateogry[1724:95832] person-----name----rose
2020-12-08 16:17:56.286437+0800 TCCateogry[1724:95832] person1----name----rose
  • 字典方法

.m文件

#import "TCPerson+TCtest1.h"

@implementation TCPerson (TCtest1)

NSMutableDictionary *names_;
+ (void)load{
    names_ = [NSMutableDictionary dictionary];
}
- (void)setName:(NSString *)name{
    NSString *key = [NSString stringWithFormat:@"%p", self];
        names_[key] = name;
}
- (NSString *)name{
    NSString *key = [NSString stringWithFormat:@"%p", self];
        return names_[key];
}

@end

在main函数里面访问

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        TCPerson *person = [[TCPerson alloc] init];
        person.name = @"jack";
        
        TCPerson *person1 = [[TCPerson alloc] init];
        person1.name = @"rose";
        
        NSLog(@"person-----name----%@",person.name);
        NSLog(@"person1----name----%@",person1.name);
    }
    return 0;
}

NSString *key = [NSString stringWithFormat:@"%p", self];是将对象的地址作为key,保证key的唯一性,比如传进来的是person,这个时候就把person的地址作为字典的key
输出结果:2020-12-08 16:23:13.590956+0800 TCCateogry[1781:99301] person-----name----jack
2020-12-08 16:23:13.591300+0800 TCCateogry[1781:99301] person1----name----rose
优点:

解决了上面全局变量的访问错乱的问题

缺点:

(1)存在内存泄漏,当你创建的对象不是在整个app运行的时候都存在,全局变量就浪费了内存
(2)多线程访问的时候容易出现线程安全问题
(3)当需要在分类里面创建多个属性的时候,需要重复写set、get,并创建多个字典,比较麻烦

  • objc-runtime-objc_setAssociatedObject方法

.m文件

#import "TCPerson+TCtest1.h"
#import 
@implementation TCPerson (TCtest1)
- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
    return objc_getAssociatedObject(self, @selector(name));
}
@end

方法说明:objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)

参数1:需要添加属性的关联对象,在分类里面一般是self;
参数2:<#const void * _Nonnull key#>关联的key,这里的key你可以理解成字典里面的key,这里的可以你可以写成static const char TCNameKey,也可以写成static const void TCNameKey = &TCNameKey;也可以写成宏定义,尽量保证唯一并一一对应的关系,只是调用的时候objc_setAssociatedObject(self, TCNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);注意一下,记住这个key传的是地址
参数3:关联的值
参数4:关联策略
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /
< Specifies a weak reference to the associated object. /
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /
< Specifies a strong reference to the associated object.
* The association is not made atomically. /
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /
< Specifies that the associated object is copied.
* The association is not made atomically. /
OBJC_ASSOCIATION_RETAIN = 01401, /
< Specifies a strong reference to the associated object.
* The association is made atomically. /
OBJC_ASSOCIATION_COPY = 01403 /
*< Specifies that the associated object is copied.
* The association is made atomically. */
};
相信一看就知道这个就明白了什么是关联策略,它相当于我们写属性的时候的copy,retain等,值得注意的是,它里面没有weak

输出结果:
020-12-08 16:44:06.726777+0800 TCCateogry[1896:108439] person-----name----jack
2020-12-08 16:44:06.727089+0800 TCCateogry[1896:108439] person1----name----rose
在其它文章里面,有些可能这么写get方法

#import "TCPerson+TCtest1.h"
#import 
@implementation TCPerson (TCtest1)
- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
    return objc_getAssociatedObject(self, _cmd);
}
@end

这里的_cmd等价于@selector(name),我们在调用函数的时候,有两个隐式参数:(NSString *)name:(id self) _cmd:(SEL)_cmd,但是只能在get方法里面用_cmd,它代表的是当前方法的@selector,如果在set里面用_cmd,设置的值得key和取值的key就不一样了

  • objc-runtime关联对象的底层源码解析

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


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

{
    struct DisguisedPointerEqual {
        bool operator()(uintptr_t p1, uintptr_t p2) const {
            return p1 == p2;
        }
    };
    
    struct DisguisedPointerHash {
        uintptr_t operator()(uintptr_t k) const {
            // borrowed from CFSet.c
#if __LP64__
            uintptr_t a = 0x4368726973746F70ULL;
            uintptr_t b = 0x686572204B616E65ULL;
#else
            uintptr_t a = 0x4B616E65UL;
            uintptr_t b = 0x4B616E65UL; 
#endif
            uintptr_t c = 1;
            a += k;
#if __LP64__
            a -= b; a -= c; a ^= (c >> 43);
            b -= c; b -= a; b ^= (a << 9);
            c -= a; c -= b; c ^= (b >> 8);
            a -= b; a -= c; a ^= (c >> 38);
            b -= c; b -= a; b ^= (a << 23);
            c -= a; c -= b; c ^= (b >> 5);
            a -= b; a -= c; a ^= (c >> 35);
            b -= c; b -= a; b ^= (a << 49);
            c -= a; c -= b; c ^= (b >> 11);
            a -= b; a -= c; a ^= (c >> 12);
            b -= c; b -= a; b ^= (a << 18);
            c -= a; c -= b; c ^= (b >> 22);
#else
            a -= b; a -= c; a ^= (c >> 13);
            b -= c; b -= a; b ^= (a << 8);
            c -= a; c -= b; c ^= (b >> 13);
            a -= b; a -= c; a ^= (c >> 12);
            b -= c; b -= a; b ^= (a << 16);
            c -= a; c -= b; c ^= (b >> 5);
            a -= b; a -= c; a ^= (c >> 3);
            b -= c; b -= a; b ^= (a << 10);
            c -= a; c -= b; c ^= (b >> 15);
#endif
            return c;
        }
    };
    
    struct ObjectPointerLess {
        bool operator()(const void *p1, const void *p2) const {
            return p1 < p2;
        }
    };
    
    struct ObjcPointerHash {
        uintptr_t operator()(void *p) const {
            return DisguisedPointerHash()(uintptr_t(p));
        }
    };

    // STL allocator that uses the runtime's internal allocator.
    
    template  struct ObjcAllocator {
        typedef T                 value_type;
        typedef value_type*       pointer;
        typedef const value_type *const_pointer;
        typedef value_type&       reference;
        typedef const value_type& const_reference;
        typedef size_t            size_type;
        typedef ptrdiff_t         difference_type;

        template  struct rebind { typedef ObjcAllocator other; };

        template  ObjcAllocator(const ObjcAllocator&) {}
        ObjcAllocator() {}
        ObjcAllocator(const ObjcAllocator&) {}
        ~ObjcAllocator() {}

        pointer address(reference x) const { return &x; }
        const_pointer address(const_reference x) const { 
            return x;
        }

        pointer allocate(size_type n, const_pointer = 0) {
            return static_cast(::malloc(n * sizeof(T)));
        }

        void deallocate(pointer p, size_type) { ::free(p); }

        size_type max_size() const { 
            return static_cast(-1) / sizeof(T);
        }

        void construct(pointer p, const value_type& x) { 
            new(p) value_type(x); 
        }

        void destroy(pointer p) { p->~value_type(); }

        void operator=(const ObjcAllocator&);

    };

    template<> struct ObjcAllocator {
        typedef void        value_type;
        typedef void*       pointer;
        typedef const void *const_pointer;
        template  struct rebind { typedef ObjcAllocator other; };
    };
  
    typedef uintptr_t disguised_ptr_t;
    inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
    inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }
  
    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; }
    };

#if TARGET_OS_WIN32
    typedef hash_map ObjectAssociationMap;
    typedef hash_map AssociationsHashMap;
#else
    typedef ObjcAllocator > ObjectAssociationMapAllocator;
    class ObjectAssociationMap : public std::map {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
    typedef ObjcAllocator > AssociationsHashMapAllocator;
    class AssociationsHashMap : public unordered_map {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
#endif
}

其流程图如下
底层流程图

你可能感兴趣的:(iOS 关联对象)