美文网首页
Category关联对象

Category关联对象

作者: 紫荆秋雪_文 | 来源:发表于2018-07-04 12:37 被阅读6次

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

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

二、Category中添加属性

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

  • 1、RevanPerson
#import <Foundation/Foundation.h>

@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 <Foundation/Foundation.h>

@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 <Foundation/Foundation.h>

@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 <Foundation/Foundation.h>

@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 <Foundation/Foundation.h>

@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 <objc/runtime.h>

@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

对象关联属性源码

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<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); }
  • ObjectAssociationMap有一个ObjcAssociation
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); }
    };
  • 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关联对象

    一、分类中添加成员变量Category中添加成员变量.png 所以Category中是不能添加成员变量 二、Cat...

  • Category关联对象

    OC中的分类严格来说,是不能添加对象;但是我们可以通过OC运行是的机制,动态为分类添加属性 一、类中的属性 当在类...

  • 关联对象

    关联对象 关联对象一般用来配合 Category 使用,在 Category 中声明属性时编译器只会自动生成 Ge...

  • 关于分类(Category)关联对象的一些了解

    本文是关于Category关联对象的一些理解化,以及部分使用建议。仅适用于新手玩家(对Category关联对象停留...

  • Category使用关联对象生成属性的原理

    Category关联对象: main.m文件: NSString+Category.h文件: NSString+C...

  • Objective-C--关联对象(AssociateObjec

    关联对象的用途 在Category中为已经注册的类增加存储字段,模拟实例变量。 关联对象存储原理 所有的关联对象都...

  • Category-关联对象

    分类添加属性 Category能否添加成员变量?如果可以,如何给Category添加成员变量?不能直接给Categ...

  • Category &associate 关联对象

    手动为Category生成一个成员变量 默认情况下,因为分类底层架构的限制,不能添加到成员变量到分类中,但是可以通...

  • Category关联对象探究

    在日常开发的过程中经常会用到在category中添加属性的需求,在添加属性后是默认不会自动生成setter和get...

  • 如何在关联对象上使用 weak

    要在 category 中定义属性, 唯一的办法就是使用关联对象. 但是关联对象的存储方式只有 assign, r...

网友评论

      本文标题:Category关联对象

      本文链接:https://www.haomeiwen.com/subject/uaesuftx.html