iOS开发-8.Runtime

  • 1.Objective-C中的Runtime
a) Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
    1) C、C++都是 编写代码-->编译连接-->运行
    
    2) 而OC则可以在运行的时候动态的去修改,例如动态的去调用自身类或者其他类的方法,或者增加、交换方法的实现

b) Objective-C的动态性是由Runtime API来支撑的

c) Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
  • 2.实现用一个字节来存储3个BOLL变量
a) &(按位与)如果大家都是1结果才是1,其他都是0,用来取出或设置特定的位

b) |(按位或)只要有一个1结果就是1,其他都是,用来取出或设置特定的位

c) ~(位运算取反)
#import <Foundation/Foundation.h>
@interface MJPerson : NSObject
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end

#import "MJPerson.h"
// &可以用来取出特定的位
// 0000 0111
//&0000 0100
//------
// 0000 0100

// 掩码,一般用来按位与(&)运算的
// 宏的替换就是字符串替换所以要注意运算先后次序
//#define MJTallMask 1
//#define MJRichMask 2
//#define MJHandsomeMask 4

//#define MJTallMask 0b00000001
//#define MJRichMask 0b00000010
//#define MJHandsomeMask 0b00000100

#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)

@interface MJPerson()
{
    char _tallRichHansome;
}
@end

@implementation MJPerson

// 0010 1010
//&1111 1101
//----------
// 0010 1000

- (instancetype)init
{
    if (self = [super init]) {
        _tallRichHansome = 0b00000100;
    }
    return self;
}

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHansome |= MJTallMask;
    } else {
        _tallRichHansome &= ~MJTallMask;
    }
}

- (BOOL)isTall
{
    // 只占1位 返回的也是1位 然而boll类型是一个字节所以会强制变成8位(全部用你当前的结果覆盖其余的位)那么解决办法 !!(2个感叹号) 或者 定义结构体的时候占2位
    // 结果用BOLL强制转 或者 !!(2个感叹号)
    return !!(_tallRichHansome & MJTallMask);
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHansome |= MJRichMask;
    } else {
        _tallRichHansome &= ~MJRichMask;
    }
}

- (BOOL)isRich
{
    return !!(_tallRichHansome & MJRichMask);
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHansome |= MJHandsomeMask;
    } else {
        _tallRichHansome &= ~MJHandsomeMask;
    }
}

- (BOOL)isHandsome
{
    return !!(_tallRichHansome & MJHandsomeMask);
}
@end
d) 用结构体的方式实现
e) 结构体是支持位域 字节排布 从低到高 从右到左
f) 位运算的效率比直接取值效率高
#import "MJPerson.h"
//#define MJTallMask (1<<0)
//#define MJRichMask (1<<1)
//#define MJHandsomeMask (1<<2)
@interface MJPerson()
{
    // 位域
    // 结构体是支持位域 字节排布 从低到高 从右到左
    struct {
        char tall : 1;
        char rich : 1;
        char handsome : 1;
    } _tallRichHandsome;
}
@end

@implementation MJPerson

- (void)setTall:(BOOL)tall
{
    _tallRichHandsome.tall = tall;
}

- (BOOL)isTall
{
    return !!_tallRichHandsome.tall;
}

- (void)setRich:(BOOL)rich
{
    _tallRichHandsome.rich = rich;
}

- (BOOL)isRich
{
    return !!_tallRichHandsome.rich;
}

- (void)setHandsome:(BOOL)handsome
{
    _tallRichHandsome.handsome = handsome;
}

- (BOOL)isHandsome
{
    return !!_tallRichHandsome.handsome;
}
@end
g) 用共用体的方式实现
    1) 共用体:大家共用一块内存
    2) 结构体:结构体内的成员都是单独存在的,占不同的内存
#import "MJPerson.h"
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)
@interface MJPerson()
{
    union {
        int bits;
        
        struct {
            char tall : 4;
            char rich : 4;
            char handsome : 4;
            char thin : 4;
        };
    } _tallRichHandsome;
}
@end

@implementation MJPerson

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= MJTallMask;
    } else {
        _tallRichHandsome.bits &= ~MJTallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & MJTallMask);
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome.bits |= MJRichMask;
    } else {
        _tallRichHandsome.bits &= ~MJRichMask;
    }
}

- (BOOL)isRich
{
    return !!(_tallRichHandsome.bits & MJRichMask);
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome.bits |= MJHandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~MJHandsomeMask;
    }
}

- (BOOL)isHandsome
{
    return !!(_tallRichHandsome.bits & MJHandsomeMask);
}

- (void)setThin:(BOOL)thin
{
    if (thin) {
        _tallRichHandsome.bits |= MJThinMask;
    } else {
        _tallRichHandsome.bits &= ~MJThinMask;
    }
}

