MethodSwizzling

一.Method Swizzling介绍

objective-c 方法分为方法名(selector)和方法实现(IMP)两部分,当调用方法时,Objective-C运行时根据selecotr去找匹配的实现代码。见”关于Objective-C方法的IMP“的介绍。MethodSwizzling是一种在运行时修改方法名与方法实现映射关系的技术,常用于在系统内部方法的基础上增加新的处理流程。
假设我们有一个类T,其有两个方法为swizzleMethod方法和altMethod方法。

- (void)swizzleMethod {
   NSLog(@"Original method called");
}

- (void)altMethod {
   NSLog(@"Alternative method called");
   [self altMethod];
}

我们以如下方式使用

   T *t = [[T alloc] init];
   [t swizzleMethod];
   NSLog(@"Methods swizzled");
   MethodSwizzle([T class],
                 @selector(swizzleMethod),
                 @selector(altMethod));

   [t swizzleMethod];
   [t release];

则输出为:

Original method called
Methods swizzled
Alternative method called
Original method called

第一次调用swizzleMethod方法时,显示是Original method called,与函数实现相同;当进行了MethodSwizzle之后,再次调用swizzleMethod方法,则多输出了一行Alternative method called,实际调用了altMethod,且altMethod内部仍然调用了altMethod方法,看似会死循环的代码。
解释如下:方法实现分为selector和IMP两部分,则我们分别用selector(swizzleMethod)和IMP(swizzleMethod)表示。
未使用MethodSwizzle之前

selector(swizzleMethod) -> IMP(swizzleMethod)
selector(altMethod) -> IMP(altMethod)

这时候如代码所示,如果[t swizzleMethod]会输出Original method called;而[t altMethod]则会死循环。
当我们进行了MethodSwizzle方法是,实际上交换了swizzleMethod和altMethod的IMP部分,即

selector(swizzleMethod) -> IMP(altMethod)
selector(altMethod) -> IMP(swizzleMethod)

这时候我们再次调用[t swizzleMethod],实际执行却是altMethod的IMP,所以会先输出Alternative method called,而在IMP(altMethod)方法中调用[t altMethod],则实际上执行又是IMP(swizzleMethod),所以会再次输出Original method called。用这种方式在原来swizzleMethod方法上增加了输出Alternative method called的处理,而不用修改函数调用。

二.经典MethodSwizzle实现

MethodSwizzle函数的实现如下,即交换两个方法的IMP。

import 
void MethodSwizzle(Class aClass, SEL orig_sel, SEL alt_sel) {
   Method orig_method = nil, alt_method = nil;
   // First, look for the methods
   orig_method = class_getInstanceMethod(aClass, orig_sel);
   alt_method = class_getInstanceMethod(aClass, alt_sel);
   // If both are found, swizzle them
   if ((orig_method != nil) && (alt_method != nil))
       {
       char temp1;
       IMP temp2;
       temp1 = orig_method->method_types;
       orig_method->method_types = alt_method->method_types;
       alt_method->method_types = temp1;
       temp2 = orig_method->method_imp;
       orig_method->method_imp = alt_method->method_imp;
       alt_method->method_imp = temp2;
       }
}

这种实现有一个问题,如果需要修改的方法是在父类中实现的,那么MethodSwizzle会把所有父类的方法改变了,而我们仅仅想修改当前子类的实现。比如,我们仅想修改UIButton的行为,但修改的方法是继承自UIView的,进行了MethodSwizzle之后,实际上将UIView及所有UIView的子类方法都修改了,这不是我们想要的。

三.解决继承问题的MethodSwizzle

解决继承问题的方法之一是,如果修改的或用于替换的方法来自父类,则拷贝一份放到当前类中,然后再进行MethodSwizzle。
Method Swizzling reimplemented,核心代码如下:

static BOOL _PerformSwizzle(Class klass, SEL origSel, SEL altSel, BOOL forInstance);

BOOL ClassMethodSwizzle(Class klass, SEL origSel, SEL altSel) {
	return _PerformSwizzle(klass, origSel, altSel, NO);
}

