美文网首页
底层代码

底层代码

作者: Jean_Lina | 来源:发表于2020-08-18 17:20 被阅读0次
    9 Category的加载处理过程:
    1 通过Runtime加载某个类的所有分类数据
    2 把所有分类Category的方法、属性、协议合并到一个大数组中,后面参与编译的Category数据,放到数组最前面
    3 将合并后的分类数据(方法、属性、协议),插入到类原来的数据前面
    
    10 Category的实现原理
    Category编译之后的底层结构是struct category_t,里面放着对象方法、类方法、协议信息、属性信息,程序在运行的时候runtime会把Category分类里面的数据合并到类信息中,类对象、元类对象中。
    
    11 Category和Extension的区别是什么?
    Extension是在编译的时候数据已经合并到类信息中。
    Category是在程序运行的时候,将分类里面的数据合并到类信息中。
    
    12 +load方法在runtime加载类、分类的时候调用
    每个类、分类的+load,在程序运行的时候,只调用一次。
    
    +load的调用顺序:
    (1)先调用类的+load
    按照编译先后顺序调用(先编译,先调用)
    调用子类的+load之前会调用父类的+load
    
    (2)再调用分类的+load
    按照编译先后顺序调用(先编译,先调用)
    
    13 Category中有load方法吗?load方法是什么时候调用的?load方法能继承吗?
    有+load方法。
    +load方法是在runtime加载类、分类的时候调用。
    +load方法可以继承,一般情况下我们不手动调用,都是系统自动调用。
    
    14 load、initialize方法的区别是什么?他们在Category中的调用顺序?以及出现继承时他们之间的调用流程?
    调用方式:
    +load是根据函数地址直接调用
    +initialize是通过objc_msgSend进行调用。
    
    调用时刻:
    load是runtime加载类、分类的时候调用(系统直接调用一次)
    initialize是类第一次接收到消息的时候调用。
    
    调用顺序:
    load先调用类的load,再调用分类的load。
    先编译的类先调用,
    调用子类的load之前会先调用父类的load。
    先编译的分类先调用。
    
    initialize先初始化父类的initialize,再初始化子类的initialize,每个类只会initialize初始化一次。如果子类没有实现+initialize,会调用父类的+initialize。所以父类的+initialize可能会被调用多次。
    如果分类实现了+initialize,就会覆盖类本身的+initialize调用。
    15 Category中能否添加成员变量?如果可以,如何给Category添加成员变量?
     Category中不能直接添加成员变量
    可以间接通过关联对象给分类添加成员变量
    
    16 +initialize方法会在类第一次接收到消息时调用。
    调用顺序:先调用父类的+initialize,后调用子类的+initialize。如果父类已经调用过+initialize就不再调用。
    
    17 block的原理是什么?本质是什么?
    block是封装了函数调用和函数调用环境的OC对象。
    block本质也是一个OC对象,内部也有一个isa指针。
    
    18 __block的作用是什么?有什么使用注意点?
    __block可以用于解决block内部无法修改auto变量值的问题。
    __block修饰变量后,编译器会把变量包装成一个对象。
    使用注意点:避免对象的循环引用。
    
    19 block的属性修饰词为什么是copy?使用block有哪些使用注意?
    block如果不copy就不会在堆上。在堆上更方便管理block的生命周期。
    block的使用注意:避免循环引用。
    
    20 block内部在修改NSMutableArray,需不需要添加__block?
    不需要
    
    block有个变量捕获机制
    block内部可以捕获auto局部变量,值传递。
    block内部可以捕获static局部变量,指针传递。
    block内部不捕获全局变量,直接访问。
    
    block有三种类型:
    __NSGlobalBlock__(block内部没有访问了auto变量)
    __NSStackBlock__(block内部访问了auto变量)
    __NSMallocBlock__:__NSStackBlock__的blockcopy之后是一个__NSMallocBlock__
    判断block类型:
    [block class];
    找block父类:[[block class] superclass];
    
    应用程序的内存分配:
    程序区域 .text区 (代码段)
    
    数据区域 .data区(全局变量) -> __NSGlobalBlock
    
    堆 ->  (alloc出来的对象,动态申请内存,程序员自己申请和管理)__NSMallocBlock
    
    栈  -> (局部变量,系统自动分配内存和释放)__NSStackBlock
    
    ARC环境下,编译器会根据情况,自动将栈区的block拷贝到堆区。
    (1)block作为函数返回值。
    (2)block赋值给__strong指针时
    (3)block作为GCD的API参数
    OC源码链接:[https://opensource.apple.com/tarballs/objc4/]
    
    OC代码转换为C++代码
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
    使用weak、strong引用:
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0  main.m -o main.cpp
    
    //SideTable结构体
    struct SideTable {
        spinlock slock;
        RefcountMap refcnts; //引用计数存储,当前最新的指针为key,value为引用计数。
        weak_table_t weak_table; //weak引用存储,散列表
    }
    struct objc_class {
        Class isa;
        Class superclass;
        cache_t cache; //方法缓存
        class_data_bits_t bits; //获取具体的类信息
    };
    
    struct method_t {
    SEL name; //函数名
    const char *types; //编码(返回值类型,参数类型)
    MethodListIMP imp; //指向函数的指针(函数地址)
    };
    
    struct cache_t {
        struct bucket_t *_buckets; //散列表
        mask_t _mask;             //散列表的长度减1
        mask_t _occupied;         //已经缓存的方法数量
    };
    struct bucket_t {
        cache_key_t _key; // SEL作为key
        IMP _imp;         //函数的内存地址
    };
    
    自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
      
    互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。
    
    截屏2020-08-29 上午1.28.07.png 截屏2020-08-29 上午1.18.55.png

    相关文章

      网友评论

          本文标题:底层代码

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