Runtime 学习笔记

Runtime

1.对象和类

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:

typedef struct objc_class *Class;

在objc/runtime.h中,struct objc_class 的定义如下:

struct objc_class {
    Class isa;//元类
    #if !__OBJC2__
    Class super_class//父类                    OBJC2_UNAVAILABLE;
    long version//版本信息                      OBJC2_UNAVAILABLE;
    long info//类信息                           OBJC2_UNAVAILABLE;
    long instance_size//实例变量大小             OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars//成员变量链表   OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists//办法 OBJC2_UNAVAILABLE;  
    struct objc_cache *cache//办法缓存         OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols//协议  OBJC2_UNAVAILABLE;
    #endif
} OBJC2_UNAVAILABLE;

虽然这个结构体已经过时了,但是还是有参考的意义,比起最新的版本更好理解。

由于 objc_class 也有 isa 指针,所以类本身也是一个对象。

下面就根据runtime提供的一些办法,使用简单的例子来探窥其究竟:

//首先创建一个测试类
#import <Foundation/Foundation.h>

@interface MyRuntimeTestClass : NSObject

@property (nonatomic, strong) NSArray *array;
@property (nonatomic, copy) NSString *string;

- (void)method1;
- (void)method2;
+ (void)classMethod1;
@end

#import "MyRuntimeTestClass.h"

@interface MyRuntimeTestClass ()
{
    NSInteger _instance1;
    NSString *_instance2;
}

@property (nonatomic, assign) NSInteger *integer;

- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;

@end

@implementation MyRuntimeTestClass

+(void)classMethod1
{
    
}

-(void)method1
{
    NSLog(@"call the method1");
}

-(void)method2
{
    NSLog(@"call the method2");
}

- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
    NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}

@end
//测试类和对象
-(void)testMyRuntimeTestClass
{
    MyRuntimeTestClass *obj = [[MyRuntimeTestClass alloc]init];
    self.myRuntimeTestClass = obj;
    unsigned int outCount = 0;
    Class cls = obj.class;
    
    NSLog(@"class name :%s",class_getName(cls));
    NSLog(@"==========================================================");

    NSLog(@"super class name :%s",class_getName(class_getSuperclass(cls)));
    NSLog(@"==========================================================");
    
    Class metaClass = objc_getMetaClass(class_getName(cls));
    NSLog(@"%s metaClass name:%s",class_getName(cls),class_getName(metaClass));
    NSLog(@"==========================================================");
    
    NSLog(@"%s is %@ a metaClass",class_getName(cls),class_isMetaClass(cls) ? @"" : @"not");
    NSLog(@"==========================================================");
    
    NSLog(@"class size:%zu",class_getInstanceSize(cls));
    NSLog(@"==========================================================");
    
    // 成员变量
    Ivar *ivars = class_copyIvarList(cls, &outCount);//获取整个成员变量列表
    for (int i = 0; i < outCount; i++) {
        Ivar ivar = ivars[i];
        NSLog(@"instance variable's name:%s at index:%d",ivar_getName(ivar),i);
    }
    free(ivars);
    
    Ivar array = class_getInstanceVariable(cls, "_array"); //获取类中指定名称实例成员变量的信息
    if (array != NULL) {
        NSLog(@"instance variable's %s",ivar_getName(array));
    }
    NSLog(@"==========================================================");
    
    // 属性操作
    objc_property_t *properties = class_copyPropertyList(cls, &outCount);
    for (int i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        NSLog(@"property's name: %s",property_getName(property));
    }
    free(properties);
    
    objc_property_t string = class_getProperty(cls, "_string");
    if (string != NULL) {
        NSLog(@"property %s",property_getName(string));
    }
    NSLog(@"==========================================================");
    
    // 方法操作
    Method *methods = class_copyMethodList(cls, &outCount);
    for (int i = 0; i < outCount; i++) {
        Method method = methods[i];
        NSLog(@"method's signature:%s",sel_getName(method_getName(method)));
    }
    free(methods);
    
    Method method1 = class_getInstanceMethod(cls, @selector(method1));
    if (method1 != NULL) {
        NSLog(@"instand method %s",sel_getName(method_getName(method1)));
    }
    
    Method classMethod = class_getClassMethod(cls, @selector(classMethod1));
    if (classMethod != NULL) {
        NSLog(@"class method %s",sel_getName(method_getName(method1)));
    }
    
    IMP imp = class_getMethodImplementation(cls, @selector(method1));
    IMP imp2 = method_getImplementation(class_getInstanceMethod(cls, @selector(method1)));
    
    imp();
    imp2();
    NSLog(@"==========================================================");

    /**
     *  1.成员变量和属性: (1) 成员变量内部使用,属性外部使用,属性是为了让类外能够访问到成员变量,即是属性是外部访问成员变量的接口。
     (2)类的变量包含成员变量和属性,成员变量就好似一个人的自己固有的属性(如大脑,眼睛等等),属性是一个人的外部特征(如他的名字,职业等等)。
     (3)只要@property 声明了成员变量,SDK自动生成成员变量,不要需要手动对应,是专用于从类外部对其进行调用或赋值的.
     (4)成员变量命名方式:_变量名
     2.@property:自动创建setter and getter
     3.类对象和类的实例(即对象):
     */
}

