objcRuntime黑魔法(class_addMethod,class_replaceMethod)

欢迎转载,请注明出处:
http://zyden.vicp.cc/about-objcruntime/
谢谢

前言:
陈列一下今天要讲的知识点:class_addMethod,class_replaceMethod,method_getImplementation,object_getClass

涉及到的知识
--使用category,通过Runtime实现用自己的函数调换掉原生函数
--oc的message forwarding
--使用Runtime为类添加原来没有的方法
--为什么category里不重写方法

注明:
本文章内技术参考当然来自四面八方,来自不同时期,小弟只是做个总结,有不好的地方欢迎大家指导

先从一个场景问题带出吧,毕业设计的时候小弟做ipad应用,到后面才决定加上旋转屏适配,看着100多个文件20多个页面差点没把血吐出来,哈哈每个controller去修改方法是不可能的了,因为强迫症也不想多创个父类,好吧决定一次过替换掉这些controller里的viewWillAppear: 和 willAnimateRotationToInterfaceOrientation:duration:,换成自己的。




先看一个category
通过运用class_addMethodclass_replaceMethod来调换掉系统库里的方法

#import "NSObject+Swizzle.h"

@implementation NSObject (Swizzle)

+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {

    Method originMethod = class_getInstanceMethod(self, origSel);
    Method newMethod = class_getInstanceMethod(self, aftSel);
    
    if(originMethod && newMethod) {//必须两个Method都要拿到
        if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
            //实现成功添加后
            class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }
        return YES;
    }
    return NO;
}
@end

1.传入两个参数,原方法选择子,新方法选择子,并通过class_getInstanceMethod()拿到对应的Method


2.class_addMethod,是相对于实现来的说的,将本来不存在于被操作的Class里的newMethod的实现添加在被操作的Class里,并使用origSel作为其选择子(注意参数中的self为被操作的Class,不要忘了这里是类方法).


3.class_replaceMethod,addMethod成功完成后,从参数可以看出,目的是换掉method_getImplaementation(roiginMethod)的选择子,将原方法的实现的SEL换成新方法的SEL:aftSel,ok目的达成了。想一想,现在通过旧方法SEL来调用,就会实现新方法的IMP,通过新方法的SEL来调用,就会实现旧方法的IMP,好了理一理思路继续往下。




这次就用NSString做载体来演示吧:

#import "MyString.h"
#import "NSObject+Swizzle.h"

@implementation MyString

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class clazz = object_getClass((id)self);
        [clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
    });
}

+ (BOOL)myResolveInstanceMethod:(SEL)sel {
    
    if(! [self myResolveInstanceMethod:sel]) {
        NSString *selString = NSStringFromSelector(sel);
        if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {
            class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");
            return YES;
        }else {
            return NO;
        }
    }
    return YES;
}

- (void)dynamicMethodIMP {
    NSLog(@"我是动态加入的函数");
}

@end

1.首先这里要提下resolveInstanceMethod:,不了解的朋友可以去补一下oc的message forwarding,就是当运行时对象调用了一个找不到的方法的时候系统会去寻找的机制,这个方法是第一步去到的地方,我们可以在这里面runtime添加方法,是的,首先我们得劫持了这个方法,做我们自己的事,通过刚才category里封装好的swizzleMethod:withMethod:
-------这个时候有朋友有疑问了,我们可以重写这个方法来做自己的事情啊,其实并不可以,在category里重写现有方法会有警告#Category is implementing a method which will also be implemented by its primary class,这种做法是不提倡的!
------------category没有办法去代替子类,它不能像子类一样通过super去调用父类的方法实现。如果category中重写覆盖了当前类中的某个方法,那么这个当前类中的原始方法实现,将永远不会被执行,这在某些方法里是致命的(这里提一下一个特例+(void)load,它会在当前方法里执行完再去category里执行).
------------如果两个category重写了同一个方法,我们无法控制哪个优先级更高,一直以来还是提倡通过继承去重写方法


2.object_getClass拿到当前MyString的Class,调用刚才category里封装好的swizzleMethod:withMethod:,用我们自己的myResolveInstanceMethod:去替换原生的,好了,现在如果我们在运行时调用了一个不存在的方法,系统会去调用我们的myResolveInstanceMethod:,是的不用怀疑。


3.现在看看myResolveInstanceMethod:里面又调用了一次myResolveInstanceMethod:,有的朋友会以为是递归其实并不是,系统去调用原生的方法,会跑到我们自己的方法实现,是因为我们之前的swizzle操作没问题,而不要忘记了,我们自己的方法selector对应的实现,已经换成了原生方法的实现,ok。。if(! [self myResolveInstanceMethod:sel])是调用原生方法的实现,去检测一次传入的方法是否存在,如果还是没有,则做class_addMethod操作为此类添加对应的方法,return YES,该方法被系统调用,OK,达到目的。


class_addMethod参数的意义

class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)

class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");

按顺序是,类--选择子--实现--方法的返回值和参数资料。
v代表返回值void,@代表id类型对象,:代表选择子。
why? 其实每一个oc方法都有两个隐式的参数(id self, SEL _cmd),也可以说是由C语言函数再加着两个参数组成一个oc方法。

最后看看我们的工作的收获:

NSLog(@"begin test");
//------------------------------------------------
    
    MyString *string = [[MyString alloc] init];
    [string performSelector:@selector(countAll)];
    [string performSelector:@selector(pushViewController)];
<pre name="code" class="objc">
//------------------------------------------------
    NSLog(@"finish test");

-----Log:
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] begin test
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是动态加入的函数
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 724评论 0 2
  • 本文转载自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex阅读 744评论 0 1
  • 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的...
    lylaut阅读 791评论 0 4
  • 12月21日作业: 1.把今天学习好的归类去做归类,执行! 2.听2个产品课件记下笔记。 3.增加10个名单。 新...
    晓风姐卓越快乐阅读 173评论 0 0