Runtime最全总结
本系列详细讲解Runtime知识点,由于运行时的内容较多,所以将内容拆分成以下几个方面,可以自行选择想要查看的部分
- OC运行时机制Runtime(一):从isa指针开始初步结识Runtime
- OC运行时机制Runtime(二):探索Runtime的消息转发机制和分类Category
- OC运行时机制Runtime(三):关联对象Associated Object和分类Category
-
OC运行时机制Runtime(四):尝试使用黑魔法 Method Swizzling
本文主要分析Category和Associated Object,接前两篇文章详细分析一下一些细节内容。
Category
分类是我们开发过程中必不可少的一个重要技术手段,包括动态添加方法,更换原有方法等,那么首先常规套路分析一下Category的结构。
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
}
这里看到结构体里分别有分类名,类名,实例方法列表,类方法列表,协议列表
这几项,所以我们通常可以在分类中动态添加方法而不能添加实例变量,当然我们也可以更详细的分析一下,方法列表和实例变量列表的区别。
struct objc_class {
Class isa;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
这里看到,objc_ivar_list
结构体里面有ivar_count
以及一个变长结构体ivar_list
,这个结构体里面存储了ivar的各种属性,包括name、type
等,同样objc_method_list
里面也存储了method_count
和变长结构体method_list
,方法结构体包括了方法名和方法实现
,这里SEL类型表示的是选择子的名字,IMP类型表示的方法的具体实现
,这里有一个问题*ivars和**methodLists分别是一级指针和二级指针
,二者的区别是使用一级指针做参数传递时,如果函数改变传入参数的值,原参数指针指向的值不会改变,而使用二级指针做参数传递时,原参数指针指向的值是可以改变的
,所以当使用添加方法时,*methodList
值可以改变,所以可以添加方法。那如果添加属性是否可以呢,我们写一个分类尝试一下
//UIImage+Detection.h
@interface UIImage (Detection)
@property (nonatomic, copy) NSString * remarkName;
@end
//ViewController.m
- (void)viewDidLoad {
UIImage * image = [UIImage imageNamed:@"image_name"];
image.imageName = @"image_name";
NSLog(@"%@", image.imageName);
}
可以看到控制台报出-[UIImage setImageName:]: unrecognized selector sent to instance 0x6000000b1040
找不到setter的错,是因为Category没有给属性自动添加setter和getter方法,但是我们如果使用_remarkName = remarkName;
这种方式重写setter方法,会因为Category不能添加变量而报错,所以这里引出一个概念Associated Object
。
Associated Object——关联对象
当我们给一个系统类添加方法,我们常用的是使用类别来进行扩展,但是如果我们想添加一个系统类的属性,我们通常是使用继承的方式,但是只是添加一个属性就使用继承有些小题大做,这里我们可以使用Associated Object
,下面继续之前的例子,将setter和getter
用关联对象的方式实现,首先请出两个当事人,哦不当事函数。
//为一个实例对象添加一个关联对象,用键来区分,由于是C函数只能使用C字符串,这个key就是关联对象的名称,value为具体的关联对象的值,policy为存储策略,用以维护相应的内存管理语义
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
//通过key和实例对象获取关联对象的值
id objc_getAssociatedObject(id object, const void *key);
//删除实例对象的关联对象
void objc_removeAssociatedObjects(id object);
由于都是c函数,所以oc的内存管理语义在这里也受到了影响,需要做相应的改变,详细的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. */
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. */
};
这里语义内容很清晰了,就不多做解释了,那针对上面的案例,如何用关联对象完成setter和getter
- (void)setImageName:(NSString *)imageName {
objc_setAssociatedObject(self, @selector(imageName), imageName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)imageName {
return objc_getAssociatedObject(self, _cmd);
}
这里的key
,可以使用c字符串等形式,我这里放入表示方法名的SEL类型也可以,_cmd 关键字表示当前方法选择子,也就是@selector(imageName)
,当然这里也可以用静态指针static void *
但是为了保证key
的唯一性,我们还是用当前的方法,可以看到成功打印出结果image_name
,下面对个函数进行详细分析。
objc_setAssociatedObject
我们在ojbc-runtime.m
文件中找到以下代码片段
#if SUPPORT_GC
PRIVATE_EXTERN void objc_setAssociatedObject_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) {
value = objc_msgSend(value, SEL_copy);
}
auto_zone_set_associative_ref(gc_zone, object, (void *)key, value);
}
#endif
PRIVATE_EXTERN void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
#if SUPPORT_GC
if (UseGC) {
if ((policy & OBJC_ASSOCIATION_COPY_NONATOMIC) == OBJC_ASSOCIATION_COPY_NONATOMIC) {
value = objc_msgSend(value, SEL_copy);
}
auto_zone_set_associative_ref(gc_zone, object, (void *)key, value);
} else
#endif
{
// Note, creates a retained reference in non-GC.
_object_set_associative_reference(object, (void *)key, value, policy);
}
}
在这里我们确定方法调用栈的内部方法为_objc_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) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(object);
if (i != associations.end()) {
// secondary table exists
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 = old_entry.value;
old_entry.policy = policy;
old_entry.value = new_value;
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
_class_setInstancesHaveAssociatedObjects(_object_getClass(object));
}
} else {
// 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);
}
由于代码量较多,我们忽略大部分的逻辑部分,看到产生作用的有如下几个类:AssociationsManager,AssociationsHashMap,ObjectAssociationMap,ObjcAssociation
下面一个个看这几个类的作用
AssociationsManager关联对象管理类
class AssociationsManager {
static OSSpinLock _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { OSSpinLockLock(&_lock); }
~AssociationsManager() { OSSpinLockUnlock(&_lock); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new(::_malloc_internal(sizeof(AssociationsHashMap))) AssociationsHashMap();
return *_map;
}
};
OSSpinLock AssociationsManager::_lock = OS_SPINLOCK_INIT;
AssociationsHashMap *AssociationsManager::_map = NULL;
这里实现了OSSpinLock
和AssociationsHashMap
这两个单例,在构造这个方法的时候,会调用OSSpinLockLock
,而在析构的时候会调用OSSpinLockUnlock
,associations
方法可以取得一个AssociationsHashMap
单例,很明显这个管理类通过持有一个自旋锁
保证了操作AssociationsHashMap
是线程安全的,所以每次只有一个线程可以对AssociationsHashMap进行操作
。
ObjcAssociation关联对象实际存储的方式
从ObjectAssociation,ObjectAssociationMap
结构体名定义来看,这两个分别是关联对象结构体和关联对象结构体映射表
,下面上源码
struct ObjcAssociation {
uintptr_t policy;
id value;
ObjcAssociation(uintptr_t newPolicy, id newValue) : policy(newPolicy), value(newValue) { }
ObjcAssociation() : policy(0), value(0) { }
};
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjcAllocator<std::pair<void * const, ObjcAssociation> > > {
public:
void *operator new(size_t n) { return ::_malloc_internal(n); }
void operator delete(void *ptr) { ::_free_internal(ptr); }
};
typedef hash_map<void *, ObjectAssociationMap *, ObjcPointerHash, ObjcPointerEqual, ObjcAllocator<void *> > AssociationsHashMap;
这里ObjcAssociation
关联对象结构体存储了policy存储策略
和value关联对象值
这两个重要属性,ObjcAssociationMap
这里关联了key
到ObjcAssociation
的映射,这个类存储了所有这个对象所关联对象的信息。
拿出我们的经典demo案例,
- (void)setImageName:(NSString *)imageName {
objc_setAssociatedObject(self, @selector(imageName), imageName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
用图来表示一下这段代码的具体实现
下面接着分析_objc_set_associative_reference
这个方法,根据new_value
出现了第一次逻辑判断
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;
用个demo看一下这个new_value
的作用
UIImage * image = [UIImage imageNamed:@"image_name"];
NSLog(@"%@", objc_getAssociatedObject(image, @selector(imageName)));
objc_setAssociatedObject(image, @selector(imageName), @"image_name", OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@", objc_getAssociatedObject(image, @selector(imageName)));
objc_setAssociatedObject(image, @selector(imageName), nil, OBJC_ASSOCIATION_COPY_NONATOMIC);
NSLog(@"%@", objc_getAssociatedObject(image, @selector(imageName)));
2019-03-19 18:21:08.667240+0800 Demo[5301:197907] (null)
2019-03-19 18:21:08.667557+0800 Demo[5301:197907] image_name
2019-03-19 18:21:08.667789+0800 Demo[5301:197907] (null)
这里看得出,如果我们在value
字段设置为nil
,相当于清除了这个关联对象的key
,所以我们初步可以认为,如果new_value为真
,那么逻辑中应该是创建一个关联对象或者修改一个关联对象的值
,如果new_value为假
,那么应该是清除这个关联对象
。为了验证,我们找到逻辑为假这部分代码。
if (j != refs->end()) {
ObjcAssociation &old_entry = j->second;
old_policy = old_entry.policy;
old_value = (id) old_entry.value;
refs->erase(j);
}
这里明显看到调用了erase
函数擦出了这个关联对象的key
。
所以_objc_set_associative_reference
方法流程如下
1.从AssociationsManager
单例中,在线程安全的状态下,取得全局关联对象哈希表AssociationsHashMap
2.根据关联对象所属类,从AssociationsHashMap
取得这个类的关联对象哈希表ObjectAssociationMap
,如果ObjectAssociationMap
这个表不存在则创建一个新的表。
3.根据void * key
,从ObjectAssociationMap
中查找到ObjcAssociation
结构体,如果没有这个结构体则新创建这个结构体。
4.如果new_value
为空,ObjectAssociationMap
会调用erase
函数擦除这个key
。
4.ObjcAssociation
结构体中应存有policy存储策略
和value值
。
以上就是objc_setAssociatedObject
方法的实现流程,另外两个方法原理雷同这里不加以多赘述。
总结
Category结构体中不可以添加实例变量
,可以添加方法
,添加属性时不会自动生成setter和getter方法
,需要我们手动实现,由于不能添加实例变量所以实现这两个方法需要用到关联对象
,它的实质是ObjcAssociation
这个结构体,里面主要存储了存储策略和具体值
,这个结构体存在于ObjectAssociationMap
哈希表中,这个表存储了每个对象具体的关联对象,键为void *类型
的一段字符串,这个哈希表存在于AssociationsHashMap
这个表中,这个表实际根据对象的不同为key
存储了全部关联对象,这个表被AssociationsManager
这个单例所持有,每次调用方法时会以单例的形式创建
,它是线程安全的
。
后续
到这里已经将最重要的三个部分分析好了,分别是运行时结构和消息机制以及关联对象,感兴趣的朋友们可以移步下一篇文章 OC运行时机制Runtime(四):尝试使用黑魔法 Method Swizzling,如果觉得本文对您有些作用,请在下方点个赞再走哈~