小白看runtime(一)基本概念

最近网上看了不少runtime的文章,自己总结一些也摘抄一些,避免过快的忘记

runtime

引入

当我们在编写OC代码时,或者是在看博客的时候,或者在最初学习OC语法的时候,当去调用一个方法时不止一次的看到或者被告知这种行为不是调用方式而是在“发消息” 这个概念,但是很少或者没有去了解究竟是为什么。那究竟为什么把“方法调用”称作发消息呢?

这就要从OC语言讲起,众所周知 OC是一门动态语言,什么是动态语言呢?

动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的JavaScript便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。

既然有动态语言那肯定有静态语言:

静态类型语言的类型判断是在运行前判断(如编译阶段),比如C#、java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,等特殊语法。

所以不同于静态类型语言 OC总是想办法把一些决定工作从编译连接推迟到运行时,需要编译器和运行时系统 (runtime system) 一起来执行编译后的代码。

C语言是一门静态语言,C语言是OC的基石,运行时机制(runtime system)不仅使得OC保持的C语言编译时的特性(如类型检查) 还借鉴了smalltalk机制为其添加了运行时机制也就是我们要介绍的runtime机制,runtime机制使得C语言具有了面对对象的特性,所以就有了OC。

runtime是由C和汇编编写的,目前有两个版本 modern 和 legacy 现在使用的Object-C 2.0是modern版本的runtime系统,只能运行在iOS和OS X10.5之后的64位程序。而OS X较老的32位程序仍然采用Object-c 1 中legacy版本的runtime系统。

总结来说 OC是动态类型语言许多事情在编译时无法确定,需要等在运行时。而造成这种现象的因为runtime机制的存在。

学习runtime先从老生常谈的objc_msgSend函数谈起

objc_msgSend函数

新建一个Person类有实例方法和类方法

  • -(void)instanceMethod;
  • +(void)classMethod;
@interface Person : NSObject
-(void)instanceMethod;
+(void)classMethod;
@end
-(void)instanceMethod{
    NSLog(@"instanceMethod is print");

}
+(void)classMethod{
    NSLog(@"classMethod is print");
    
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *per = [[Person alloc] init];
        //很简单发送'调用'两个方法 分别是类方法和实例方法
        [Person classMethod];
        [per instanceMethod];
        
    }
    return 0;
}
输出:
2017-04-29 11:13:21.493311+0800 runtime[5163:1579843] classMethod is print
2017-04-29 11:13:21.493433+0800 runtime[5163:1579843] instanceMethod is print

在main.m目录下 使用clang -rewrite-objc main.m编译mian.m文件生成main.cpp
摘抄一些重要的代码


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));

        ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("classMethod"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)per, sel_registerName("instanceMethod"));

    }
    return 0;
}

我们可以看到一共出现四次objc_msgSend函数alloc 和init时候以及'调用'我们自己设置的方法

除去外衣我们只看函数的参数

objc_msgSend(objc_getClass("Person"), sel_registerName("classMethod"));  

objc_msgSend(per, sel_registerName("instanceMethod"));

在源码里面查看

 id objc_msgSend(id self, SEL op, ...)

函数声明长这样子
第一个参数类型为id,大家对它都不陌生,它是一个指向类实例的指针
长这样:

typedef struct objc_object *id;

学过C语言很快就能看出id是一个指向objc_object结构体的指针。
那我们就查看下结构体里有些什么呢?

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

只有一个Class类型的指针

那Class又是个什么鬼?

typedef struct objc_class *Class;

原来是个指向objc_class结构体的指针啊
既然是个结构体那我们看下结构体有些什么

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         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;

感觉东西好多而且加上这么多的概念有些乱啊

我们就用简单的图来描述思路


是不是思路清晰了许多?
我们着重来看下methodLists
老规矩看看是个什么东西

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}  

结构体methodLists是指向objc_method_list指针的指针。
其中的struct objc_method method_list[1]
看看官方的描述为:长度可变的结构体数组
也就是说可以动态修改*methodLists的值来添加成员方法.