BOOL MethodSwizzle(Class klass, SEL origSel, SEL altSel) {
	return _PerformSwizzle(klass, origSel, altSel, YES);
}
// if the origSel isn't present in the class, pull it up from where it exists
// then do the swizzle
BOOL _PerformSwizzle(Class klass, SEL origSel, SEL altSel, BOOL forInstance) {
    // First, make sure the class isn't nil
	if (klass != nil) {
		Method origMethod = NULL, altMethod = NULL;

		// Next, look for the methods
		Class iterKlass = (forInstance ? klass : klass->isa);
		void *iterator = NULL;
		struct objc_method_list *mlist = class_nextMethodList(iterKlass, &iterator);
		while (mlist != NULL) {
			int i;
			for (i = 0; i < mlist->method_count; ++i) {
				if (mlist->method_list[i].method_name == origSel) {
					origMethod = &mlist->method_list[i];
					break;
				}
				if (mlist->method_list[i].method_name == altSel) {
					altMethod = &mlist->method_list[i];
					break;
				}
			}
			mlist = class_nextMethodList(iterKlass, &iterator);
		}

		if (origMethod == NULL || altMethod == NULL) {
			// one or both methods are not in the immediate class
			// try searching the entire hierarchy
			// remember, iterKlass is the class we care about - klass || klass->isa
			// class_getInstanceMethod on a metaclass is the same as class_getClassMethod on the real class
			BOOL pullOrig = NO, pullAlt = NO;
			if (origMethod == NULL) {
				origMethod = class_getInstanceMethod(iterKlass, origSel);
				pullOrig = YES;
			}
			if (altMethod == NULL) {
				altMethod = class_getInstanceMethod(iterKlass, altSel);
				pullAlt = YES;
			}

			// die now if one of the methods doesn't exist anywhere in the hierarchy
			// this way we won't make any changes to the class if we can't finish
			if (origMethod == NULL || altMethod == NULL) {
				return NO;
			}

			// we can safely assume one of the two methods, at least, will be pulled
			// pull them up
			size_t listSize = sizeof(struct objc_method_list);
			if (pullOrig && pullAlt) listSize += sizeof(struct objc_method); // need 2 methods
			struct objc_method_list *mlist = malloc(listSize);
			mlist->obsolete = NULL;
			int i = 0;
			if (pullOrig) {
				memcpy(&mlist->method_list[i], origMethod, sizeof(struct objc_method));
				origMethod = &mlist->method_list[i];
				i++;
			}
			if (pullAlt) {
				memcpy(&mlist->method_list[i], altMethod, sizeof(struct objc_method));
				altMethod = &mlist->method_list[i];
				i++;
			}
			mlist->method_count = i;
			class_addMethods(iterKlass, mlist);
		}

		// now swizzle
		IMP temp = origMethod->method_imp;
		origMethod->method_imp = altMethod->method_imp;
		altMethod->method_imp = temp;

		return YES;
	}
	return NO;
}

MethodSwizzle.h
MethodSwizzle.m

四.method_exchangeImplementations API

Apple在10.5引入了新的API method_exchangeImplementations用于交换方法的IMP,其同样没有解决继承问题,使用方法如下:

Method myReplacementMethod =
    class_getClassMethod([MyClass class], @selector(myReplacementMethod));
Method windowDealloc =
    class_getInstanceMethod([NSWindow class], @selector(dealloc));
method_exchangeImplementations(myReplacementMethod, windowDealloc);

五.JRSwizzle

jrswizzle中总结了以上几种Method Swizzling的实现,并实现了一个完善的JRSwizzle版本,可以解决继承问题,且可以在不同版本系统下使用。JRSwizzle在NSObjective的类别中实现,使用方法为:

[SomeClass jr_swizzle:@selector(foo) withMethod:@selector(my_foo) error:&error];

JRSwizzle.h
JRSwizzle.m

参考:
MethodSwizzling wiki
Method Swizzling reimplemented
jrswizzle
Supersequent implementation
Objective-C Runtime Reference

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值