在我们开发中,runtime
常用的特性还有method swizzling
,和阅读property
源码一样,我们来做一个实例代码,下面我们摘抄一段从NSHipster的代码:
#import
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 当交换一个类方法是,使用下面的代码:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
在上面的代码中,我们可以找到runtime
相关的关键方法:
-
class_getInstanceMethod
获取类的实例方法 -
class_getClassMethod
获取类的类方法 -
object_getClass
根据实例的类型 -
class_addMethod
添加方法 -
class_replaceMethod
替换方法 -
method_exchangeImplementations
交换方法的实现 -
method_getTypeEncoding
获取方法的返回类型
下面我们就来一一的阅读这些方法的在runtime
中是如何实现的。
class_getInstanceMethod和class_getClassMethod
我们可以先看看两个方法的实现
/// class_getClassMethod
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
/// class_getInstanceMethod
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
我们可以看出这两个方法其实最后都是调用到class_getInstanceMethod
这一个方法上面,我们看到class_getClassMethod
中调用cls->getMeta()
,然后进一步调用class_getInstanceMethod
,我们先来看一下getMeta()
究竟做了什么事情。
// 这个方法其实最后就是获取到class的`ISA`指针
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
在class_getInstanceMethod
中,我们可以看到主要由lookUpImpOrNil
和_class_getMethod
两个方法组成,其中_class_getMethod
主要返回了最后的Method
,接下来,我们仔细的阅读一下这两个方法。
/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
......
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
....
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
......
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
这一段代码很长,但是如调用时候的注释// Search method lists, try method resolver, etc.
可以看出这个方法就是搜索方法列表,尝试方法解析,这个方法的注释十分详细,就不一一的翻译和解释这个方法了。
我们看一下主要的方法_class_getMethod
static Method _class_getMethod(Class cls, SEL sel)
{
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
method_t *m = nil;
runtimeLock.assertLocked();
// fixme nil cls?
// fixme nil sel?
assert(cls->isRealized());
// 查看cls是否该方法,如果没有,则指向父类。
while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
cls = cls->superclass;
}
return m;
}
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
这里runtime
一共有三层方法去查找了Method
,_class_getMethod
这个方法主要是用于加锁,保证后续代码的安全,getMethod_nolock
主要是遍历使用getMethodNoSuper_nolock
判断当前类是否有我们期望找到的方法。getMethodNoSuper_nolock
这个方法从名称上就可以看出,这个是查找当前类方法,不包括父类。从这三个方法之后看是否可以查找到Method,如果可以则返回,否则返回nil
。
class_addMethod和class_replaceMethod
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
mutex_locker_t lock(runtimeLock);
return addMethod(cls, name, imp, types ?: "", YES);
}
/**********************************************************************
* addMethod
* fixme
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertLocked();
checkIsKnownClass(cls);
assert(types);
assert(cls->isRealized());
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
result = m->imp;
} else {
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(method_t) | fixed_up_method_list;
newlist->count = 1;
newlist->first.name = name;
newlist->first.types = strdupIfMutable(types);
newlist->first.imp = imp;
prepareMethodLists(cls, &newlist, 1, NO, NO);
cls->data()->methods.attachLists(&newlist, 1);
flushCaches(cls);
result = nil;
}
return result;
}
这里列举了class_addMethod
和class_replaceMethod
两个方法的实现,他们都共同调用addMethid
去完成后续操作,在addMethod
中会通过getMethodNoSuper_nolock
当前子类是否有这个方法,如果有返回该方法或者替换该方法,如果没有该方法,则向method_list_t
中添加一个method
。
method_exchangeImplementations
我们看一下method_exchangeImplementations
的声明,在注释部分,我们可以看到这个方法等于下面方法的自动版本。那么我们先来看看method_setImplementation
和method_getImplementation
做了些什么呢?
/**
* @note This is an atomic version of the following:
* \code
* IMP imp1 = method_getImplementation(m1);
* IMP imp2 = method_getImplementation(m2);
* method_setImplementation(m1, imp2);
* method_setImplementation(m2, imp1);
* \endcode
*/
首先,我们看一下method_getImplementation
的实现,从代码我们可以发现,他直接返回了Method
的impl
。
IMP
method_getImplementation(Method m)
{
return m ? m->imp : nil;
}
接下来我们看一下method_setImplementation
的实现方法。
/***********************************************************************
* method_setImplementation
* Sets this method's implementation to imp.
* The previous implementation is returned.
**********************************************************************/
static IMP
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
runtimeLock.assertLocked();
if (!m) return nil;
if (!imp) return nil;
IMP old = m->imp;
m->imp = imp;
// Cache updates are slow if cls is nil (i.e. unknown)
// RR/AWZ updates are slow if cls is nil (i.e. unknown)
// fixme build list of classes whose Methods are known externally?
flushCaches(cls);
//更新类的方法树,标记类和方法是否设置了使用了`method_swizzling`
updateCustomRR_AWZ(cls, m);
return old;
}
首先,_method_setImplementation
做了一个方法交换,紧接着,更新了设置Method
的类,在注释中我们可以发现,方法交换,最好不要对全局方法做方法交换,会导致flushCaches
和updateCustomRR_AWZ
更新缓慢。
method_getTypeEncoding
const char *
method_getTypeEncoding(Method m)
{
if (!m) return nil;
return m->types;
}
method_getTypeEncoding
这个方法比较简单,他直接返回这个Method定义下的类型。
小结
基本上跟method swizzling
相关的方法和使用就差不多全部介绍完了,更多的方法可以自己去阅读和学习~
更好的阅读体验可以参考个人网站:https://zevwings.com