iOS 關聯(lián)對象

在上一篇文章中我們理解了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函數(shù)中:

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函數(shù)里面訪問

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
優(yōu)點:

解決了上面全局變量的訪問錯亂的問題

缺點:

(1)存在內存泄漏,當你創(chuàng)建的對象不是在整個app運行的時候都存在,全局變量就浪費了內存
(2)多線程訪問的時候容易出現(xiàn)線程安全問題
(3)當需要在分類里面創(chuàng)建多個屬性的時候,需要重復寫set、get,并創(chuàng)建多個字典,比較麻煩

  • objc-runtime-objc_setAssociatedObject方法

.m文件

#import "TCPerson+TCtest1.h"
#import <objc/runtime.h>
@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#>)

參數(shù)1:需要添加屬性的關聯(lián)對象,在分類里面一般是self;
參數(shù)2:<#const void * _Nonnull key#>關聯(lián)的key,這里的key你可以理解成字典里面的key,這里的可以你可以寫成static const char TCNameKey,也可以寫成static const void TCNameKey = &TCNameKey;也可以寫成宏定義,盡量保證唯一并一一對應的關系,只是調用的時候objc_setAssociatedObject(self, TCNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);注意一下,記住這個key傳的是地址
參數(shù)3:關聯(lián)的值
參數(shù)4:關聯(lián)策略
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. */
};
相信一看就知道這個就明白了什么是關聯(lián)策略,它相當于我們寫屬性的時候的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 <objc/runtime.h>
@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),我們在調用函數(shù)的時候,有兩個隱式參數(shù):(NSString *)name:(id self) _cmd:(SEL)_cmd,但是只能在get方法里面用_cmd,它代表的是當前方法的@selector,如果在set里面用_cmd,設置的值得key和取值的key就不一樣了

  • objc-runtime關聯(lián)對象的底層源碼解析

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 <typename T> 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 <typename U> struct rebind { typedef ObjcAllocator<U> other; };

        template <typename U> ObjcAllocator(const ObjcAllocator<U>&) {}
        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<pointer>(::malloc(n * sizeof(T)));
        }

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

        size_type max_size() const { 
            return static_cast<size_type>(-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<void> {
        typedef void        value_type;
        typedef void*       pointer;
        typedef const void *const_pointer;
        template <typename U> struct rebind { typedef ObjcAllocator<U> 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<void *, ObjcAssociation> ObjectAssociationMap;
    typedef hash_map<disguised_ptr_t, ObjectAssociationMap *> AssociationsHashMap;
#else
    typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
    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); }
    };
    typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
    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); }
    };
#endif
}

其流程圖如下
底層流程圖
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容