Category关联对象

一、分类中添加成员变量
Category中添加成员变量.png

  • 所以Category中是不能添加成员变量

二、Category中添加属性

1、在Category中定义属性,但是不实现

  • 1、RevanPerson
#import 

@interface RevanPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

#import "RevanPerson.h"

@implementation RevanPerson

@end
  • 2、RevanPerson+RevanMsg
#import "RevanPerson.h"

@interface RevanPerson (RevanMsg)

@property (nonatomic, assign) int age;

@end
#import "RevanPerson+RevanMsg.h"

@implementation RevanPerson (RevanMsg)

@end
  • 3、测试代码
#import "ViewController.h"
#import "RevanPerson.h"
#import "RevanPerson+RevanMsg.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person1 = [[RevanPerson alloc] init];
    person1.name = @"person1";
    person1.age = 11;
    NSLog(@"\nname is %@, age is %d", person1.name, person1.age);
}
@end
打印输出:
-[RevanPerson setAge:]: unrecognized selector sent to instance 0x60400001f570
  • 报错信息表示找不到setAge:方法age这个属性是在RevanPerson的Category中,虽然现在直接在Category中定义属性不会报错,但是不能使用。我们知道在类中定义一个属性时,会自动在.h文件中生成setAge:和age方法的声明并且在.m文件中生成一个成员变量_age和setAge:和age方法的实现。
  • Category中定义属性后只会在.h文件中声明setter和getter方法,不会在.m文件实现setter和getter方法和自动生成成员变量,所以想要使用Category中的属性必须要手动实现属性的setter和getter方法。

2、在Category中定义属性,并且实现属性的setter和getter方法

  • 1、RevanPerson
#import 

@interface RevanPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

#import "RevanPerson.h"

@implementation RevanPerson

@end
  • 2、RevanPerson+RevanMsg
#import "RevanPerson.h"

@interface RevanPerson (RevanMsg)

@property (nonatomic, assign) int age;

@end
#import "RevanPerson+RevanMsg.h"

#import "RevanPerson+RevanMsg.h"

@implementation RevanPerson (RevanMsg)

- (void)setAge:(int)age {
    
}

- (int)age {
    return 0;
}

@end

  • 3、测试代码
#import "ViewController.h"
#import "RevanPerson.h"
#import "RevanPerson+RevanMsg.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person1 = [[RevanPerson alloc] init];
    person1.name = @"person1";
    person1.age = 11;
    NSLog(@"\nname is %@, age is %d", person1.name, person1.age);
}
@end
打印输出:
name is person1, age is 0
虽然程序不会崩溃,但是输出有不对
  • 分析:对应外界实例对象在使用name属性和age属性时,无法区别age是类中属性还是分类中属性。现在给age赋值失败的原因是分类中的属性没有像类中的属性那样有自动生成一个_age属性来保存外界传入的age值,当外界在使用age值得使用返回_age。所以我们可以通过在分类中加一个变量来存储外界传进来的age值。

2.1、在分类中添加一个变量来存储外界传入的值

  • 1、RevanPerson
#import 

@interface RevanPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

#import "RevanPerson.h"

@implementation RevanPerson

@end

  • 2、RevanPerson+RevanMsg
#import "RevanPerson+RevanMsg.h"
//定义静态变量
static int age_s;

@implementation RevanPerson (RevanMsg)
- (void)setAge:(int)age {
    age_s = age;
}

- (int)age {
    return age_s;
}
@end
  • 3、测试代码
#import "ViewController.h"
#import "RevanPerson.h"
#import "RevanPerson+RevanMsg.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person1 = [[RevanPerson alloc] init];
    person1.name = @"person1";
    person1.age = 11;
    NSLog(@"\nname is %@, age is %d", person1.name, person1.age);
}
@end
打印输出:
name is person1, age is 11
  • 这样就解决了存储值得问题了,但是还有其他问题
  • 当有多个对象使用分类中属性时
