先自我想想下面关于 Category 思考题:
- Category能否添加成员变量?如果可以,如何给
Category
添加成员变量?- Category的实现原理,以及Category为什么只能加方法不能加属性。
- Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
- load、initialize的区别,以及它们在category重写的时候的调用的次序。
第一题回答:
不能直接添加成员变量,但是可以添加属性(@property),然后通过runtime的方式实现实现get、set方法,从而达到间接添加成员变量的效果。
直接添加成员变量编译器就已经报错
直接添加成员变量
在Category添加的属性,实际上只是在.h文件声明setter和getter方法
在.h文件声明get,set方法 编译器提示需要在.m文件实现get、set方法
现在我们来实现get、set方法
- (void)setAge:(int)age
{
}
- (int)age
{
return 0;
}
Person *person = [[Person alloc] init];
person.age = 18;
系统运行上面代码已经没有任何问题了,对外也实现了直接赋值Person类的age属性;但是现在我们发现从外部传值进来的age没有付给任何属性,这样也就无法保存实例变量。到这一步我们就要思考如何来接收、传递变量数据?
方法一:通过使用静态全局变量
static int _age;
@implementation Person (Age)
- (void)setAge:(int)age
{
_age = age;
}
- (int)age
{
return _age;
}
@end
Person *person = [[Person alloc] init];
//调用set方法
person.age = 18;
//调用get方法
NSLog(@"person1.age = %d", person.age);
//调用person2 get方法
NSLog(@"person2.age = %d", [Person new].age);
/**
运行结果如下:
2018-06-12 10:20:33.094612+0800 TestCategory[9951:373561] person1.age = 18
2018-06-12 10:20:33.094908+0800 TestCategory[9951:373561] person2.age = 18
*/
person2.age不是预期的0,是第一次赋值的18。这是_age是静态全局变量,只要程序在运行_age变量就存在;一次赋值,所有person实例都会受影响,哪怕后来创建的实例也不例外。
static声明的变量与类没有关联,并不是真正意义上的成员对象。这里也不推荐大家使用,只是为了解编程原理。
方法二:使用RunTime动态接收、传递
由于OC是动态语言,方法真正的实现是通过runtime完成的,runtime提供了动态添加属性和获得属性的方法。
#import <objc/runtime.h>
static NSString *kPersonAgeKey = @"kPersonAgeKey";
@implementation Person (Age)
- (void)setAge:(int)age
{
objc_setAssociatedObject(self, &kPersonAgeKey, [NSNumber numberWithInt:age], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (int)age
{
NSNumber *number = objc_getAssociatedObject(self, &kPersonAgeKey);
if (number) {
return [number intValue];
}
return 0;
}
@end
/**
运行结果如下:
2018-06-12 10:34:50.181881+0800 TestCategory[10247:426647] person1.age = 18
2018-06-12 10:34:50.182092+0800 TestCategory[10247:426647] person2.age = 0
*/
这里运行结果和预期一致,实现添加成员变量的效果。此时已经成功给Person添加age属性,并且Person对象可以通过点语法为属性赋值,age属性与Person实例对象相关联。
objc_setAssociatedObject
/**
* Sets an associated value for a given object using a given key and association policy.(使用给定的键和关联策略为给定的对象设置关联值。)
*
* @param object The source object for the association.(关联的源对象,这里要给自己添加属性,用self。)
* @param key The key for the association.(关联的key,在get方法的objc_getAssociatedObject中通过次key获得属性的值并返回。)
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.(与对象的键相关联的值)
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”(策略,属性以什么形式保存。)
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
关联策略objc_AssociationPolicy有以下几种
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object.
(关联对象弱引用,一般用于Delegate对象关联)*/
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.
关联对象被复制,原子性*/
};
objc_getAssociatedObject比较简单,请仔细阅读理解
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
可以看出关联对象的使用非常简单,主要就是两个方法。上面理解好了足以应对初级iOS面试,中高级iOS开发还需要理解关联对象的底层原理,CoreFoundation框架是如何设计Category的?
关联对象原理
实现关联对象技术的核心对象有
-
AssociationsManager
-
AssociationsHashMap
-
ObjectAssociationMap
-
ObjcAssociation
其中Map同我们平时使用的字典类似, 通过key-value一一对应存值。
Objective-C Runtime源码是开源的,下载地址为: http://opensource.apple.com/tarballs/objc4/
需要下载一个新一点的代码包,我下的是 objc4-723.tar.gz
对关联对象技术的核心对象有了一个大概的意识,我们通过源码来探寻这些对象的存在形式以及其作用
objc_setAssociatedObject函数
来到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);
}
objc_setAssociatedObject
函数其实内部调用的是_object_set_associative_reference
函数,我们来到_object_set_associative_reference
函数,这就是runtime为Category动态添加成员变量的核心,读懂这段函数也意味着理解RunTime动态关联对象的原理。
_object_set_associative_reference函数
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);
}
我们依次解释上述代码的关键信息:
1.转换对象
根据policy调用对应的方法,比如retain或者copy,将value转为new_value。
static id acquireValue(id value, uintptr_t policy) {
switch (policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
return objc_retain(value);
case OBJC_ASSOCIATION_SETTER_COPY:
return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
}
return value;
}
2.获取全局的AssociationsHashMap和object的对象地址
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
先获取AssociationsManager
单例,构造函数中会进行加锁;接着通过associations()方法获取静态AssociationsHashMap
;再把object的地址取反,得到一个unsigned long类型的变量,用作AssociationsHashMap
的键。
AssociationsManager
内容不多,看一下源码也就理解了。
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock, and calling its assocations()
// method lazily allocates the hash table.
spinlock_t AssociationsManagerLock;
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;
}
};
在看一下AssociationsHashMap
源码,内容也很少,主要就是Key-Value的Map表,其中Key为disguised_ptr_t
,Value为ObjectAssociationMap
。
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); }
};
3.设置全局的AssociationsHashMap和object的对象地址管理关系
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();
}
这里是函数的核心,先解释两个概念ObjectAssociationMap
和ObjcAssociation
,有助于理解代码思想。
ObjectAssociationMap
看名称也能猜到该类的作用,用来存储ObjcAssociation
的Map表,其中Key为_object_set_associative_reference
方法入口的key参数。
看一下ObjectAssociationMap
源码,前面两个参数(void *
, ObjcAssociation
)分别对应了Key和Value
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
,该对象也很简单,只有policy
和value
,两个值正对应着我们调用objc_setAssociatedObject
函数传入的值,也就是说objc_setAssociatedObject
函数的value和policy参数最终是存储在ObjcAssociation
中的。现在就能理解RunTime为Category设置关联对象时,策略上为什么没有weak和Strong了,Value不支持简单数据类型(int, bool)的原因
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; }
};
现在解释代码运行过程:
3.1 先根据object对象地址寻找ObjectAssociationMap
,如果能找到ObjectAssociationMap
,进入3.2,否则自行创建ObjectAssociationMap
,进行对象赋值。
3.2 找到ObjectAssociationMap
,则通过key查找ObjcAssociation
?如果能找到ObjcAssociation
,进入3.3,否则进去3.4.
3.3 找到ObjcAssociation
,则将找到的赋给old_association
,用于后期对象销毁。
3.4 创建新的ObjcAssociation
,设置到ObjectAssociationMap
所对应的key上。
如果我们value设置为nil的话,那么会执行下面的代码
// 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);
}
}
从上述代码中可以看出,如果我们设置value为nil时,就会将关联对象从ObjectAssociationMap
中移除。
最后我们通过一张图可以很清晰的理清楚其中的关系
image通过上图我们可以总结为:一个实例对象就对应一个ObjectAssociationMap
,而ObjectAssociationMap
中存储着多个此实例对象的关联对象的key以及ObjcAssociation
,为ObjcAssociation
中存储着关联对象的value和policy策略。
由此我们可以知道关联对象并不是放在了原来的对象里面,而是自己维护了一个全局的map用来存放每一个对象及其对应关联属性表格。
objc_getAssociatedObject函数
该函数内部实际调用了_object_get_associative_reference
,从_object_get_associative_reference
函数内部可以看出,向set方法中那样,反向将value一层一层取出最后return出去。
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
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()) {
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;
}
objc_removeAssociatedObjects函数
objc_removeAssociatedObjects
用来删除所有的关联对象,objc_removeAssociatedObjects
函数内部调用的是_object_remove_assocations
函数,该函数将object对象向对应的所有关联对象全部删除。
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
为什么没有weak呢?
总过上面对源码的分析我们知道,object经过DISGUISE
函数被转化为了unsigned long类型的disguised_object
。
disguised_ptr_t disguised_object = DISGUISE(object);
而同时我们知道,weak修饰的属性,当没有拥有对象之后就会被销毁,并且指针置位nil,那么在对象销毁之后,虽然在map中既然存在值object对应的AssociationsHashMap
,但是因为object地址已经被置位nil,会造成坏地址访问而无法根据object对象的地址转化为disguised_object了。
更多文章:
iOS底层原理总结 - 关联对象实现原理
深入理解Objective-C:Category
objc category的秘密
刨根问底Objective-C Runtime
runtime源码分析之category的实现
网友评论