根据上面的一些例子,我们来看看元类:

1413628797629491
1413628797629491
/**
 *  测试NSObject
 */
-(void)testNSObject
{
    NSObject *obj = [[NSObject alloc]init];
    Class cls = obj.class;//typedef struct objc_class *Class;
    
    NSLog(@"NSObject name is %s",class_getName(cls));
    NSLog(@"NSObject super class name is %s",class_getName(class_getSuperclass(cls)));// object super isa
    NSLog(@"NSObject metaClass name is %s",class_getName(objc_getMetaClass(class_getName(cls))));// object isa
}

2.方法和消息

selector:

常说的方法或是选择子,其实质就是函数的指针,根据 selector 可以找到对应的办法。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL

IMP

IMP实际上是一个函数指针,指向方法实现的首地址,是一个函数实现的入口。

办法的调用:

Objective-C 语言中,消息直到运行时才被绑定起来。编译器会将消息表达式[receiver message]转化为一个消息函数的调用,即objc_msgSend

这个函数完成了动态绑定的所有事情:

  1. 首先它找到selector对应的方法实现。因为同一个方法可能在不同的类中有不同的实现,所以我们需要依赖于接收者的类来找到的确切的实现。
  2. 它调用方法实现,并将接收者对象及方法的所有参数传给它。
  3. 最后,它将实现返回的值作为它自己的返回值。
messaging1.gif

获取办法的地址

该方式就类似 C 语言的函数调用,直接找到函数的地址。一般情况下不会使用该方式,只有在比较频繁调用某个函数的时候才会使用该方式。即使 OC 中使用了办法缓存机制,如果一个办法频繁调用,根据缓存列表,快速找到相应的办法和办法实现,也是有一定的时间的。

/**
 *  直接获取办法的地址:有cocoa框架提供,并非 Objective-C 语言的特性 (一个办法比较频繁调用才会使用该办法,相当于直接调用函数)
 */
-(void)testGetMethodAdress
{
    void (*setter)(id, SEL, BOOL);
    setter = (void (*)(id, SEL, BOOL))[MyRuntimeTestClass methodForSelector:@selector(method1)];
    for (int i = 0 ; i < 1000 ; i++)
        setter(self, @selector(method1), YES);
}

消息转发

当对象接收到无法解读的消息后,就会启动”消息转发“。消息转发分三个阶段:动态解析,备用接收者,完整的消息转发。

动态解析:

该方式是当对象接收到无法解读的消息后,会调用 +(BOOL)resolveInstanceMethod:(SEL)sel,

+(BOOL)resolveClassMethod:(SEL)sel

这两个办法,这时候我们可以在该办法处理对象无法解读的消息。