#import "ViewController.h"
#import "RevanPerson.h"
#import "RevanPerson+RevanMsg.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person1 = [[RevanPerson alloc] init];
    person1.name = @"person1";
    person1.age = 11;
    
    RevanPerson *person2 = [[RevanPerson alloc] init];
    person2.name = @"person2";
    person2.age = 22;
    NSLog(@"\nname is %@, age is %d", person1.name, person1.age);
    NSLog(@"\nname is %@, age is %d", person2.name, person2.age);
    
}

@end
打印输出:
2018-07-04 10:21:24.497306+0800 03-Category关联属性[1151:25812] 
name is person1, age is 22
2018-07-04 10:21:24.497445+0800 03-Category关联属性[1151:25812] 
name is person2, age is 22
  • 发现person1和person2的age输出值是一样的,所以使用 变量来存储分类中属性的值是有问题的
    2.2、在分类中添加一个可变字典来存储外界传入的值

使用变量存储分类中的值之所以失败是没有办法做到分类属性和对象一一对应起来,所以可以使用一个字典,来把对象和属性一样对应起来存储

  • 1、RevanPerson
#import 

@interface RevanPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

#import "RevanPerson.h"

@implementation RevanPerson

@end
  • 2、RevanPerson+RevanMsg
#import "RevanPerson+RevanMsg.h"
//定义静态变量
static NSMutableDictionary *revan_age_dic;


@implementation RevanPerson (RevanMsg)

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

- (void)setAge:(int)age {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    [revan_age_dic setObject:@(age) forKey:key];
}

- (int)age {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    return [[revan_age_dic objectForKey:key] intValue];
}
@end
  • 3、测试代码
#import "ViewController.h"
#import "RevanPerson.h"
#import "RevanPerson+RevanMsg.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person1 = [[RevanPerson alloc] init];
    person1.name = @"person1";
    person1.age = 11;
    
    RevanPerson *person2 = [[RevanPerson alloc] init];
    person2.name = @"person2";
    person2.age = 22;
    NSLog(@"\nname is %@, age is %d", person1.name, person1.age);
    NSLog(@"\nname is %@, age is %d", person2.name, person2.age);
    
}

@end
打印输出
2018-07-04 10:45:38.678684+0800 03-Category关联属性[1242:38545] 
name is person1, age is 11
2018-07-04 10:45:38.678901+0800 03-Category关联属性[1242:38545] 
name is person2, age is 22
  • 使用了字典以后实现了对象和分类中属性的一一对应,但是会存在以下问题
    • 内存泄漏,因为定义的这个字典是一个常量,所以会一直在内存中
    • 存在线程安全的问题

三、关联对象

在runtime中提供了一个为对象关联属性的函数

1、给对象关联属性
     /**
     给实例对象self关联age
     @param object#> 需要关联的对象 description#>
     @param key#> 使用的key description#>
     @param value#> 关联的值 description#>
     @param policy#> 策略 description#>
     */
    objc-objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
2、获取对象的关联属性

    /**
     获取对象的关联属性

     @param object#> 关联属性的对象 description#>
     @param key#> 关联属性的key description#>
     */
    objc_getAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>)
  • 1、RevanPerson
#import 

@interface RevanPerson : NSObject
/** 名字 */
@property (nonatomic, copy) NSString * name;
@end

#import "RevanPerson.h"

@implementation RevanPerson

@end
  • 2、RevanPerson+RevanMsg
#import "RevanPerson.h"

@interface RevanPerson (RevanMsg)
/** 年龄 */
@property (nonatomic, assign) int age;
@end

#import "RevanPerson+RevanMsg.h"
#import 

@implementation RevanPerson (RevanMsg)

