Runtime学习笔记记录

文章目录

  • 一 runtime原理
  • 二 Runtime相关的头文件
    • 2-1 iOS 中 Runtime相关的头文件
    • 2-2 操作对象的类型的定义
  • 三 函数的定义
  • 四 runtime的应用
    • 4-1 获取person类的所有变量
    • 4-2 获取person类的所有方法
    • 4-3 改变LLPerson对象的私有变量age的值
    • 4-4 为person的category类增加一个新属性
    • 4-5 为person类 添加一个新方法newMethod
    • 4-6 交换person类的两个方法实现

一 runtime原理

因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。

RunTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。

二 Runtime相关的头文件

2-1 iOS 中 Runtime相关的头文件
image.png

这些都是和运行时相关的头文件 其中主要使用的函数定义在Message.h 和 runtime.h这两个头文件中,在Message.h 中主要包含了一些向对象发送消息的函数 这是OC对象方法调用的底层实现

使用时 导入头文件

#import <objc/message.h>
#import <objc/runtime.h>

runtime.h是运行时最重要的文件,其中包含了对运行时进行操作的方法。 主要包括:

2-2 操作对象的类型的定义
/// 一个类型,代表着类定义中的一个方法
typedef struct objc_method *Method;

///代表实例(对象)的变量
typedef struct objc_ivar *Ivar;

/// 代表一个分类
typedef struct objc_category *Category;

/// 代表OC声明的属性
typedef struct objc_property *objc_property_t;

// Class代表一个类,它在objc.h中这样定义的  typedef struct objc_class *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;

这些类型的定义,对一个类进行了完全的分解,将类定义或者对象的每一个部分都抽象为一个类型type,对操作一个类属性和方法非常方便。OBJC2_UNAVAILABLE标记的属性是Ojective-C 2.0不支持的,但实际上可以用响应的函数获取这些属性,例如:如果想要获取Class的name属性,可以按如下方法获取:

    Class classPerson = LLPerson.class;        
    const char *name = class_getName(classPerson);    
    NSLog(@"%s",name);    

三 函数的定义

  • 对对象进行操作的方法一般以object_开头
  • 对类进行操作的方法一般以class_开头
  • 对类或对象的方法进行操作的方法一般以method_开头
  • 对成员变量进行操作的方法一般以ivar_开头
  • 对属性进行操作的方法一般以property_开头开头
  • 对协议进行操作的方法一般以protocol_开头
    根据以上的函数的前缀 可以大致了解到层级关系。

对于以objc_开头的方法,则是runtime最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。