/**
 *  动态解析:当对象接收到无法识别的消息,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)
 */
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"resolveInstanceMethod");
    NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
    
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"testMySendMsgOfDymanicMehod"]) {
        class_addMethod(self.class, @selector(testMySendMsgOfDymanicMehod), (IMP)functionForTestMySendMsgOfDymanicMehod, "@:");
    }
    return [super resolveInstanceMethod:sel];;
}
+(BOOL)resolveClassMethod:(SEL)sel
{
    NSLog(@"resolveClassMethod");
    NSLog(@"can not performSelector called: %@",NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}
void functionForTestMySendMsgOfDymanicMehod(id self, SEL _cmd)
{
    NSLog(@"the IMP of functionForTestMySendMsgOfDymanicMehod");
}

备用接收者:

当第一步的动态解析也无法解读未知的消息的时候,就会转向备用接收者:-(id)forwardingTargetForSelector:(SEL)aSelector

其实质就是寻找有没有其他对象能解读该对象,即是所谓的备用接收者。

/**
 *  备用接收者
 */
-(id)forwardingTargetForSelector:(SEL)aSelector
{
    NSLog(@"forwardingTargetForSelector");
    NSLog(@"can not performSelector called: %@",NSStringFromSelector(aSelector));
    NSString *selectorString = NSStringFromSelector(aSelector);
    if ([selectorString isEqualToString:@"method1"]) {
        
        return self.myRuntimeTestClass;
    }
    return [super forwardingTargetForSelector:aSelector];
}

完整消息转发:

当前面两种方式都不能解读未知的消息,那就启用最后一中方式,即是将尚未处理的那条消息的有关的全部细节都封装在 NSInvocation 中,此对象包含选择子,目标以及参数。在触发 NSInvocation 对象时,“消息派发系统”将亲自出马,把消息指派给目标对象。

/**
 *  由于完整的消息转发需要将尚未处理的消息有关的全部细节封装在NSInvocation中,故应重写该办法。
 */
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"methodSignatureForSelector:");
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if ([MyRuntimeTestClass instancesRespondToSelector:aSelector]) {
            
            signature = [MyRuntimeTestClass instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
/**
 *  完整的消息转发
 */
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"forwardInvocation");
    if ([MyRuntimeTestClass resolveClassMethod:anInvocation.selector]) {
        
        anInvocation.target = self.myRuntimeTestClass;
    }
}

3.办法交换

有时候我们需要替换系统的办法时,比如我想在每次调用

-(void)viewWillAppear:(BOOL)animated

都打印一段话。这样每个 VC 都码上打印语句,未免效率有些不高,这时候我们就可以使用办法交换,将我们自己的办法和系统的办法交换。

/**
 *  第一次调用类的类方法或实例方法之前被调用
 */
+(void)initialize
{
    NSLog(@"initialize");
}

/**
 *  在类初始加载时调用
 */
+(void)load
{
    NSLog(@"load");
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        Class class = [self class];
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzlingSelector = @selector(YYviewWillAppear:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzlingMethod = class_getInstanceMethod(class, swizzlingSelector);
        
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
        
        if (didAddMethod) {
            
            //实现两个办法的实现部分交换
            class_replaceMethod(class, swizzlingSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }else{
            
            method_exchangeImplementations(originalMethod, swizzlingMethod);
        }
    });
    
    /*
        1.Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。
     
        2.Method(typedef struct objc_method *Method):在类定义中表示方法的类型
     
        3. Implementation(typedef id (*IMP)(id, SEL, ...)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。
     */
}

#pragma mark -method swizzling

-(void)YYviewWillAppear:(BOOL)animated
{
    [self YYviewWillAppear:animated];
    NSLog(@"method had changed!");
}

注:办法的交换永远都应该在 load 办法中实现,因为 load 办法在类初始化的时候就会调用,而 initialize 要在类被发送消息的时候才会调用。

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 611评论 0 2
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,541评论 33 466
  • 我们都知道OC是一门动态语言,那么什么是动态语言呢?动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,...
    闫仕伟阅读 521评论 0 4
  • 在Objective-C中,消息直到运行时才绑定到方法实现上。编译器将消息表达式[receiver message...
    我系哆啦阅读 424评论 0 15