Objective-C通过联合存储为类增加属性及原理解析
联合存储实现方式及底层原理解析
作者:wangzz
原文地址:http://blog.csdn.net/wzzvictory_tjsd/article/details/9347981
转载请注明出处
如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号wangzzstrive来支持我,谢谢!
动态语言的最大好处,就是灵活性,对于Objective-C来说,能在运行时动态地为类增加方法和实例变量是很多其它语言羡慕不已的能力。现在说说为类增加实例变量用到的技术:联合存储。
一、联合存储的实现方式
下面这段代码实现了为Duck类增加color属性:
Duck+associative.h文件
#import "Duck.h"
@interface Duck (associative)
@property (nonatomic, retain) NSString *color;
@end
Duck+associative.m文件
#import "Duck+associative.h"
#import <objc/runtime.h>
@implementation Duck (associative)
static char colorKey = NULL;
- (NSString *)color {
return objc_getAssociatedObject(self, &colorKey);
}
- (void)setColor:(NSString *)aColor {
objc_setAssociatedObject(self, &colorKey,
aColor,
OBJC_ASSOCIATION_RETAIN);
}
调用举例:
Duck *smallDuck = [[Duck alloc] init];
smallDuck.color = @"red color";
NSLog(@"duck color:%@",smallDuck.color);
[smallDuck release];
输出结果:
2013-07-18 19:09:26.578 ObjcRunTime[429:403] duck color:red color
至此,我们已经成功的为Duck类增加了一个color属性。
二、为类动态增加属性用到的技术
主要用到了三种设计模式:
1、访问器(accessor)
访问器模式是Cocoa设计模式中很重要的一个,使用它的目的是通过少量的方法(通常时get和set方法)来访问类中每个实例的引用。通过该技术,尽管Duck类没有color实例变量,但是通过联合存储,依然可以实现同访问实例变量完全一样的效果。这些对于类的使用者来说,屏蔽了实现细节。
可以说,访问器模式是通过联合存储实现为类增加属性的必要前提。
2、类别(category)
类别可以在运行时为类动态的增加方法,这是可以利用访问器模式实现为类增加属性的基础。
3、联合存储(associative storage)
通过类别和访问器,再结合联合存储技术,我们完成了为类增加属性的功能。这一切让用户觉得好像真的增加新的实例变量了,但是实际上我们只是通过访问器模拟出来了一个,而不是真正的增加了。
三、联合存储的实现原理
从上面的例子可以看出,为类增加属性看起来是so easy的事情,主要是调了objc_setAssociatedObject和objc_getAssociatedObject两个方法。我的疑问是为类增加的属性对应的对象值存储在哪了呢?下面通过这两个方法的实现部分来寻找答案:
1、objc_setAssociatedObject方法的实现部分:
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {
if (UseGC) {
//这部分是有垃圾回收机制的实现,我们不用管
if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) {
value = objc_msgSend(value, @selector(copy));
}
auto_zone_set_associative_ref(gc_zone, object, key, value);
} else {
//这是引用计数机制部分的实现
// Note, creates a retained reference in non-GC.
_object_set_associative_reference(object, key, value, policy);
}
}
从上述方法中可以看出,objc_setAssociatedObject实际上调用的是:
2、_object_set_associative_reference方法的实现部分:
__private_extern__ void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
uintptr_t old_policy = 0;
// NOTE: old_policy is always assigned to when old_value is non-nil.
id new_value = value ? acquireValue(value, policy) : nil, old_value = nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (new_value) {
//如果new_value不为空,开始遍历associations指向的map,查找object对象是否存在保存联合存储数据的ObjectAssociationMap对象
// break any existing association.
AssociationsHashMap::iterator i = associations.find(object);
if (i != associations.end()) {
//object对象在associations指向的map中存在一个ObjectAssociationMap对象refs
//检查refs中是否使用key保存过value
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
//使用过该key保存value,用新的value和policy替换掉原来的值
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = old_entry.value;
old_entry.policy = policy;
old_entry.value = new_value;
} else {
//没用使用过该key保存value,将value和policy保存到key映射的map中
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// object对象在associations指向的map中不存在一个ObjectAssociationMap对象
// 则新建一个ObjectAssociationMap对象,并将new_value通过key的地址映射到该map中保存
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
_class_assertInstancesHaveAssociatedObjects(object->isa);//通知object对象,绑定了一个新的值
}
} else {
//new_value为空,如果使用过该key保存过value,则解除使用该key保存的value值
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = (id) old_entry.value;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_value) releaseValue(old_value, old_policy);
}
3、通过这个方法的实现部分可以清楚的看出,在runtime系统中:
①有一个单例的AssociationsHashMap实例
该实例的生成方式如下:
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new(::_malloc_internal(sizeof(AssociationsHashMap))) AssociationsHashMap();
return *_map;
}
②AssociationsHashMap实例用于保存一个个的ObjectAssociationMap对象
③每个类都拥有一个ObjectAssociationMap实例,每个类通过联合存储模式保存的键值对也都保存在ObjectAssociationMap实例中。
④Key对应的值无所谓,我们需要的是key的地址,因此定义key时通常的写法是:
static char colorKey = NULL;
也就是说,说有的数据其实还是保存在AssociationsHashMap实例中,现在似乎一切都豁然开朗了!
四、联合存储的优缺点
1、优点
联合存储的最大的优点,在于它能通过灵活的方式实现为类增加属性。
2、缺点
效率低,使用单条机器指令就可以访问真正的实例变量,但是访问存储在映射表中的值需要多个函数调用,效率问题还是需要注意的。
事实上,目前许多Cocoa类,像NSAttributedString、NSFileManager、NSNotification、NSProcessInfo等都广泛地使用了联合存储。
Objective-C通过联合存储为类增加属性及原理解析的更多相关文章
- python类对象属性查找原理
class Foo(object): def __init__(self): # 这是一个对象属性 self.obj_pro = 12 # 这是一类属性 c_pro = 11 # 这是一个静态方法 @ ...
- plist文件、NSUserDefault 对文件进行存储的类、json格式解析
========================== 文件操作 ========================== Δ一 .plist文件 .plist文件是一个属性字典数组的一个文件: .plis ...
- Spring aop:decare-parent 为类增加新的方法
Spring aop:decare-parent 为类增加新的方法: 使用XML配置的方式: XML: <?xml version="1.0" encoding=" ...
- JavaScript一个cookie存储的类
所有输出都在浏览器的控制台中 <script type="text/javascript"> /** * cookieStorage.js * 本类实现像localSt ...
- 【IOS 开发】Objective - C 面向对象高级特性 - 包装类 | 类处理 | 类别 | 扩展 | 协议 | 委托 | 异常处理 | 反射
一. Objective-C 对象简单处理 1. 包装类 (1) 包装类简介 NSValue 和 NSNumber : -- 通用包装类 NSValue : NSValue 包装单个 short, i ...
- MinerUrl.java 解析页面后存储URL类
MinerUrl.java 解析页面后存储URL类 package com.iteye.injavawetrust.miner; /** * 解析页面后存储URL类 * @author InJavaW ...
- 分别给Python类和实例增加属性和方法
#定义一个类Student class Student(object): pass #给类增加一个属性name Student.name = 'xm' print Student.name # xm ...
- guxh的python笔记六:类的属性
1,私有属性 class Foo: def __init__(self, x): self.x = x 类的属性在实例化之后是可以更改的: f = Foo(1) print(f.x) # 1 f.x ...
- Runtime应用(二)使用对象关联为分类增加属性(每个对象的属性互不干扰)
一.对象的关联方法有 1. void objc_setAssociatedObject(id object, const void *key, id value,objc_AssociationPol ...
随机推荐
- django中文件(头像)上传
一些常用的小功能,记录下来 先说一种上传功能. 模板表单 <form enctype="multipart/form-data" name="form1" ...
- 学习Swift -- 构造器(下)
构造器(下) 可失败的构造器 如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有必要的.这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某 ...
- 金山网络2014春季Android实习生招聘-成都站-笔试第二题
一个文件名为input.txt的文件当中,每一行都有一个单词,要求统计单词出现的频率,并且按照从小到大出现次数打印,次数相同的按照首字母顺序排序. package jinshanwangluo.exa ...
- go 语言与循环
package main import "fmt" type Employee struct{name string; age int} func displayName(e *E ...
- bzoj 1223: [HNOI2002]Kathy函数 数位DP 高精度
1223: [HNOI2002]Kathy函数 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 207 Solved: 90[Submit][Stat ...
- DPI/PPI/dp/sp/px/pt 移动设计手册
转自DPI/PPI/dp/sp/px/pt 移动设计手册 做移动设计的同学,不管是原生app或者web app,应该对字体字号都是很头痛的问题.根本原因是,我们用唯一分辨率的电脑,设计各个不同尺寸大小 ...
- [转载]# Ajax异步请求阻塞情况的解决办法
最近使用ExtJs4的mvc模式在开发了在线漫画的后台,因为异步请求比较多,有的回应时间长,有点短.我发现在多次并发的情况下,会造成阻塞的情况.也就是说如果回应时间长的请求还在进行中,短的请求却被挂起 ...
- jQuery EasyUI parser 的使用场景
转自原文地址:http://www.easyui.info/archives/216.html parser,故名意思,就是解析器的意思,别看他只有那么几行代码,jQuery Easyui 能够根据c ...
- CyclicBarrier的介绍和使用
转自:http://www.itzhai.com/the-introduction-and-use-of-cyclicbarrier.html 类说明: 一个同步辅助类,它允许一组线程互相等待,直到到 ...
- Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制
新的锁定类提高了同步性 —— 但还不能现在就抛弃 synchronized JDK 5.0为开发人员开发高性能的并发应用程序提供了一些很有效的新选择.例如,java.util.concurrent.l ...