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)

iOS runtime基础_第1张图片
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 
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

6、埋点

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

参考文章

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

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

Runtime Method Swizzling开发实例汇总

打个断点。后续补充

你可能感兴趣的:(iOS runtime基础)