iOS 关联属性底层探索
苹果官方资源opensource
objc4-838可编译联调源码
我们知道在iOS在分类里添加属性必须是关联属性。
先来看看这个案例:
main.m
声明:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
// MyPerson
@interface MyPerson : NSObject {
int _sum;
}
@property (nonatomic, copy) NSString *hobby;
- (void)speak;
- (void)printClassAllMethod:(Class)cls; // 打印当前类的MethodList
+ (void)method1;
@end
@implementation MyPerson
- (void)speak {
NSLog(@"%s", __func__);
}
- (void)printClassAllMethod:(Class)cls {
unsigned int count = 0;
Method *methods = class_copyMethodList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@ - %p", NSStringFromSelector(sel), imp);
}
free(methods);
}
+ (void)method1 {
NSLog(@"%s", __func__);
}
@end
// MyPerson的分类(还没给name添加动态关联,此时系统给予警告⚠️)
@interface MyPerson (Test)
@property (nonatomic, copy) NSString *name;
- (void)cate_instanceMethod;
+ (void)cate_classMethod;
@end
@implementation MyPerson (Test)
- (void)cate_instanceMethod {
NSLog(@"%s", __func__);
}
+ (void)cate_classMethod {
NSLog(@"%s", __func__);
}
@end
// main
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"main");
MyPerson *p = [MyPerson alloc];
[p speak];
[p cate_instanceMethod];
}
return 0;
}
把main.m
编译成main.cpp
来看看MyPerson
的分类里有什么东西:
$ cd main文件的目录
$ clang -rewrite-objc main.m
打开main.cpp
拉到代码底部发现了_category_t
:
找到_category_t
的声明:
可以看到分类是没有成员变量的
(分类是不能声明成员变量的)。
然后找到分类声明方法和属性的地方:
发现分类里即是声明了属性,但并不会给我们生成getter/setter
。
那就需要使用动态关联属性的方式自己写一个getter/setter
。
将分类部分的代码修改一下:
// MyPerson的分类
@interface MyPerson (Test)
@property (nonatomic, copy) NSString *name;
- (void)cate_instanceMethod;
+ (void)cate_classMethod;
@end
@implementation MyPerson (Test)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, MyNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, MyNameKey);
}
- (void)cate_instanceMethod {
NSLog(@"%s", __func__);
}
+ (void)cate_classMethod {
NSLog(@"%s", __func__);
}
@end
然后就可以访问name属性了
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"main");
MyPerson *p = [MyPerson alloc];
[p speak];
p.name = @"安安";
NSLog(@"%@", p.name); // 安安
}
return 0;
}
源码分析关联属性
打开objc4源码
找到objc-runtime.m
- 1.
objc_setAssociatedObject
的源码声明:
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
_object_set_associative_reference
的源码声明:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;
// 类对象不允许关联属性
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 把当前对象封装成DisguisedPtr类型,以方便使用,可把DisguisedPtr看成当前对象
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 把关联策略policy和关联属性的值保存到 ObjcAssociation的实例里
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue(); // 处理关联策略相关的
bool isFirstAssociation = false;
{
// 相当于是构造的时候,自动加锁,出了作用域析构掉,自动解锁。
// 保证在同一时间只有一条线程去操作AssociationsHashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
1.类对象是不允许关联对象的
2.把当前对象object
封装成了DisguisedPtr
类型的实例,以方便使用
3.把关联策略policy和关联属性的值保存到ObjcAssociation
的实例里
4.AssociationsManager manager;
的作用是在作用域内进行加锁/解锁,保证在同一时间只有一条线程去操作AssociationsHashMap
来看看AssociationsManager
的声明:
5.AssociationsHashMap
数据结构是以key-value
键值对的方式存储:key
是 DisguisedPtr实例(object)
; value
是ObjectAssociationMap实例
AssociationsHashMap
数据结构是以key-value
键值对的方式存储:key
是我们调用API传递进来的MyNameKey
;value
是ObjcAssociation对象(关联策略和关联属性的值)
于是分析出来AssociationsHashMap
的数据结构简约是这样的:
/*
object1/object2 是DisguisedPtr实例,相当于是我们需要关联属性的当前对象。
key1..key4 是设置关联属性API参数的key(本文中使用了一个MyNameKey)
policyAndValue1..policyAndValue4 是ObjcAssociation实例,存储着关联策略和关联属性的值
*/
{
object1: {
key1: policyAndValue1,
key2: policyAndValue2,
},
object2: {
key3: policyAndValue3,
key4: policyAndValue4,
},
}
注意:这个AssociationsHashMap
是一个全局唯一的哈希链表(无需扩容)
6.如果value有值,最后就是判断AssociationsHashMap
是否有这个对象,如果没有则插入,如果有则返回ObjcAssociationMap
对象,再去替换掉新的内容
(try_emplace
是迭代器,以供AssociationsHashMap
和ObjcAssociationMap
的访问)
7.如果value没有值,则从AssociationsHashMap
擦除掉
- 2.
objc_getAssociatedObject
的源码声明:
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
_object_get_associative_reference
的源码声明:
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
// 相当于是构造的时候,自动加锁,出了作用域析构掉,自动解锁。
// 保证在同一时间只有一条线程去访问AssociationsHashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
逻辑就更简单啦:
1.加锁/解锁,保证在同一时间只有一条线程去访问AssociationsHashMap
2,通过当前对象object
从AssociationsHashMap
找到ObjcAssociationMap
,在通过key
从ObjcAssociationMap
找到ObjcAssociation关联策略和关联属性的值
。
- 3.
objc_removeAssociatedObjects
的源码声明:
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object, /*deallocating*/false);
}
}
_object_remove_assocations
的源码声明:
// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if (!deallocating) {
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
associations.erase(i);
}
}
// Associations to be released after the normal ones.
SmallVector<ObjcAssociation *, 4> laterRefs;
// release everything (outside of the lock).
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released.
if (deallocating)
laterRefs.append(&i.second);
} else {
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
在当前对象被释放的时候,会自动地把关联对象给释放了。
// Replaced by NSZombies
- (void)dealloc {
_objc_rootDealloc(self);
}
// _objc_rootDealloc
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
// rootDealloc
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
// object_dispose
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
// objc_destructInstance
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
_object_remove_assocations
就是释放关联对象。
从源码就能看出:关联属性不需要程序员去做内存管理。
动态添加关联属性后,看看编译后代码main.cpp: