美文网首页
Runloop 本质是什么?

Runloop 本质是什么?

作者: XLsn0w | 来源:发表于2021-08-12 09:18 被阅读0次

    Runloop 本质是什么?
    本质是一个OC对象,内部也有isa指针。
    Runloop 结构分析
    结构内部有2个重要的成员变量

    Runloop 运行模式
    Runloop 事件处理流程
    Runloop 牵涉概念
    自动释放池
    内存管理
    定时器
    线程包活
    卡顿检测
    OC对象本质

    KVC与kvo
    KVC
    什么是kvc,
    KVC,俗称“键值编码”,全称是“Key Value Coding”,它是一种可以直接通过字符串的名称(Key)来访问类属性的机制,而不是通过调用Setter或者Getter方法来进行访问。
    kvc 能干什么?

    setValue:forkey 给对象的属性赋值,但是层级只有一层。
    setValue:forkeyPath,支持一级属性赋值,也支持多级属性赋值
    利用kvc给对象的成员变量赋值。

    kvc 的实现原理(kvc的赋值,取值流程)
    赋值流程
    1 依次查找方法看看有没有实现。setKey:、_setKey,如果找到了方法,则传参并且调用方法。
    2,如果没有找到方法,则通过accessInstanceVariablesDirectly一个方法,查看是否能直接放问成员变量。能则给成员变量赋值,如果不能则抛出异常。
    取值流程
    1,依次查找几个方法是否实现。_key,_isKey,key
    ,isKey。如果实现就调用取值。
    2,如果没有实现,则通过一个方法,查看是否能直接访问成员变量。如果能则获取成员变量的值,如果不能则报错。
    KVC 相关问题
    KVC 是否线程安全问题
    可能会引出:kvc是否线程安全问题,以及自己设计kvo(主要是模型思路和上面几个点的判断和处理)
    5.线程安全 (自旋锁,递归锁,互斥锁,信号量,读写锁,栅栏函数) 底层都是pthread_mutex的封装
    线程安全
    内存管理
    内存管理的三种方案,
    iOS中主要通过引用计数来管理内存,当引用计数为0的时候销毁内存。
    苹果一共提供了3中方案,(TaggetPointer、NONPOINTER_ISA、散列表),
    TaggetPointer

    Tagged Pointer是专⻔⽤来存储⼩的对象,例如NSNumber,NSDat
    Tagged Pointer指针的值不再是地址了,⽽是真正的值。所以,实际上它不再是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。所以,它的内存并不存储 在堆中,也不需要创建和释放

    NONPOINTER_ISA
    一切对象均为objc_object对象,objc_object对象内部有一个isa属性。这个isa指针之前只是一个纯指针。现在也报含指针外的其他信息,例如对象的引用计数、是否被弱引用...这时这个isa就是NONPOINTER_ISA。
    isa是isa_t类型的联合体,其内部通过位域技术储存很多了对象的信息。
    NONPOINTER_ISA 中的引用计数存储位置
    /表示该对象的引用计数值,满了就会存在sidetable 中/
    uintptr_t extra_rc : 19;
    复制代码
    源码中的extra_rc就是用来存储引用计数的,具体原理放到下面的引用计数部分说明。
    散列表--引用计数&弱引用计数
    散列表的结构
    系统维护了一张全局的Hash表,里面存了一张张SideTable散列表,而这个散列表中就储存了对象的引用计数以及弱引用情况。
    struct SideTable {
    spinlock_t slock;//锁,用于控制数据访问安全
    RefcountMap refcnts;//引用计数表s
    weak_table_t weak_table;//弱引用计数表s
    ...
    ...
    };

    复制代码

    spinlock_t slock 锁,用于控制这张散列表SideTble的数据访问安全。

    RefcountMap refcnts:引用计数表RefcountMap,用于储存对象的引用计数情况,(在isa_t中extra_rc位引用计数满了后会把一半的引用计数放到某个散列表SideTable中的引用计数表中)

    weak_table_t weak_table:弱引用情况表,用于储存对象的弱引用情况。

    weak_table_t 弱引用计数表
    1,weak_table_t 是个二维数组,里面包含了一个个weak_table,weak_table里面是一个个weak_entry数组
    2, 当一个对象的属性被设置成weak时,weak_table表中会查找当内部有没有该对象的弱引用数组(weak_entry数组),如果有就直接插入这个属性到这个weak_entry数组,没有就先创建weak_entry数组再插入
    3,、当对象被释放时delloc,会通过对象指针去查找weak_table没有该对象的weak_entry数组,有的话遍历weak_entry数组,将内部的属性置为nil;最后将这个weak_entry数组remov

    为什么是 weak_entry数组 (因为一个对象可能拥有多个弱应用属性)

    散列表
    检测内存管理的方式
    1,xcode 自带一个静态内存分析和Instrument
    2,工具,MLeaksFinder:精准 iOS 内存泄露检测工具
    内存泄漏主要有两种方式

    Laek Memory 这种是忘记 Release 操作所泄露的内存。
    Abandon Memory 这种是循环引用,无法释放掉的内存。

    MLeaksFinder 实现的原理
    1,不入侵开发代码
    这里使用了 AOP 技术,hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法,关于如何 hook,请参考 Method Swizzling。
    2, 实现原理

    为基类 NSObject 添加一个方法 -willDealloc 方法
    该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言.

    如果已经释放那么空指针不会调用断言函数,如果没有释放旧会断点。
    空指针是指向nil(调用方法无反应),野指针是指向垃圾内存(危险)
    内存泄漏
    内存泄漏的解决方案
    定时器,block 代理的循环引用。
    1,用weak修饰,
    2,nstimer 可以考虑添加中间层
    ARC和MRC如何管理内存
    arc 依靠llvm编译器和Runtime 实现
    llvm 干了什么

    加上,release,retain,autoRlease 操作

    当我们编译源码的时候,编译器会分析源码中每个对象的生命周期,然后基于这些对象的生命周期,来添加相应的引用计数操作代码。所以,ARC 是工作在编译期的一种技术方案,这样的好处是:
    编译之后,ARC 与非 ARC 代码是没有什么差别的,所以二者可以在源码中共存。实际上,你可以通过编译参数 -fno-objc-arc 来关闭部分源代码的 ARC 特性。
    runtime 干了什么
    运行时,清空弱引用的对象。
    用一张哈希表,key 为弱引用的对象地址。values为数组,里面存放指针。
    当弱引用的对象被释放时,会将数组里面的指针全部置为nil。
    自动释放池
    @autoreleasepool{}关键字通过编译器转换成objc_autoreleasePoolPush和objc_autoreleasePoolPop这一对方法。 将自动释放池中的对象加到自动释放池中。

    由objc_autoreleasePoolPush作为自动释放池作用域的第一个函数。
    使用objc_autorelease将对象加入自动释放池。
    由objc_autoreleasePoolPop作为自动释放池作用域的最后一个函数。

    自动释放池的结构
    自动释放池都是由一个或者多个AutoreleasePoolPage组成,page的 SIZE 为 4096 bytes ,它们通过parent和child指针组成一个双向链表。

    hotPage:是当前正在使用的page,操作都是在hotPage上完成,一般处于链表末端或者倒数第二个位置。存储在 TLS 中,可以理解为一个每个线程共享一个自动释放池链表。
    coldPage:位于链表头部的page,可能同时为hotPage。

    release 和autoRelease 方法
    release 会立即释放,见与set方法中先retain后release
    autoRelease 则会等到自动释放池结束的时候释放,见与类方法创建对象的时候。
    [NSMutableArarry arry]
    autorelease pool的释放时机

    MRC下调用自动释放池release方法后,会对在autorelease对象进行释放,因此,此后访问的person变量为野指针,再去访问自然会导致crash。

    而ARC下,@autoreleasepool并不会立即在结束括号符后,立即释放person变量,而是会在一个合适的时间点。

    合适的时间点。因此,当runloop进入kCFRunLoopEntry时,自动释放池会进行push操作,当runloop进入kCFRunLoopBeforeWaiting | kCFRunLoopExit状态时,自动释放池会进行pop操作。

    自动释放池的应用
    autorelease pool和RunLoop(运行循环)

    主线程中,系统已经在main.m中通过@autoreleasepool创建了自动释放池,所以我们无需额外去创建和释放了.

    子线程中自动释放池的创建和释放都无需我们进行额外的操作。当然,在某些场景下,也可以手动通过@autoreleasepool进行创建和释放。

    autorelease pool和降低内存峰值
    当被加到自动释放池的对象越来越来多,却没有得到及时释放,就会导致内存溢出。这个时候,我们可以手动添加自动释放池来解决这个问题。
    block
    block 是什么?
    block 是带有自动变量的匿名函数。
    block 的本质
    blcok的本质是OC对象,其结构体内部也带有isa指针。
    block的变量捕获机制
    局部变量(捕获值,)存放在block内部一个同名的成员变量中.
    静态变量(静态变量的地址捕获到block中),存放在block内部一个同名的成员变量中
    当访问全局变量时,因为全局变量是一直存在,不会销毁,所以在block中直接访问全局变量,不需要进行捕获。
    block 的类型 (全局,堆,栈)
    2,block的类型,有没访问auto变量,区分是全局的还是栈空间的
    3,栈空间类型的block 执行copy操作,变成堆空间的block .
    如何改变block内捕获的变量值(__block的用法)
    __block 原理
    在block内部来修改外部变量的值,当然,__block只能用来修饰auto变量,不能用来修饰全局变量和静态变量。
    __block修饰的auto变量,编译器会将此变量封装成一个结构体(其实也是一个对象),结构体内部有以下几个成员变量 isa,val(使用的外部变量,如果是基本数据类型,就是变量的值,如果是对象类型,就是指向对象的指针)
    如果是修饰对象类型的auto变量,那么生成的结构体中会多出copy和dispose两个函数,用来管理person对象的内存。
    block循环引用带来的问题。如何解决
    如果block作为一个对象的属性,并且在block中也使用到了这个对象,则会产生循环引用,导致block和对象相互引用,无法释放。
    用weak 修饰block 内用捕获的对象。
    性能优化
    事件响应传递链
    寻找 响应者(iOS响应者链)
    触碰屏幕时,系统会把这一操作封装成一个UIEvent放到事件队列里。然后application从事件队列中取出这个事件。接着寻找响应这个事件的最佳视图。此时用到2个重要的方法。

    (void) hitTest (返回视图层级中能响应触控点最深的视图)
    (Bool) pointInside (返回视图中是否包含响应的点)

    寻找响应者结论
    1,寻找事件的最佳响应视图是通过hittest和pointInside完成的。
    2,hittest的调用顺序是从UIWindow开始的,对每个视图的子视图一次调用。子视图的调用顺序是从后面往前,也可以说是显示从上面到下面。
    3,遍历直到找到响应视图,然后逐级返回到UIWindow返回此视图。
    处理者
    卡顿现象的解决和原理
    组件化开发
    mach-o
    埋点
    启动流程
    app的启动流程分为2种(冷启动和热启动)
    app 的启动阶段

    dyld
    runtime
    main

    相关文章

      网友评论

          本文标题:Runloop 本质是什么?

          本文链接:https://www.haomeiwen.com/subject/xdnkbltx.html