iOS runtime基础

1、概述
2、isa ,SEL,IMP, Method 关系
3、消息机制 以及消息转发机制
4、runtime的使用场景
5、参考文章

......

概述:

runtimeObjective-C是动态语言,它将很多静态语言在编译和链接时做的事放到了运行时,这个运行时系统就是runtime
runtime其实就是一个库,它基本上是用C和汇编写的一套API,这个库使C语言有了面向对象的能力。
OC是动态语言:函数真正调用的时机是在运行时,在运行的时候根据函数的名称找到对应的函数来调用。

isa ,SEL,IMP, Method 关系

OC中,类和类的实例在本质上没有区别,都是对象,任何对象都有isa指针,它指向类或元类

SEL:SEL(选择器)是方法的selector的指针。方法的selector表示运行时方法的名字。OC在编译时,会依据每一个方法的名字、参数,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL

IMP:IMP是一个函数指针,指向方法最终实现的首地址。SEL就是为了查找方法的最终实现IMP。

Method:用于表示类定义中的方法,它的结构体中包含一个SEL和IMP,相当于在SEL和IMP之间作了一个映射。

消息机制与消息转发机制

既然是运行时机制, 那么处理消息的时机就在运行时的时候处理。消息直到运行时才绑定到方法的实现上。编译器会将消息表达式[receiver message]转化为一个消息函数,即objc_msgSend(receiver, selector)

objc_class的定义.png

isa指针的作用:当我们向一个对象发送消息时,runtime会根据这个对象的isa指针找到这个对象所属的类,在这个类的方法列表及父类的方法列表中,寻找与消息对应的selector指向的方法,找到后就运行这个方法。

执行顺序是这样的:

1. 通过对象的`isa`指针获取类的结构体。

2. 在结构体的方法表里查找方法的`selector`。

3. 如果没有找到`selector`,则通过`objc_msgSend`结构体中指向父类的指针找到父类,并在父类的方法表里查找方法的`selector`。

4. 依次会一直找到`NSObject`。

5. 一旦找到`selector`,就会获取到方法实现`IMP`。

6. 传入相应的参数来执行方法的具体实现。

7. 如果最终没有定位到`selector`,就会走消息转发流程。


接上面最后一步,如果还是没有定位到selector,那么怎么进行消息转发呢。

消息转发分为三个步骤:

1、动态方法解析,我们可以利用运行时绑定方法。当消息机制触发时,selectorfunction时那么就会利用运行时库动态添加一个方法,而这个方法的函数实现是我们用C写的一个函数,这个函数的两个参数为self,和_cmd,因为OC方法的本质就是至少包含两个参数的C函数这两个参数便是隐藏的self,和_cmd,前者是消息接受者,后者是一个SEL指针
+(BOOL)resolveClassMethod:(SEL)sel{
    
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(function)) {
        class_addMethod(self, sel,(IMP)function , "v@:");
        return YES;
    }
    return [super resolveClassMethod:sel];
}
void function (id self,SEL _cmd)
{
    NSLog(@"绑定方法");
}

2、习惯一般称为备用者。
-(id)forwardingTargetForSelector:(SEL)aSelector{
    return [NewObject new]; //这个备用对象实现了需要的方法
}
3、如果上面两个方法都没有实现,没有进行动态绑定也没有指定备用对象处理,会执行这一步。首先进行签名,然后返回给NSInvocation对象使用。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector==@selector(function)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        
    }
    return [super methodSignatureForSelector: aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector =[anInvocation selector];
    NewObject *new1=[NewObject new];
    NewObject *new2=[NewObject new];
    if ([RP1 respondsToSelector:selector]) {
        
        [anInvocation invokeWithTarget:new1];
        
    } if ([RP2 respondsToSelector:selector]){
        
        [anInvocation invokeWithTarget:new2];
    }
}

可以看到消息可以可以转发给多个对象进行处理.

runtime的使用场景

1、字典转模型等,可以参考MJExtension源码 或者其他JsonModel等。
2、方法交换(黑魔法)。