例如:使用runtime对当前的应用中加载的类进行打印,别被吓一跳。 将近有5000+个

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{        
    unsigned int count = 0;    
    Class *classes = objc_copyClassList(&count);    
    NSLog(@"%ud",count);    
    for (int i = 0; i < count; i++) {
        const char  *name = class_getName(classes[i]);        
        NSLog(@"%s",name);
    }       

四 runtime的应用

4-1 获取person类的所有变量
person.h
@property(nonatomic,copy)NSString * name;
- (void)eat;
- (void)run;

person.m
@implementation LLPerson{
    NSInteger age;
}
- (instancetype)init{
    self = [super init];
    if (self) {        
        [self setupUI];
    }
    return self;
}
- (void)setupUI{
    self.name = @"奥卡姆剃须刀";    
    age = 25;    
}
- (void)eat{    
    NSLog(@"吃");    
}
- (void)run{    
    NSLog(@"跑");
}

// 在控制器中
// 过去LLperson所有的成员变量
- (void)getClassVariable{    
    unsigned int count = 0;    
    //  获取类的一个包含所有变量的列表 ivar 是一个runtime声明的一个宏是   实例变量的意思
    Ivar *ivars = class_copyIvarList([LLPerson class], &count);    
    for (int i = 0; i < count; i++) {
        
        // 遍历每一个变量 包括名称和类型
        Ivar ivar = ivars[i];
        
        const char *varialbeName = ivar_getName(ivar);
        
        const char *varialbeType = ivar_getTypeEncoding(ivar);
        
        NSLog(@"name--%s    type:%s",varialbeName,varialbeType);        
    }
}
//  输出结果
 LLRuntimeDemo[3337:110216] name--age    type:q
 LLRuntimeDemo[3337:110216] name--_name    type:@"NSString"

分析:Ivar 一个指向objc_ivar结构体指针 包含了变量名 变量类型等信息 可以看到 私有属性name 能够访问到了 在有些项目中 为了对某些私有属性进行隐藏,某些.h文件中没有出现相应的显式创建,而是如上面的person类中,在.m中进行私有创建,但是我们可以通过runtime这个有效的方法,访问到所有包括这些隐藏的私有变量。

拓展:
① class_copyIvarList能够获取一个含有类中所有成员变量的列表,列表中包括属性变量和实例变量。需要注意的是,现在在控制台name返回的是"_name",那么如果在person.m中加入:@synthesize name;
那么控制台第二行返回的是"(Name: name) ----- (Type:i) ;"
(因为@property是生成了"_name",而@synthesize是执行了"@synthesize name = _name;"
②如果单单需要获取属性列表的话,可以使用函数:class_copyPropertyList();只是返回的属性变量仅仅是“age”,做为实例变量的name是不被获取的。
而class_copyIvarList()函数则能够返回实例变量和属性变量的所有成员变量。

4-2 获取person类的所有方法

    unsigned int count;
    //获取方法列表,所有在.m文件显式实现的方法都会被找到,包括setter+getter方法;
    Method *allMethods = class_copyMethodList([LLPerson class], &count);
    for(int i =0;i<count;i++)
    {
        //Method,为runtime声明的一个宏,表示对一个方法的描述
        Method md = allMethods[i];
        //获取SEL:SEL类型,即获取方法选择器@selector()
        SEL sel = method_getName(md);
        //得到sel的方法名:以字符串格式获取sel的name,也即@selector()中的方法名称
        const char *methodname = sel_getName(sel); NSLog(@"(Method:%s)",methodname);
    
    }
// 控制台输出
LLRuntimeDemo[3561:121536] (Method:setupUI)
LLRuntimeDemo[3561:121536] (Method:eat)
LLRuntimeDemo[3561:121536] (Method:.cxx_destruct
LLRuntimeDemo[3561:121536] (Method:name)
LLRuntimeDemo[3561:121536] (Method:setName:)
LLRuntimeDemo[3561:121536] (Method:init)
LLRuntimeDemo[3561:121536] (Method:run)

控制台输出了包括setget等方法名称。
【备注:.cxx_destruct方法是关于系统自动内存释放工作的一个隐藏的函数,当ARC下,且本类拥有实例变量时,才会出现;】
分析:Method是一个指向objc_method结构体指针,表示对类中的某个方法的描述。在API中的定义:typedef struct objc_method Method;
而objc_method结构体如下:

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

  • method_name :方法选择器@selector(),类型为SEL。 相同名字的方法下,即使在不同类中定义,它们的方法选择器也相同。
  • method_types:方法类型,是个char指针,存储着方法的参数类型和返回值类型。
  • method_imp:指向方法的具体实现的指针,数据类型为IMP,本质上是一个函数指针。 在第五个按钮行为“增加一个方法”部分会提到。

SEL:数据类型,表示方法选择器,可以理解为对方法的一种包装。在每个方法都有一个与之对应的SEL类型的数据,根据一个SEL数据“@selector(方法名)”就可以找到对应的方法地址,进而调用方法。
因此可以通过:获取 **Method结构体->得到SEL选择器名称->得到对应的方法名 **,这样的方式,便于认识OC中关于方法的定义。

4-3 改变LLPerson对象的私有变量age的值

注意:这利用运行时改变的私有边量必须是id类型的 不能是常量 因为在object_setIvar方法中赋值的时候 只能赋值id类型的

    NSLog(@"改变前的值 %@",self.person);        
    unsigned int count = 0;    
    Ivar *allList = class_copyIvarList([self.person class], &count);    
    Ivar ivar = allList[0];    
    object_setIvar(self.person, ivar, @(18));    
    NSLog(@"改变后的值 %@",self.person);

// 控制台输出
LLRuntimeDemo[3854:135280] 改变前的值 奥卡姆剃须刀---25
LLRuntimeDemo[3854:135280] 改变后的值 奥卡姆剃须刀---18
4-4 为person的category类增加一个新属性

我们都知道在分类中是不可以给本类添加新的属性的
但是我们可以利用Runtime来使对象关联 为LLPerson类进行分类的新属性创建:

创建LLPerson的分类
#import "LLPerson.h"
@interface LLPerson (Category)
@property (nonatomic,assign) float height;


#import "LLPerson+Category.h"
#import <objc/runtime.h>


//做为key,字符常量 必须是C语言字符串;
const char * key = "heightKey";

@implementation LLPerson (Category)

//  设置值  要对应一个key
- (void)setHeight:(float)height{
    
    NSNumber *num = [NSNumber numberWithFloat:height];
    
    /*
     第一个参数是需要添加属性的对象;
     第二个参数是属性的key;
     第三个参数是属性的值,类型必须为id,所以此处height先转为NSNumber类型;
     第四个参数是使用策略,是一个枚举值,类似@property属性创建时设置的关键字,可从命名看出各枚举的意义;
     objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
     */
    objc_setAssociatedObject(self, key, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
}

//提取属性的值:
-(float)height{
    NSNumber *number = objc_getAssociatedObject(self, key);
    return [number floatValue];
}


//在ViewController中
- (void)addVariable{    
    self.person.height = 180;    
    NSLog(@"%lf",self.person.height);
}

// 再次调用获取类的方法和属性   以下是打印结果
LLRuntimeDemo[4588:172392] (Method:setupUI)
LLRuntimeDemo[4588:172392] (Method:eat)
LLRuntimeDemo[4588:172392] (Method:.cxx_destruct)
LLRuntimeDemo[4588:172392] (Method:description)
LLRuntimeDemo[4588:172392] (Method:name)
LLRuntimeDemo[4588:172392] (Method:setName:)
LLRuntimeDemo[4588:172392] (Method:init)
LLRuntimeDemo[4588:172392] (Method:run)
LLRuntimeDemo[4588:172392] (Method:height)
LLRuntimeDemo[4588:172392] (Method:setHeight:)
LLRuntimeDemo[4588:172392] name--age    type:@"NSString"
LLRuntimeDemo[4588:172392] name--name    type:@"NSString"

分析: 可以看到分类的新属性可以在per对象中对新属性height进行访问赋值。
获取到person类属性时,依然没有height的存在,但是却有height和setHeight这两个方法;因为在分类中,即使使用@property定义了,也只是生成set+get方法,而不会生成_变量名 分类是不允许定义变量的
使用runtime中 ** objc_setAssociatedObject()** 和 ** objc_getAssociatedObject**方法 本质上只是为对象per添加了对height的属性关联 但是达到了新属性的作用

4-5 为person类 添加一个新方法newMethod
//  这种方法等价于对Father类添加Category对方法进行扩展):
- (void)addMethod{

/*  动态添加方法
   第一个参数表示 Class cls类型 
   第二个参数 表示待调用的方法名称
   第三个参数 (IMP)myAddingFunction IMP是一个函数指针  这里表示具体实现方法myAddingFunction
   第四个参数表示方法的参数 0 代表没有参数
*/

    class_addMethod([self.person class], @selector(newMethod), (IMP)myAddingFunction, 0);    
    [self.person performSelector:@selector(newMethod)];

}

int myAddingFunction(id self,SEL _cmd){
    
    NSLog(@"已新增方法 newMethod");
    
    return 1;
}
4-6 交换person类的两个方法实现

- (void)changeMethod{
    Method method1 = class_getInstanceMethod([self.person class], @selector(eat));    
    Method method2 = class_getInstanceMethod([self.person class], @selector(run));
    method_exchangeImplementations(method1, method2);    
    [self.person eat];
}

交换方法的使用场景 项目中的某个功能 在项目中需要多次被引用 当项目的需求发生改变是 要使用另一种功能代替这个功能 切要求不改变旧的项目 (也就是不改变原来的方法实现的前提下) 那么我们可以在分类中在写一个(符合新的需求的)方法 然后交换两个方法的实现 这样在不改变项目的代码而只是增加了新的代码的情况下 就完成了项目的改进 很好的体现了该项目的封装性和利用率
注:交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次。

之前写的文章没传demo
现金demo已传LLRuntime

参考多篇runtime资料:

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

推荐阅读更多精彩内容

  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 608评论 0 2
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,697评论 7 64
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,128评论 0 9
  • 在日本最好的零售店铺:唐吉诃德,唐企鹅坐在弯弯的月亮上,月亮代表365天24小时营业,深夜蓝相匹配的小动物,最终想...
    AbbyLiLi阅读 565评论 0 0