OC基础部分

作者: SeanLink | 来源:发表于2021-10-26 14:17 被阅读0次

    两年前看了小码哥iOS底层班的视频,现在翻过来回顾一下.顺便做下笔记:

    -----------------------------xcode控制台操作-----------------------------

    1.OC文件转为C++代码
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

    在使用clang转换OC为C++代码时,可能会遇到以下问题
    cannot create __weak reference in file using manual reference

    解决方案:支持ARC、指定运行时系统版本,比如
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

    2.查看内存数据
    Debug -> Debug Workfllow -> View Memory (Shift + Command + M)

    创建一个实例对象,至少需要多少内存?

    import <objc/runtime.h>

    class_getInstanceSize([NSObject class]);

    创建一个实例对象,实际上分配了多少内存?

    import <malloc/malloc.h>

    malloc_size((__bridge const void *)obj);

    常用LLDB指令:
    print、p:打印
    po:打印对象

    读取内存
    memory read/数量格式字节数 内存地址
    x/数量格式字节数 内存地址
    x/3xw 0x10010

    修改内存中的值
    memory write 内存地址 数值
    memory write 0x0000010 10

    格式
    x是16进制,f是浮点,d是10进制

    字节大小
    b:byte 1字节,h:half word 2字节
    w:word 4字节,g:giant word 8字节

    -----------------------------NSObject探究-----------------------------

    Class obejct = [[NSObject class] class];
    //判断是否是元类对象
    BOOL result = class_isMetaClass(obejct);//result = 0
    //获取元类对象
    Class meta = object_getClass(obejct);
    BOOL result1 = class_isMetaClass(meta);//result = 1
    

    isa、superclass的指向.

    instance的isa指向class
    class的isa指向meta-class
    meta-class的isa指向基类的meta-class
    class的superclass指向父类的class,如果没有父类,superclass指针为nil

    meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class(即NSObject的元类对象的superclass指针指向NSObejct类对象)

    instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类
    class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类

    image.png

    NSObject对象结构:

    /* 类对象 */
    struct mj_objc_class {
        Class isa;
        Class superclass;
        cache_t cache;
        class_data_bits_t bits;
    }; &FAST_DATA_MASK(0x00007ffffffffff8UL)
       ↓
    struct class_rw_t {
        uint32_t flags;
        uint32_t version;
        const class_ro_t *ro;
        method_list_t * methods;    // 方法列表
        property_list_t *properties;    // 属性列表
        const protocol_list_t * protocols;  // 协议列表
        Class firstSubclass;
        Class nextSiblingClass;
        char *demangledName;
    };
    
    class_ro_t :
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;  // instance对象占用的内存空间
    #ifdef __LP64__
        uint32_t reserved;
    #endif
        const uint8_t * ivarLayout;
        const char * name;  // 类名
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;  // 成员变量列表
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    };
    

    -----------------------------KVO,KVC探究-----------------------------

    KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
    KVO实现相关:
    1.调用willChangeValueForKey:
    2.调用原来的setter实现
    3.调用didChangeValueForKey:
    4.didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法

    KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性

    常见的API有
    - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    - (void)setValue:(id)value forKey:(NSString *)key;
    - (id)valueForKeyPath:(NSString *)keyPath;
    - (id)valueForKey:(NSString *)key; 
    

    category的底层结构

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    

    category的加载过程
    1.通过Runtime加载某个类的所有Category数据
    2.把所有Category的方法、属性、协议数据,合并到一个大数组中(后参与编译的Category数据,会在数组的前面)
    3.将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

    +load方法的调用顺序:
    1.先调用类的load,按照编译先后调用的load(先编译,后调用,调用子类的load之前会调用父类的load)
    2.在调用分类的load(先编译,先调用)

    +initialize方法会在类第一次接收到消息时调用
    调用顺序:
    先调用父类的+initialize,再调用子类的initialize(先初始化父类,在初始化子类,每个类只会初始化一次)

    +initialize和+load的区别:
    +initialize是通过objc_msgSend进行调用的,
    如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
    如果分类实现了+initialize,就覆盖类本身的+initialize调用

    如何给分类添加成员变量:
    默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现

    关联对象提供了一下API:

    //添加关联对象
    void objc_setAssociatedObject(id object, const void * key,
                                    id value, objc_AssociationPolicy policy)
    //获得关联对象
    id objc_getAssociatedObject(id object, const void * key)
    //移除所有关联对象
    void objc_removeAssociatedObjects(id object)
                                 
    

    key的常见用法

    static void *MyKey = &MyKey;
    objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    objc_getAssociatedObject(obj, MyKey)
    
    static char MyKey;
    objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    objc_getAssociatedObject(obj, &MyKey)
    
    使用属性名作为key
    objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    objc_getAssociatedObject(obj, @"property");
    
    使用get方法的@selecor作为key
    objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    objc_getAssociatedObject(obj, @selector(getter))
    
    
    objc_AssociationPolicy 对应的修饰符
    OBJC_ASSOCIATION_ASSIGN assign
    OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
    OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
    OBJC_ASSOCIATION_RETAIN strong, atomic
    OBJC_ASSOCIATION_COPY copy, atomic

    关联对象设计的核心类:
    AssociationsManager
    AssociationsHashMap
    ObjectAssociationMap
    ObjcAssociation

    
    class AssociationsManager {
        static AssociationsHashMap *_map;
    };
    
    class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *>
    
    class ObjectAssociationMap : public std::map<void *, ObjcAssociation>
    
    class ObjcAssociation {
            uintptr_t _policy;
            id _value;
            }
    
    

    关联对象原理

    AssociationsManager
    AssociationsHashMap *_map;

    AssociationsHashMap
    disguised_ptr_t ObjectAssociationMap
    disguised_ptr_t ObjectAssociationMap

    AssociationsMap
    void * ObjectAssociation
    void * ObjectAssociation

    ObjectAssociation
    uintptr_t _policy ; id _value;

    image.png

    -----------------------------Block探究-----------------------------

    block的底层结构探究,首先定义一个block

     int age = 10; 
    void (^block)(int, int) =  ^(int a , int b){
                NSLog(@"this is a block! -- %d", age);
                NSLog(@"this is a block!");
                NSLog(@"this is a block!");
                NSLog(@"this is a block!");
            };
    

    通过转换成c++代码得到结果

    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            int age = 10;
    
            void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    
    
            ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
        }
        return 0;
    }
    

    可以看出block内部实现就是 __main_block_impl_0
    我们点开看一下 __main_block_impl_0结构体的内部

    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        int age;
    };
    
    struct __main_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    };
    
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    

    将main_block_impl_0抽离开来看到,block里面也会存在一个isa指针,因为是栈上的block,所以age只是值传递

    struct __main_block_impl_0 {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
        struct __main_block_desc_0* Desc;
    //如果是__block age会变成一个对象 __Block_byref_age_0 *age
        int age;
       __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    block本质:
    block本质上也是一个OC对象,它内部也有个isa指针
    block是封装了函数调用以及函数调用环境的OC对象

    block变量的捕获


    image.png
    block类型 环境
    NSGlobalBlock 没有访问auto变量
    NSStackBlock 访问了auto变量
    NSMallocBlock NSStackBlock调用了copy

    每一种类型的block调用copy后的结果如下所示:

    block类型 副本源的配置存储域 复制效果
    NSGlobalBlock 程序的数据区域 什么也不做
    NSStackBlock 从栈复制到堆
    NSMallocBlock 引用计数增加

    以下情况,ARC会自动将栈上的block复制到堆上,比如以下情况:
    1.block作为函数返回值时
    2.将block赋值给__strong指针时
    3.block作为Cocoa API中方法名含有usingBlock的方法参数时
    4.block作为GCD API的方法参数时

    当block内部访问了对象类型的auto变量时
    如果block是在栈上,将不会对auto变量产生强引用

    如果block被拷贝到堆上
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

    如果block从堆上移除
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的auto变量(release)

    函数 调用时机
    copy函数 栈上的block复制到堆时
    dispose函数 堆上的block废弃时

    __block对引用的影响

    __block可以用于解决block内部无法修改auto变量值的问题
    __block不能修饰全局变量、静态变量(static)
    编译器会将__block变量包装成一个对象

    当block在栈上时,并不会对__block变量产生强引用

    当block被copy到堆时
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会对__block变量形成强引用(retain)

    如果__block变量从堆上移除
    会调用__block变量内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放指向的对象(release)

    block
    __Block_byref_age_0 *age
    

    变成

    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    }
    

    __block的__forwarding指针
    复制之前:
    栈上的__forwarding指向自己
    复制之后:
    栈上的__forwarding指向堆上的block
    堆上的forwarding指向自己

    解决循环引用:
    第一种.让一个引用改成弱引用.
    第二种.在block函数里面__block将指针置位nil,但这种情况必须要调用block

    面试相关:

    -----------------------------OC基础-----------------------------

    一个NSObejct对象占用多少内存?
    正常来说一个NSObject对象只需要8个字节的空间就行了,但实际上runtime存在一个内存对齐的一个原理.而在runtime源码里面 alignedInstanceSize 这个方法的返回可以看到,实际上返回的是不足16个字节返回的是16个字节.通过下面代码可以看到,系统分配了16个字节给NSObject对象(可以通过malloc_size函数得到);但NSObject对象内部只使用了8个字节空间(在64bit环境下,可以通过class_getInstanceSize函数获得)。

    // Class's ivar size rounded up to a pointer-size boundary.
        uint32_t alignedInstanceSize() {
            return word_align(unalignedInstanceSize());
        }
    
    size_t instanceSize(size_t extraBytes) {
            size_t size = alignedInstanceSize() + extraBytes;
            // CF requires all objects be at least 16 bytes.
            if (size < 16) size = 16;
            return size;
        }
    

    对象的isa指针指向哪里?
    1.instance对象的isa指向class对象,当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
    2.class对象的isa指向meta-class(元类)对象
    3.meta-class对象的isa指向基类的meta-class对象

    OC的类信息存放在哪里?
    1.对象的方法、属性、成员变量、协议信息,存放在class对象中
    2.类方法,存放在meta-class对象中
    3.成员变量的具体值,存放在instance对象中

    -----------------------------KVO-----------------------------

    iOS用什么方式实现一个对象的KVO?(KVO的本质是什么?)
    1.系统利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
    2.当修改instance对象的属性时,会调用Foundation框架的_NSSetXXXValueAndNotify函数:
    willChangeValueForKey:
    父类原来的setter
    didChangeValueForKey:
    内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)

    如何手动触发KVO?
    手动调用willChangeValueForKey:和didChangeValueForKey:

    直接修改成员变量会触发KVO么?(下划线调用赋值)
    不会触发KVO

    KVC的赋值和取值过程是怎样的?原理是什么?会触发KVO么?
    赋值原理: setValue:forKey:
    1.按照按照setKey:、_setKey:顺序查找方法
    2.找到方法了就直接传递参数,调用方法
    3.未找到方法查看accessInstanceVariablesDirectly(默认返回YES)方法(如果返回NO直接跳到第六步,异常)
    4.按照_key、_isKey、key、isKey顺序查找成员变量
    5.找到了直接赋值
    6.未找到调用setValue:forUndefinedKey:并抛出异常NSUnknownKeyException

    取值原理:valueForKey:
    1.按照getKey、key、 isKey、_key顺序查找方法
    2.找到方法了就调用方法
    3.未找到方法查看accessInstanceVariablesDirectly(默认返回YES)方法(如果返回NO直接跳到第六步,异常)
    4.按照_key、_isKey、key、isKey顺序查找成员变量
    5.找到了直接取值
    6.调用valueForUndefinedKey:并抛出异常NSUnknownKeyException

    -----------------------------Category-----------------------------

    Category的使用场合是什么?

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

    Category和Class Extension的区别是什么?
    Class Extension在编译的时候,它的数据就已经包含在类信息中
    Category是在运行时,才会将数据合并到类信息中

    Category能否添加成员变量?如果可以,如何给Category添加成员变量?
    不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果

    相关文章

      网友评论

        本文标题:OC基础部分

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