+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{
    
    Class class = [self class];
    //原有方法
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    //替换原有方法的新方法
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    //先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况
    BOOL didAddMethod = class_addMethod(class,originalSelector,
    method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    
    if (didAddMethod) {
        //添加成功:说明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
        class_replaceMethod(class,swizzledSelector,
                            method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
            //添加失败:说明源SEL已经有IMP,直接将两个SEL的IMP交换即可
            method_exchangeImplementations(originalMethod, swizzledMethod); } }
            
           
@end

@implementation UIViewController (LXSwizzling)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self methodSwizzlingWithOriginalSelector:@selector(viewWillDisappear:) bySwizzledSelector:@selector(sure_viewWillDisappear:)];
    });
}
- (void)sure_viewWillDisappear:(BOOL)animated {
    [self sure_viewWillDisappear:animated];
    [MBProgressHUD hideHUDForView:self.view animated:YES];
}

为什么方法交换调用在+load方法中?

Objective-C runtime会自动调用两个类方法,分别为+load与+ initialize+load 方法是在类被加载的时候调用的,也就是一定会被调用。而+initialize方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。也就是说+initialize方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是永远不会被调用的。此外+load方法还有一个非常重要的特性,那就是子类、父类和分类中的+load方法的实现是被区别对待的。换句话说在Objective-C runtime自动调用+load方法时,分类中的+load方法并不会对主类中的+load方法造成覆盖。综上所述,+load方法是实现 Method Swizzling 逻辑的最佳“场所”。如需更深入理解

为什么方法交换要在dispatch_once中执行?

方法交换应该要线程安全,而且保证在任何情况下(多线程环境,或者被其他人手动再次调用+load方法)只交换一次,防止再次调用又将方法交换回来。除非只是临时交换使用,在使用完成后又交换回来。 最常用的解决方案是在+load方法中使用dispatch_once来保证交换是安全的。之前有读者反馈+load方法本身即为线程安全,为什么仍需添加dispatch_once,其原因就在于+load方法本身无法保证其中代码只被执行一次。

3、给分类添加属性

使用 objc_getAssociatedObject 和 objc_setAssociatedObject 来做到存取方法。

#import "Person+AddAttribute.h"
#import <objc/runtime.h>
static NSString *ageKey = @"ageKey";
static NSString *ageKey2 = @"ageKey2";
@implementation Person (AddAttribute)
-(void)setAge:(NSString *)age{
    objc_setAssociatedObject(self, &ageKey, age, OBJC_ASSOCIATION_RETAIN);
}
-(NSString *)age{
    return objc_getAssociatedObject(self, &ageKey);
}
-(void)setAge2:(NSUInteger)age2{
    objc_setAssociatedObject(self, &ageKey2, @(age2), OBJC_ASSOCIATION_ASSIGN);
}
-(NSUInteger)age2{
    NSNumber *numVaue = objc_getAssociatedObject(self, &ageKey2);
    return [numVaue integerValue];
}

4.关于强制横竖屏的时候有用到。

if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
                
                SEL selector = NSSelectorFromString(@"setOrientation:");
                
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
                
                [invocation setSelector:selector];
                
                [invocation setTarget:[UIDevice currentDevice]];
                
                int val = UIInterfaceOrientationPortrait;
                
                [invocation setArgument:&val atIndex:2];
                
                [invocation invoke];
                
              
            }

5、获得成员列表与属性列表

 unsigned int count = 0; /** Ivar:表示成员变量类型 */
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    //获得一个指向该类成员变量的指针
    for (int i =0; i < count; i ++) {
        //获得Ivar
        Ivar ivar = ivars[i];
        //根据ivar获得其成员变量的名称--->C语言的字符串
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        NSLog(@"%d----%@",i,key);
    }
    NSLog(@"\n");

    
    
    unsigned int count2 = 0;
    //属性列表
    objc_property_t *properties = class_copyPropertyList([UIButton class], &count2);
    
    for (int i =0; i<count2; i++) {
        objc_property_t property = properties[i];
        const char  *name = property_getName(property);
        NSString *key = [NSString stringWithUTF8String:name];
        NSLog(@"%d ----%@",i,key);
    }

6、埋点

iOS动态性(二)可复用而且高度解耦的用户统计埋点实现

参考文章

http://www.jianshu.com/p/d6a68575ce10

http://www.jianshu.com/p/91708b5b0501

Runtime Method Swizzling开发实例汇总

打个断点。后续补充

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

推荐阅读更多精彩内容