- (BOOL)isThin
{
    return !!(_tallRichHandsome.bits & MJThinMask);
}
@end
h) 例如kvo或者autoresizingMask是怎么知道传入的不同按位或的值呢?
    1) 最终传入的值与自身进行按位与如果成立则包含了自身;
  • 3.isa详解
a) 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针

b) 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址

c) 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

d) 64位之前isa就是Class类型,是isa_t类型
image
e) isa详解-位域
image
  • 4.Class的结构
a) 类对象/元类对象的地址值最后三位都是0,因为isa底层原理

b) 一开始是只有class_ro_t,然后创建class_rw_t并且拷贝class_ro_t的内容,并且将bits指向class_rw_t
image
c) class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容 
image
d) class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
在这里插入图片描述
e) method_t是对方法/函数的封装
image
f) Type Encoding
在这里插入图片描述
g) cache(方法缓存)
    1) Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
    2) 方法缓存cache调用过的方法直接扔到缓存里面,下次再调用直接从cache调用
    3) 缓存源码实现查找
        3.1) objc-cache.mm
        3.2) bucket_t * cache_t::find(cache_key_t k, id receiver)
在这里插入图片描述
h) 散列表
    1) 存的时候通过一个位运算去存,前面没有的就会设置为NULL(牺牲内存空间来换取执行效率/空间换时间)
    2) 散列表扩容的时候是在原来容量基础上X2,并且清空原来缓存的数据,重新开始缓存
    3) 存或者取的时候有可能是一样的 那么做法是-1或者+1取,重新生成index
在这里插入图片描述
  • 5.objc_msgSend执行流程
a) OC中的方法调用,其实都是转换为objc_msgSend函数的调用

b) objc_msgSend的执行流程可以分为3大阶段
    1) 消息发送
    2) 动态方法解析
    3) 消息转发
    
c) 源码跟读
在这里插入图片描述
d) 消息发送
    1) 查找方式
        1.1) 二分查找(排好序的 从中间开始分然后查找)
        1.2) 线性查找(普通的for循环遍历查找)
在这里插入图片描述
e) 动态方法解析
    1) 开发者可以实现以下方法,来动态添加方法实现
        1.1) +resolveInstanceMethod:// 对象方法
        1.2)
+resolveClassMethod:// 类方法
    2) 动态解析过后,会重新走“消息发送”的流程
        2.1) “从receiverClass的cache中查找方法”这一步开始执行
在这里插入图片描述
f) 消息转发
    1) 这个步骤你就开始找不到源码了
    2) 你自己的类没有能力处理这个方法
    3) 元类对象:是一种特殊的类对象,类对象的类对象
    4) 类方法也是有消息转发机制的
    5) 开发者可以在forwardInvocation:方法中自定义任何逻辑
在这里插入图片描述
    6) 生成NSMethodSignature方式
在这里插入图片描述
  • 6.super的本质
a) @dynamic (告诉编译器不要自动生成setter&getter方法的实现、不要自动生成成员变量)

b) @synthesize (自动生成setter&getter方法的实现和自动生成成员变量)

c) super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
    1) struct objc_super2
在这里插入图片描述
        1.1) receiver是消息接收者
        1.2) current_class是receiver的Class对象-->superclass找父类对象
    2) SEL
  • 7.LLVM的中间代码(IR)
a) Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)
    1) OC --> 中间代码(.ll文件) --> 汇编、机器代码
    
b) 可以使用以下命令行指令生成中间代码
    1) clang -emit-llvm -S main.m
    
c) 语法简介
    1) @ - 全局变量
    2) % - 局部变量
    3) alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
    4) i32 - 32位4字节的整数
    5) align - 对齐
    6) load - 读出
    7) store 写入
    8) icmp - 两个整数值比较,返回布尔值
    9) br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
    10) label - 代码标签
    11) call - 调用函数

d) 官方文档:  
    1) https://llvm.org/docs/LangRef.html
  • 8.Runtime的应用
a) 查看私有成员变量
    1) 设置UITextField占位文字的颜色
在这里插入图片描述
b) 字典转模型
    1) 利用Runtime遍历所有的属性或者成员变量
    2) 利用KVC设值
在这里插入图片描述
c) 替换方法实现
    1) class_replaceMethod
    2) method_exchangeImplementations
在这里插入图片描述
在这里插入图片描述
  • 9.Runtime API
a) 类
    1) 动态创建一个类(参数:父类,类名,额外的内存空间)
        1.1) Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

    2) 注册一个类(要在类注册之前添加成员变量)
        2.1) void objc_registerClassPair(Class cls) 

    3) 销毁一个类
        3.1) void objc_disposeClassPair(Class cls)

    4) 获取isa指向的Class
        4.1) Class object_getClass(id obj)

    5) 设置isa指向的Class
        5.1) Class object_setClass(id obj, Class cls)

    6) 判断一个OC对象是否为Class
        6.1) BOOL object_isClass(id obj)

    7) 判断一个Class是否为元类
        7.1) BOOL class_isMetaClass(Class cls)

    8) 获取父类
        8.1) Class class_getSuperclass(Class cls)

