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 <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#>)

参数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 <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),我们在调用函数的时候,有两个隐式参数:(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 <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
}

其流程图如下
底层流程图
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容