- (void)setAge:(int)age {
    
    /**
     给实例对象self关联age

     @param object#> 需要关联的对象 description#>
     @param key#> 使用的key description#>
     @param value#> 关联的值 description#>
     @param policy#> 策略 description#>
     */
    objc_setAssociatedObject(self, @selector(age), @(age), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)age {
    /**
     获取对象的关联属性

     @param object#> 关联属性的对象 description#>
     @param key#> 关联属性的key description#>
     */
    return [objc_getAssociatedObject(self, @selector(age)) intValue];
}
@end

  • 3、测试代码
#import "ViewController.h"
#import "RevanPerson.h"
#import "RevanPerson+RevanMsg.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RevanPerson *person1 = [[RevanPerson alloc] init];
    person1.name = @"person1";
    person1.age = 11;
    
    RevanPerson *person2 = [[RevanPerson alloc] init];
    person2.name = @"person2";
    person2.age = 22;
    NSLog(@"\nname is %@, age is %d", person1.name, person1.age);
    NSLog(@"\nname is %@, age is %d", person2.name, person2.age);
    
}
@end
打印输出:
2018-07-04 11:07:22.342609+0800 03-Category关联属性[5305:59092] 
name is person1, age is 11
2018-07-04 11:07:22.342731+0800 03-Category关联属性[5305:59092] 
name is person2, age is 22

对象关联属性源码

  • runtime源码
  • objc_setAssociatedObject源码
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}
  • AssociationsManager是Associations关联对象的管理者,其中有一个static AssociationsHashMap 类型的变量_map
class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map;//有一个static AssociationsHashMap类型的_map
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};
  • AssociationsHashMap中有个泛型ObjectAssociationMap
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); }
  • ObjectAssociationMap有一个ObjcAssociation
class ObjectAssociationMap : public std::map {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };
  • 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; }
    };
  • 小结:从上面的源码分析可以知道
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation

这4个属性从上至下一层一层所有

  • 关联属性源码
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);
    /** 给object关联的值value
        通过判断value是否存在来决定new_value对象是有值还是为nil
     */
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        //AssociationsManager管理者
        AssociationsManager manager;
        //manager通过associations获得一个AssociationsHashMap
        AssociationsHashMap &associations(manager.associations());
        /** 传入的object关联对象
            获得一个disguised_object
         */
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            // i和传入的object对象有关联
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            //i不是结束
            if (i != associations.end()) {
                // secondary table exists
                // 通过i获取到ObjectAssociationMap类型refs
                ObjectAssociationMap *refs = i->second;
                //我们传入的key,和ObjectAssociationMap *refs获取到j
                ObjectAssociationMap::iterator j = refs->find(key);
                //如果没有结束
                if (j != refs->end()) {
                    //把j->second赋值给old_association
                    old_association = j->second;
                    //通过ObjcAssociation把我们传入的 关联策略(policy),和被转换的我们传入的值(new_value)保存到j->second也就是refs
                    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();
            }
        }
        // new_value == nil
        else {
            // setting the association to nil breaks the association.
            // 通过传入的object获取到 i
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                // 通过I找到ObjectAssociationMap
                ObjectAssociationMap *refs = i->second;
                // 在通过key找到ObjectAssociationMap中的 j
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    // 把j抹去
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
  • 关联属性
    关联属性@2x.png
  • 可以把这对象都当成字典来理解
    • a)AssociationsManager通过传入object对象参数来指向唯一对应的AssociationsHashMap
    • b)AssociationsHashMap通过传入key参数来指向唯一对应的ObjectAssociationMap
    • c)AssociationsHashMap通过iterator获取j
    • d)最后使用ObjcAssociation把传入的策略(policy)和值(value)一起赋值给j->second

获取关联属性源码

  • 1、objc_getAssociatedObject
id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
  • 2、_object_get_associative_reference
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        //通过传入的object对象获取唯一disguised_object
        disguised_ptr_t disguised_object = DISGUISE(object);
        //通过disguised_object获取唯一AssociationsHashMap
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            //通过传入的唯一key来获取对应的value和policy
            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;
}

Category关联对象小结

  • 1、Category中不能直接添加成员变量
  • 2、Category中可以直接添加属性变量,但是必须要实现属性的setter和getter方法
  • 3、Categoryruntime关联属性
    Category关联属性原理.png

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