b) 成员变量
    1) 获取一个实例变量信息
        1.1) Ivar class_getInstanceVariable(Class cls, const char *name)

    2) 拷贝实例变量列表(最后需要调用free释放)
        2.1) Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

    3) 设置和获取成员变量的值
        3.1) void object_setIvar(id obj, Ivar ivar, id value)
        3.2) id object_getIvar(id obj, Ivar ivar)

    4) 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
        4.1) BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

    5) 获取成员变量的相关信息
        5.1) const char *ivar_getName(Ivar v)
        5.2) const char *ivar_getTypeEncoding(Ivar v)

c) 属性
    1) 获取一个属性
        1.1) objc_property_t class_getProperty(Class cls, const char *name)

    2) 拷贝属性列表(最后需要调用free释放)
        2.1) objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

    3) 动态添加属性
        3.1) BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)

    4) 动态替换属性
        4.1) void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)

    5) 获取属性的一些信息
        5.1) const char *property_getName(objc_property_t property)
        5.2) const char *property_getAttributes(objc_property_t property)

d) 方法
    1) 获得一个实例方法、类方法
        1.1) Method class_getInstanceMethod(Class cls, SEL name)
        1.2) Method class_getClassMethod(Class cls, SEL name)

    2) 方法实现相关操作
        2.1) IMP class_getMethodImplementation(Class cls, SEL name) 
        2.2) IMP method_setImplementation(Method m, IMP imp)
        2.3) void method_exchangeImplementations(Method m1, Method m2) 

    3) 拷贝方法列表(最后需要调用free释放)
        3.1) Method *class_copyMethodList(Class cls, unsigned int *outCount)

    4) 动态添加方法
        4.1) BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

    5) 动态替换方法
        5.1) IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

    6) 获取方法的相关信息(带有copy的需要调用free去释放)
        6.1) SEL method_getName(Method m)
        6.2) IMP method_getImplementation(Method m)
        6.3) const char *method_getTypeEncoding(Method m)
        6.4) unsigned int method_getNumberOfArguments(Method m)
        6.5) char *method_copyReturnType(Method m)
        6.6) char *method_copyArgumentType(Method m, unsigned int index)

    7) 选择器相关
        7.1) const char *sel_getName(SEL sel)
        7.2) SEL sel_registerName(const char *str)

    8) 用block作为方法实现
        8.1) IMP imp_implementationWithBlock(id block)
        8.2) id imp_getBlock(IMP anImp)
        8.3) BOOL imp_removeBlock(IMP anImp)
  • 10.其它知识点总结
a) objc_msgSendSuper 和 objc_msgSendSuper2区别

b) 数组可以当成指针来用

c) 分类的方法尽量+属于自己的前缀区分,万一覆盖了呢

d) 方法替换:替换系统的方法 例如拦截整个项目中所有按钮的点击事件

e) hook:钩子函数 拦截系统的方法 塞入自己的实现

f) 类簇(cu):我们看到的类型不一定是他的真实类型 NSString、NSArray、NSDictionary,真实类型是其他类型

g) 转成底层代码的方式
    1) 转成cpp文件(作为参考)
    2) 程序运行起来查看汇编
    3) 转成汇编
在这里插入图片描述
  • 11.面试题1
    在这里插入图片描述
a) isKindOfClass // 传入的对象的类对象是否==判断的类对象或者他的子类
b) isMemberOfClass // 传入的对象的类对象是否==判断的类对象
c) super class的返回类型是谁取决于方法调用者是谁即:消息接收者 消息接收者仍然是子类对象,只不过从父类的方法开始寻找方法
c) 底层实现原理
在这里插入图片描述
  • 12.面试题2
    在这里插入图片描述
a) 函数调用堆栈的问题
    1) 局部变量分配在栈空间
    2) 栈空间分配,从高地址到低地址
b) 寄存器(存储在cpu里面,相当于一个元件,参数都是存储在这里面)比内存的效率更高
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 一、什么是Runtime Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同 O...
    迷心迷阅读 422评论 0 0
  • 面试题 讲一下 OC 的消息机制OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receive...
    e297b14c9e53阅读 319评论 0 0
  • Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同Objective-C的动态性...
    鼬殿阅读 821评论 0 1
  • 目录 前言 iOS编译流程 runtime介绍 消息发送流程 消息转发流程 Method Swizzling 参考...
    看影成痴阅读 534评论 0 5
  • 一、简介 Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同 Objective...
    阿凡提说AI阅读 535评论 1 11