简单的理解就是methodLists是方法的数组
里面存着类或者实例的方法

来看看objc_method

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

是不是非常眼熟?SEL objc_msgSend的第二个参数
来看看他的真面目

SEL

typedef struct objc_selector *SEL;

一个指向objc_selector结构体的指针
你可以理解为:SEL是一个仅仅包含方法名字的字符串

IMP


typedef id (*IMP)(id, SEL, ...); 

恩他是个函数指针,它会指向一个函数。
当发送消息时,会执行一段代码,哪里去找那段代码呢?由IMP来指定。

如果你思路清晰的话很快可以看出这货的参数和objc_msgSend的参数相同
确定一个IMP需要id和SEL。换句话说给我一个id,和一个SEL(方法名)就能确定一个方法的IMP(实现)。
到现在一堆的概念头都快炸了我们来完善一下思路结构图

部分结构图

顺着思路是不是觉得思路清晰了一些,对结构有了一些直观认识。
但是有好多的东西都没有介绍 ,慢慢来,一口吃不成胖子。

method_types表示的是方法的返回值和参数编码

返回值和参数编码

runtime.h中我们可以找到一些有关于的函数

BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types) 
IMP class_replaceMethod(Class cls, SEL name, IMP imp, 
                                    const char *types) 

添加方法和替换方法
下面写一个简单例子来体会它存在的意义,手写要添加一个方法需要几个条件:方法名(SEL)、方法的实现地址(IMP)、方法所属的类(Class)。
幸运的是runtime API为我们提供了便捷的实现IMP的Block

 IMP imp_implementationWithBlock(id block)

那我们就现在手动创建一个类并未其添加一个方法test,并尝重写description方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建继承自NSObject类的People类
        Class People = objc_allocateClassPair([NSObject class], "People", 0);
        //将People类注册到runtime中
        objc_registerClassPair(People);
        //注册test: 方法选择器
        SEL sel = sel_registerName("test:");
        //函数实现
        IMP imp = imp_implementationWithBlock(^(id this,id args){
            NSLog(@"方法的调用者为 %@",this);
            NSLog(@"参数为 %@",args);
            return @"返回值测试";
        });
        
        //向People类中添加 test:方法;函数签名为@@:@,
        //    第一个@表示返回值类型为id,
        //    第二个@表示的是函数的调用者类型,
        //    第三个:表示 SEL
        //    第四个@表示需要一个id类型的参数
        class_addMethod(People, sel, imp, "@@:@");
        //替换People从NSObject类中继承而来的description方法
        class_replaceMethod(People,@selector(description), imp_implementationWithBlock(^NSString*(id this,...){
            return @"我是Person类的对象";}),
                            "@@:");
        
        //完成 [[People alloc]init];
        id p1 = objc_msgSend(objc_msgSend(People, @selector(alloc)),@selector(init));
        //调用p1的sel选择器的方法,并传递@"???"作为参数
        id result = objc_msgSend(p1, sel,@"???");
        //输出sel方法的返回值
        NSLog(@"sel 方法的返回值为 : %@",result);
        
        //获取People类中实现的方法列表
        NSLog(@"输出People类中实现的方法列表");
        unsigned int methodCount;
        Method * methods = class_copyMethodList(People, &methodCount);
        for (int i = 0; i<methodCount; i++) {
            NSLog(@"方法名称:%s",sel_getName(method_getName(methods[i])));
            NSLog(@"方法Types:%s",method_getDescription(methods[i])->types);
        }
        free(methods);
   
    }
    return 0;
}

参考:
http://www.jianshu.com/p/c0157110caa5
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,726评论 0 9
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,137评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,569评论 33 466
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 736评论 0 2
  • 刚上 大学的时候,学校学生比较流流行开通QQ空间写写文章,我也跟着风开起来空间,班上有写文艺女生对文字有着很好...
    juliareal阅读 171评论 0 0