Runtime
什么是Runtime
runtime
是由C
和C++
、汇编实现的一套API
,为OC语言
加入了面向对象、运行时的功能运行时(
runtime
)将数据类型的确定由编译时推迟到了运行时平时编写的
OC
代码,在程序运行过程中,最终会转换成runtime
的C语言代码——runtime
是Objective-C
的幕后作者
如类结构中的ro
和rw
属性
ro
(read-only
)在编译时已经确定rw
(read-write
)在运行时才确定,因此可以使用runtime
进行修改
方法的本质是什么
方法的本质是发送消息objc_msgSend
,即寻找IMP
的过程
发送消息会有以下⼏个流程:
快速查找流程 —— 通过汇编
objc_msgSend
查找缓存cache_t
是否有imp
实现慢速查找流程 —— 通过
C++
中lookUpImpOrForward
递归查找当前类和父类的rw
中methodlist
的方法动态方法解析 —— 通过调用
resolveInstanceMethod
和resolveClassMethod
来动态方法决议——实现消息动态处理快速转发流程 —— 通过
CoreFoundation
来触发消息转发流程,forwardingTargetForSelector
实现快速转发,由其他对象来实现处理方法慢速转发流程 —— 先调用
methodSignatureForSelector
获取到方法的签名,生成对应的invocation
;再通过forwardInvocation
来进行处理以上流程均无法挽救就崩溃并报错
SEL和IMP的关系
SEL
是方法编号,也是方法名,在dyld加载镜像
到内存时,通过_read_image方法
加载到内存的表中了
IMP
是函数实现指针,找IMP
就是找函数实现的过程
SEL
和IMP
的关系就可以解释为:
SEL
就相当于书本的⽬录标题IMP
就是书本的⻚码函数
就是具体页码对应的内容
能否向运行时创建的类中添加实例变量
具体情况具体分析:
- 编译好的类不能添加实例变量
- 运行时创建的类可以添加实例变量,但若已注册到内存中就不行了
原因:
- 编译好的实例变量存储的位置在ro,而ro是在编译时就已经确定了的
- 编译完成,内存结构就完全确定就法修改
- 只能修改rw中的方法或者可以通过关联对象的方式来添加属性
Category:从底层原理研究到面试题分析
利用runtime-API创建对象
API介绍
动态创建类
1 | /** |
添加成员变量
1 | /** |
注册到内存
1 | /** |
添加属性变量
1 | /** |
添加方法
1 | /** |
整体使用
1 | // hobby的setter-IMP |
注意事项
- 记得导入
<objc/runtime.h>
- 添加成员变量
class_addIvar
必须在objc_registerClassPair
前,因为注册到内存时ro
已经确定了,不能再往ivars
添加(同第四个面试题) - 添加属性变量
class_addProperty
可以在注册内存前后,因为是往rw
中添加的 class_addProperty
中“属性的属性”——nonatomic/copy
是根据属性的类型变化而变化的class_addProperty
不会自动生成setter
和getter
方法,因此直接调用KVC
会崩溃
不只可以通过KVC
打印来检验,也可以下断点查看ro、rw
的结构来检验
关联对象分析
实则是为了解决分类创建属性的问题
分类直接添加属性的后果
- 编译会出现警告:没有setter方法和getter方法
- 运行会报错:-[FXPerson setName:]: unrecognized selector sent to instance 0x100f61180’
为什么不能直接添加属性
Category
在runtime
中是用一个结构体表示的:
1 | struct category_t { |
里面虽然可以添加属性变量,但是这些properties
并不会自动生成Ivar
,也就是不会有 @synthesize
的作用,dyld
加载期间,这些分类会被加载并patch
到相应的类中。这是一个动态过程,Ivar
不能动态添加.
解决方案
手动实现setter、getter方法,关联对象
1 |
|
关联对象原理
setter方法——objc_setAssociatedObject
分析
苹果设计接口时往往会加个中间层——即使底层实现逻辑发生变化也不会影响到对外接口
1 | void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { |
跟进去看看_object_set_associative_reference
实现
1 | void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { |
ObjcAssociation old_association(0, nil)
处理传进来的值得到new_value
获取到管理所有关联对象的hashmap总表的管理者
AssociationsManager
,然后拿到hashmap
总表AssociationsHashMap
DISGUISE(object)
对关联对象的地址进行取反操作得到哈希表对应的下标index
如果
new_value
为空(即对属性赋值为nil
)就直接找到相应的表进行删除如果
new_value
不为空,就拿到总表的迭代器通过拿到的下标index
进行遍历查找;如果找到管理对象的关联属性哈希map
表,然后再通过key
去遍历取值- 如果取到了,就先把新值设置到
key
上,再将旧值释放掉 - 如果没取到,就直接将新值设置在
key
上
- 如果取到了,就先把新值设置到
getter方法——objc_getAssociatedObject
分析
1 | id objc_getAssociatedObject(id object, const void *key) { |
1 | id _object_get_associative_reference(id object, void *key) { |
weak置空原理
在weak一行打下断点运行项目
1 | int main(int argc, const char * argv[]) { |
Xcode
菜单栏Debug->Debug Workflow->Always show Disassembly
打上勾查看汇编——汇编代码会来到libobjc
库的objc_initWeak
weak创建过程
objc_initWeak
location
:表示__weak
指针的地址(我们研究的就是__weak
指针指向的内容怎么置为nil
)newObj
:所引用的对象,即例子中的person
1 | id objc_initWeak(id *location, id newObj) |
method swizzing坑点
Runloop
-
- 每条线程都有唯一的一个与之对应的RunLoop对象,其关系是保存在一个全局的 Dictionary 里;主线程的RunLoop已经自动创建,子线程的RunLoop需要主动创建;RunLoop在第一次获取时创建,在线程结束时销毁
-
- 指定事件在运行循环中的优先级的,线程的运行需要不同的模式,去响应各种不同的事件,去处理不同情境模式。(比如可以优化tableview的时候可以设置UITrackingRunLoopMode下不进行一些操作,比如设置图片等。)
以
+scheduledTimerWithTimeInterval:
的方式触发的timer
,在滑动页面上的列表时,timer
会暂停回调, 为什么?- 滑动
scrollView
时,主线程的RunLoop
会切换到UITrackingRunLoopMode
这个Mode
,执行的也是UITrackingRunLoopMode
下的任务(Mode
中的item
),而timer
是添加在NSDefaultRunLoopMode
下的,所以timer
任务并不会执行,只有当UITrackingRunLoopMode
的任务执行完毕,runloop
切换到NSDefaultRunLoopMode
后,才会继续执行timer
。
- 滑动
-
将
Timer
放到NSRunLoopCommonModes
中执行即可[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
-
- 注意timer添加到runloop时应该设置为什么mode
- 注意timer在不需要时,一定要调用invalidate方法使定时器失效,否则得不到释放
-
- 常驻内存、
AutoreleasePool
自动释放池
- 常驻内存、
AutoreleasePool
和RunLoop
有什么联系?iOS应用启动后会注册两个 Observer 管理和维护 AutoreleasePool。应用程序刚刚启动时默认注册了很多个Observer,其中有两个Observer的 callout 都是 _ wrapRunLoopWithAutoreleasePoolHandler,这两个是和自动释放池相关的两个监听。
第一个 Observer 会监听 RunLoop 的进入,它会回调objc_autoreleasePoolPush() 向当前的 AutoreleasePoolPage 增加一个哨兵对象标志创建自动释放池。这个 Observer 的 order 是 -2147483647 优先级最高,确保发生在所有回调操作之前。
第二个 Observer 会监听 RunLoop 的进入休眠和即将退出 RunLoop 两种状态,在即将进入休眠时会调用 objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 根据情况从最新加入的对象一直往前清理直到遇到哨兵对象。而在即将退出 RunLoop 时会调用objc_autoreleasePoolPop() 释放自动自动释放池内对象。这个Observer 的 order 是 2147483647 ,优先级最低,确保发生在所有回调操作之后。
NSRunLoop
和CFRunLoopRef
区别CFRunLoopRef
基于C
线程安全,NSRunLoop
基于CFRunLoopRef
面向对象的API
是不安全的
Block
多线程
内存管理
计算机系统题
数据结构&算法题
- 链表和数组的区别是什么?插入和查询的时间复杂度分别是多少?
- 哈希表是如何实现的?如何解决地址冲突?
- 排序题:冒泡排序,选择排序,插入排序,快速排序(二路,三路)能写出那些?
- 链表题:如何检测链表中是否有环?如何删除链表中等于某个值的所有节点?
- 数组题:如何在有序数组中找出和等于给定值的两个元素?如何合并两个有序的数组之后保持有序?
- 二叉树题:如何反转二叉树?如何验证两个二叉树是完全相等的?
性能优化
设计模式
核心动画
- iOS核心动画高级技巧 - 1
- iOS核心动画高级技巧 - 2
- iOS核心动画高级技巧 - 3
- iOS核心动画高级技巧 - 4
- iOS核心动画高级技巧 - 5
- iOS核心动画高级技巧 - 6
- iOS核心动画高级技巧 - 7
- iOS核心动画高级技巧 - 8
- 用UIKit和UIView在视图上执行iOS动画
SWIFT
Flutter
iOS 面试题
- 2018 iOS面试题—–UI相关:事件传递,图像显示,性能优化,离屏渲染
- 2018 iOS面试题—–Objective_C语言特性相关问题
- 2018 iOS面试题—–runtime相关
- 2018 iOS面试题—–算法相关
- 2019 iOS面试题—–内存管理、自动释放池与循环引用
- 2019 iOS面试题—–Block原理、Block变量截获、Block的三种形式
- 2019 iOS面试题—–进程、线程、多进程、多线程、任务、队列、NSThread、GCD、NSOprationQueue…
- 2019 iOS面试题—–多线程相关之GCD、死锁、dispatch_barrier_async、dispatch_group_async、Dispatch Semaphore
- 2019 iOS面试题—–多线程相关之NSOperation、NSOperationQueue、NSThread+runloop实现常驻线程、加锁
- 2019 iOS面试题—–RunLoop数据结构、RunLoop的实现机制、RunLoop的Mode、RunLoop与NSTimer和线程
- 2019 iOS面试题—–网络相关之HTTP协议
- 2019 iOS面试题—–网络相关之HTTPS、对称加密、非对称加密
- 2019 iOS面试题—–一个基于UDP的简单的聊天Demo(用C语言、python、GCDAsyncUdpSocket来实现UDP通信)
- 2019 iOS面试题—–网络相关之UDP的特点、UDP的报文结构及差错检测
- 2019 iOS面试题—–网络相关之TCP、三次握手、四次挥手
- 2019 iOS面试题—–网络相关之TCP进阶:可靠数据传输、流量控制(滑动窗口)、拥塞控制
- 2019 iOS面试题—–网络相关之DNS
- 2019 iOS面试题—–网络相关之Cookie和Session
- 2019 iOS面试题—–网络相关之IP协议、IP数据报分片、IPv4编址、网络地址转换(NAT)
- 2019 iOS面试题—–网络相关之IPv6、从IPv4到IPv6的迁移
算法
- 七种常见的数组排序算法整理(C语言版本)
- 2019 算法面试相关(leetcode)–数组和链表
- 2019 算法面试相关(leetcode)–字符串
- 2019 算法面试相关(leetcode)–栈和队列
- 2019 算法面试相关(leetcode)–优先队列
- 2019 算法面试相关(leetcode)–哈希表
- 2019 算法面试相关(leetcode)–树、二叉树、二叉搜索树
- 2019 算法面试相关(leetcode)–递归与分治
- 2019 算法面试相关(leetcode)–贪心算法
- 2019 算法面试相关(leetcode)–动态规划(Dynamic Programming)
- 2019 算法面试相关(leetcode)–动态规划之背包问题