iOS总结

作者: 喵星人and亦人 | 来源:发表于2020-10-10 21:05 被阅读0次

    1.分类的加载处理过程

    1. 通过Runtime加载某个类的所有Category数据
    2. 把所有Category的方法、属性、协议数据,合并到一个大的数组中(这个大的数组是一个二维数组),后面参与编译的Category数据会在数组的前面
    3. 将合并后的分类数据(方法、属性、协议),插入到类的原来的数据的前面。
      因为以上所以分类的方法会覆盖类里面的相同名字的方法,但是不是真正的覆盖,只是最先遍历到的是分类里面的方法

    2.Category的实现原理

    1. Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
    2. 在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

    3. Category和Class Extension的区别是什么?

    • Class Extension在编译的时候,他的数据就已经包含在类信息中(编译期已经合并到类信息中了)
    • Category是在运行时,才会将数据合并到类信息中

    4. Load方法的调用

    1. load方法会在runtime加载类,分类的时候调用(也就是在程序运行的时候调用)
    2. 每个类,分类的load方法在程序运行的过程中只会调用一次
    3. load方法的调用顺序
       1. 先调用类的load方法
        * 按照编译先后顺序调用(先编译,先调用)
        * 调用子类的load方法之前会先调用父类的load方法
       2. 再调用分类的load方法
        * 按照编译先后顺序调用(先编译,先调用)
      
    4. +load方法可以继承,但是一般不会主动调用(系统调用+load方法的时候没有用到消息发送机制,而是直接找到的+load方法的地址来直接调用的)

    5. +Initialize的调用

    1. +Initialize 会在类第一次接收到消息的时候调用
      • 先调用父类的+ initalize,再调用子类的+ initialize,但是它也遵循消息发送机制,即如果分类中实现了+ initalize,那么就会覆盖原来类的+ initalize,原来类的+ initalize就不会被调用.
      • 先初始化父类,再初始化子类,每个类只会初始化一次
        *+ initalize的调用再源码中,会在每次对象调用方法前去判断,当前类的+ initalize方法是否被调用过,如果没有被调用过就判断他的父类的+ initalize方法是否被调用,没有调用就先调用父类的+ initalize,再调用自己的+ initalize方法,如果都调用过了接下来走正常的消息发送机制.
      • initalize和+load的很大区别是,+ initalize查找过程后的调用是通过objc_msgSend进行调用的,所以有以下特点
        * 如果子类没有实现+ initalize,会调用父类的+ initalize(所以父类的+ initalize可能会被调用多次)
        * 如果分类实现了+ initalize,就会覆盖类本身的+ initalize的调用
        

    6. Block

    1. block本质
      • block本质上是一个OC对象,它内部也有一个isa指针
      • block是封装了函数调用以及函数调用环境的OC对象
    2. block捕获变量
      • 局部变量(atuo,static)的捕获,auto变量的捕获是值传递,static捕获是地址传递,这样捕获的原因是auto变量可能会销毁,所以只能是值传递
      • 全局变量,block内部不会捕获,因为全局变量,任何地方都能访问,所以不需要捕获


        WX20201012-112136@2x.png
      • self 也会被block所捕获,self也是局部变量,因为self本身就是地址,所以他也是值传递
    3. block 的类型
      • 没有访问auto变量的是global类型
      • 访问了auto变量是stack类型
      • stack类型调用copy后变为malloc类型


        Block调用copy.png
    Block类型.png
    1. block的copy
      在ARC环境下,编译器会鞥局情况自动将栈上的block复制到堆上,比如以下情况
      • block作为函数的返回值时
      • 将block赋值给__strong指针时
      • block作为Cocoa API中方法名含有usingBlock的方法的参数时
      • block作为GCD API的方法参数时
    2. block对象类型的auto变量的捕获


      对象类型的auto的变量.png
    3. __block修饰符
      • __block可以用于解决block内部无法修改auto变量值的问题
      • __block 不能修饰全局变量,静态变量
      • 编译器会将__block修饰的变量包装成一个对象
    4. 被__block 修饰的对象类型
      • 当__block变量在栈上时,不会对指向的对象产生强引用
      • 当__block变量被copy到堆上时
        1. 会调用__block变量内部的copy函数
        2. copy 函数内部会调用_Block_object_assign函数
        3. _Block_object_assign函数会根据所指向的对象的修饰符(__strong,__weak,__unsafe_unretaind)做出相应的操作,形成强引用或者弱引用(注意: 这里仅限ARC时会retain,MRC时不会retain)
      • 如果__block变量从堆上移除
        1. 会调用__block变量内部的dispose函数
        2. dispose 函数内部会调用_Block_object_dispose函数
        3. _Block_object_dispose函数会自动释放指向的对象(release)
    5. block的循环引用
      • 可以用___weak或者__unsafe_unretianed来解决循环引用的问题
      • 他们的不同点
        __weak:  不会产生强引用,指向的对象销毁时,会自动让指针置为nil
        __unsafe_unretianed不会产生强引用,不安全,因为当指向的对象销毁时,指针不会置为nil,指针存储的地址不变,会产生野指针的问题
        
      • 使用__block也能解决循环引用的问题,但是必须要调用block,并且要在block内部将变量置为nil,因为__block修饰的变量会被包装成一个对象,这个对象内部对变量有一个强引用,对象本身对被包装的__block对象有一个强引用,会形成一个三角引用,需要调用block后,并且在block的调用内部将变量置为nil,才会打破这种循环引用.
    6. block的属性修饰词为什么是copy?使用block有哪些注意?
      • block一旦没有进行copy操作,就不会在堆上,就不能自己控制他的生命周期
      • 使用注意: 要注意循环引用

    相关文章

      网友评论

        本文标题:iOS总结

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