美文网首页iOS 底层分析
iOS 底层 - 名词解析

iOS 底层 - 名词解析

作者: He_Define | 来源:发表于2019-12-05 15:05 被阅读0次

    目录

    • 前言
    • 名词解析
    • OC消息传递和转发机制
    • Runtime
    • runtime动态创建类
    • Runloop
    • Method Swizzling黑魔法
    • 自己动手写一个框架
    • Category实现原理 和 Protocol
    • 反射机制
    • Json到Model的转化
    • 快速归档
    • 访问私有变量
    • 。。。

    名词解析

    1. 源码 <objc/objc.h>/<objc-private.h> 中的名词

    ① 实例对象 ===> id 和结构体 objc_object
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    源码指出,id 是指向 objc_object 的指针。而注释告诉我们,id 就是一个指向某个类的实例对象的指针。那 objc_object 就是某个类的实例对象,如下:

    // <objc/objc.h> 源码中
    // Objective-C 2.0 已废弃  
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    
    // <objc-private.h> 源码中
    // Objective-C 2.0 支持的定义
    struct objc_object {
    private:
        isa_t isa;
    public:
        Class ISA();
        Class getIsa();
        void initIsa(Class cls /*nonpointer=false*/);
        void initClassIsa(Class cls /*nonpointer=maybe*/);
        void initProtocolIsa(Class cls /*nonpointer=maybe*/);
        void initInstanceIsa(Class cls, bool hasCxxDtor);
        ...
    }
    

    objc_object 表示一个类的实例objc_object 里面有一个 isa_t 类型的 isa 指针,这个指针指向了这个对象的类对象

    ② 类 ==== Class 和结构体 objc_class
    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    

    从源码中可以看出,Class 是一个指向 objc_class 结构体的指针.注释告诉我们,Class 表示一个 Objective-C 类。那么 objc_class 又是什么?

    // <objc/runtime.h> 源码中
    // Objective-C 2.0 已经废弃
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
    ....
    #endif
    } OBJC2_UNAVAILABLE;
    
    
    // <objc-runtime-new.h> 源码中
    // Objective-C 2.0 支持
    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             
        class_data_bits_t bits;    
        class_rw_t *data() { 
            return bits.data();
            // 这个结构体里有所有的变量列表,方法列表等
        }
        ...
    }
    

    我们可以从源码中看出来, objc_class 结构体继承自 objc_object. 说明 objc_class 也是个对象。那我们就很简单得推断出 objc_class 也有个 isa 指针。那么这个 isa 指针又指向的是什么呢?

    其实这里仅仅从源码来看,我们并不知道这里的isa指向了哪里,反正不是父类,因为父类下面还有个 superclass 指向了它的父类
    那 isa 指向了哪里呢? 只能通过网上的解答来解释了:
    isa指向的是它的 metaclass(元类) , 元类也是 objc_class 的另一种对象


    关于类和对象

    我们常说的对象,一般指的是 实例对象。 但其实对象不仅仅是实例对象,还有其他的, 比如类对象. 我们上面有说到,objc_classobjc_object 几乎一致,那我们从 runtime 的源码下继续深入研究中看到

    // <objc-runtime-new.h> 源码
    struct objc_class : objc_object {
        ...
    }
    

    从这里看出,objc_class 是继承了 objc_object 的结构体。这里的源码我们其实可以先得出一个结论:
    类也是一个对象

    关于 metaclass 元类

    在国外大牛的文章:What is a meta-class in Objective-C? 中对 metaclass 的解释总结过来有三个要点:

    1. metaclass 元类也是一个对象,是一个 obj_class 的一种,即是类对象的一种。这意味着, 你也可以对这些元类作为对象来发送消息。也因此每个Class都有其 metaclass 原类
    2. 所有的 metaclass 元类都使用同一个基础类的 元类。这意味着 几乎所有的metaclass 元类都是 ‘NSObject的元类’ 的派生类。
    3. 几乎所有的 metaclass元类 上的 isa 指针 指向的都是 ‘NSObject的元类’.元类也有个 isa 指针指向 root meteClass 根元,根元的 isa 指针指向自己

    最后来一张经典图片收尾



    ③ 方法选择器 ==== SEL 和结构体 objc_selector
    /// An opaque type that represents a method selector.
    typedef struct objc_selector *SEL;
    

    SELobjc_selector 结构体的指针,表示了一个方法的选择器。结构体的内容在 runtime.hobj.h 的源码中无法找到,所以我们无法得知 objc_selector 到底是什么。不过很多 Blog 和书籍中指出: " SEL 其实也是其中一种数据类型,用来定义类方法,是类成员方法的指针,本质上其实只是一串字符串。”(其实可以看做是类方法的identifier),接下来验证这种说法是否正确

    // 首先,我们先定义一个 SEL, 定义 SEL 可以有多种方式
    SEL aSelector = @selector(SEL selector);
    
    SEL bSelector = SEL sel_registerName(const char *name);
    // (NSSelectorFromString 方法就是通过 sel_registerName 方法来注册的 SEL)
    SEL cSelector = NSSelectorFromString(NSString *cSelectorName);
    // 其实从定义我们就可以看出来, SEL 和 字符串 可能存在某种转化。
    
    // 除此之外, 我们输出下这个 SEL 发现:
    // 尽管报了 warning ,但是你还是可以看到输出的就是个字符串,而没有报错
    NSLog(@"%s",aSelector);
    
    
    // 另外,我们还可以通过另一个方法来看看 SEL 其实也是个类似Key的东西
    // 举例, dog 和 cat 是两个不同的实例对象,都有一个名为 description 的方法
    SEL selector1 = @selector(description);
    [dog performSelector:selector1];    // 调用 dog 的 description 方法
    [cat performSelector:selector1];    // 调用 cat 的 description 方法
    // 可以看出虽然 SEL 虽然相同, 但是调用的方法是不同的。
    

    那么其实 我们是否可以大胆得得出一个结论:SEL 其实就只是 一个方法名 或者说 标识. 这一点其实从
    后面的 objc_method 结构体中 SEL method_name 命名可以看出。

    ④ 方法实现 ==== IMP
    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    // 指向C的函数
    typedef void (*IMP)(void /* id, SEL, ... */ );                  // 无返回值
    #else
    // OC的指向方法的指针
    typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);    // 有返回值
    #endif
    

    IMP(Implementation)是一个指向 方法实现的地址 的指针,参数都包含 idSEL 类型。通过 id (实例对象)里的 SEL (方法名) 获取到唯一的实现方法。

    • 获取 IMP 的方式
    IMP imp = class_getMethodImplementation(Class cls, SEL name)
    IMP imp = method_getImplementation(Method _Nonnull m) 
    
    • 调用方式
    // 无参无返回值的 IMP 调用。    (其实走的是C语言的指向函数)
    imp();
    
    // 对于有参数有返回值的 IMP 的调用比较麻烦。
    // 这里给出一种常规操作: 强制类型转换 (例子中返回值和参数的类型都是 int)
    int (*imp)(id, SEL, int) ;
    imp = (int (*)(id, SEL, int))method_getImplementation(method); // 强制类型转换
    Object *obj = [[Object alloc] init];
    NSLog(@"%d", imp(obj, @selector(method_name:), 3));
    

    当然,IMP还有更底层更广泛的应用

    其实,直接调用方法的 IMP指针效率比调用方法本身更高。但是也不要盲目得去用IMP来提高速率,毕竟可读性肯定没有那么高了。

    ⑤ 布尔值 ==== bool 和 BOOL 和 Boolean、true 和 false 、 YES 和 NO
    • 关于 booltrue/false
    // bool 的定义 在 <stdbool.h> 中 原代码较长,总结下来是这样的
    // 如果是 C 语法
    #define bool _Bool // 因为 C 语言 不具有 bool 值的,因此 C99 用 _Bool 来兼容 C++
    #define true 1
    #define false 0
    // 如果是 C++ 语法
    #define bool  bool
    #define false false
    #define true  true
    
    1. 这里注意, bool 不管在 C 还是在 C++, 都是只有 1个字节
    2. bool 值为 true/false, 对应的值只有 1/0
    3. 所有的 非0 数值, 强制转换成 bool 类型 都会转换成 1/true 。 如下:
    NSLog(@"%d %d %d %d %d", (bool)1, (bool)-1, (bool)0, (bool)256, (bool)8);
    //输出 1, 1, 0, 1, 1
    
    • BOOLYES/NO
    // BOOL 的定义 在 <objc.h> 中
    // OBJC_BOOL_IS_BOOL 的定义省略. 总结就如下
    #if OBJC_BOOL_IS_BOOL // 当 64位iPhone 或 ARMv7K 时
        typedef bool BOOL;
    #else // 当是 32位iPhone 以及 非ARMv7K 时
        typedef signed char BOOL; 
    #endif
    
    // objc_bool 的 类型与 系统位数有关
    #if __has_feature(objc_bool) 
    #define YES __objc_yes   // __objc_yes = signed char 1 (32位) / true (64位)
    #define NO  __objc_no    // __objc_no = signed char 0 (32位) / false (64位)
    #else
    #define YES ((BOOL)1)
    #define NO  ((BOOL)0)
    #endif
    
    1. BOOL 不管是32位系统还是64位系统,都占了 1个字节
    2. 32 位系统 的类型是 signed char . 字符空间只有8位,超过8位的都只会取低8位的值。如 256 = 0b1 0000 0000 转换成 BOOL 会等于 0
    3. 所有的 非0 数值, 强制转换成 BOOL 类型,都不会转换成 1/true
    NSLog(@"%d %d %d %d %d", (BOOL)1, (BOOL)-1, (BOOL)0, (BOOL)256, (BOOL)8);
    //输出 1 -1 0 0 8
    

    注意: 这里不会转换成0/1 会导致一个问题。如果在条件判断语句中,使用 condition == YES,或 condition != YES这种写法,等式会不成立。比如:

    BOOL condition = 123;                               BOOL condition = 123;
    if (condition == YES) {                             if (condition) {
        // 不会走这里                                       // 走这里, 正确
    } else {                     ==用该方法改进==>        } else {
        // 走的是这里. 这是错误的
    }                                                   }
    
    • 总结来说
    • 判断语句中不要用 == YES 的方式
    • 尽量避免赋值给 BOOL 。 如果要赋值给 BOOL 也请注意取值范围 (因为超过8位会截取)
    • Boolean
    // Boolean 的定义在 <MacTypes.h> 中
    typedef unsigned char Boolean;
    

    Boolean 的注释中 有说到 Mac OS historic type, sizeof(Boolean)==1 说明Boolean只有一个字节

    区别 nil Nil NULL 和 NSNull
    // 源码中的 nill Nil 在 <objc.h>
    // Nil 定义
    #ifndef Nil
    # if __has_feature(cxx_nullptr)
    #   define Nil nullptr
    # else
    #   define Nil __DARWIN_NULL
    # endif
    #endif
    
    // nil 定义
    #ifndef nil
    # if __has_feature(cxx_nullptr)
    #   define nil nullptr
    # else
    #   define nil __DARWIN_NULL
    # endif
    #endif
    
    // NULL 在 C 语言中的定义
    #undef NULL
    #if defined(__cplusplus)
    #define NULL 0
    #else
    #define NULL ((void *)0)
    #endif
    
    // NSNull
    @interface NSNull : NSObject <NSCopying, NSSecureCoding>
    + (NSNull *)null;
    @end
    
    • 适用场景:

      • Nil 代表 类对象 指向一个 空指针 如: Class a = Nil
      • nil 代表 OC对象 指向一个 空指针 如: NSString *a = nil
      • NULL 一般用来使 基础类型 指向 空指针 如: int *pointerInt = NULL
      • NSNull 表示一个 空的占位对象
         //这里的 [NSNull null] 不能用 nil 替换。[NSNull null] 表示空对象
         NSArray *arr = [NSArray arrayWithObjects:@"1",[NSNull null],@"2", nil];
         NSLog(@"%@",arr);
        // 输出  (1,"<null>",2)
        
    • 注意事项:

      • 对象很可能会被销毁后不置nil。(这个其实也是很常见。比如你命名一个对象时用了 assign 而不是 weak 时。对象被销毁后不会置nil, 所以指针指向的地址以及不存在,成为野指针)
        这里有一点不置nil的对象 发送消息时会崩溃。 但是 置nil的对象 发送消息消息不会崩溃
        所以,为了安全起见, 在调用方法之前判断一下是否为空是很有必要的

        NSString *str = nil;
        if (str != nil) {
            // do Something
        }
        
      • 尽量不要用 NULL 去初始化 OC对象

      • iOS开发中 在向服务请求数据或者请求网页,对获得的数据解析时,如果遇到 (null) 或 <null> 时。因为这个数据不是 nil 而是 NSNull对象 ,所以在这个解析后的对象发送消息 会崩溃。崩溃消息:unrecognized selector sent to instance
        解决的办法会在后面说到

    2. 源码<objc/runtime.h>/<objc-runtime-new.h>中的名词

    ① 方法 ==== Method 和结构体 method_t / objc_method
    // <runtime.h> 源码中 (Objective-C 2.0 已废弃)
    typedef struct objc_method *Method;
    struct objc_method {
        SEL _Nonnull method_name                            OBJC2_UNAVAILABLE;
        char * _Nullable method_types                       OBJC2_UNAVAILABLE;
        IMP _Nonnull method_imp                             OBJC2_UNAVAILABLE;
    }OBJC2_UNAVAILABLE;
    

    已经在 Objective-C 2.0 中废弃的,我们就不看了。重点关注下面的源码

    // <objc-runtime-new.h> 源码中
    typedef struct method_t *Method;
    
    struct method_t {
        SEL name;               // 方法名
        const char *types;      // 返回值和参数
        MethodListIMP imp;      // 指向“方法的实现”的指针  注: using MethodListIMP = IMP;
    
        struct SortBySELAddress :
            public std::binary_function<const method_t&,
                                        const method_t&, bool>
        {
            bool operator() (const method_t& lhs,
                             const method_t& rhs)
            { return lhs.name < rhs.name; }
        }; // 根据name的地址对方法进行排序的函数
    };
    

    这里我们可以知道 一个 Method 需要具备以下几个条件: 方法名、参数、返回值、实现方法
    不同的类可以有相同的 Method 方法。查找 Method 的时候会在类的 Method 链表中进行查找。
    定位到一个 Method 一般有下面两种:

    // 获取实例方法
    Method *method = class_getInstanceMethod([ClassName class], @selector(method_name))
    // 获取类方法
    Method *method = class_getClassMethod([ClassName class], @selector(method_name))
    

    常用的方法

    // 给类添加一个方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    // 替换 cls 类中的 name 方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
    // 交换 m1, m2 方法
    method_exchangeImplementations(Method  m1, Method  m2)
    // 设置 指向方法的实现 指针。 返回 原来的指向方法实现的指针
    IMP method_setImplementation(Method method, IMP imp)
    
    ② 成员变量 ==== Ivar 和结构体 ivar_t / objc_ivar
    // <runtime.h> 源码中 (Objective-C 2.0 已废弃)
    typedef struct objc_ivar *Ivar;
    struct objc_ivar {
        char *ivar_name;  //实例变量名
        char *ivar_type;  //实例变量类型类型
        int ivar_offset;  //基地址偏移字节
        int space;
    }
    

    以上源码已经在Objective-C 2.0中被废弃

    // <objc-private.h>
    typedef struct ivar_t *Ivar;
    
    // <objc-runtime-new.h> 源码中
    struct ivar_t {
        int32_t *offset;            // 基地址 偏移字节
        const char *name;           // 实例变量名
        const char *type;           // 实例变量类型
        uint32_t alignment_raw;     //对齐
        uint32_t size;              //大小
        uint32_t alignment() const {
            if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
            return 1 << alignment_raw;
        }
    };
    

    Ivar 是指向 对象的实例变量。Ivar 是指向 ivar_t 结构体 的指针,从结构体看出,Ivar 包括类型和名字等。

    • 常用的方法:
    // 1. 获取 Ivar 实例变量列表
    unsigned int count;
    Ivar *ivarList = class_copyIvarList([Class class], &count);
    // 这里如果想要知道每一个实例变量。用个 for循环 就可以了
    for (int i = 0; i < count; i ++) {
        Ivar ivar = ivarList[i];
    }
    free(ivarList);  // 结束后要释放
    
    // 2. 获取 Ivar 实例变量
    Ivar ivar = class_getInstanceVariable([Class class], ivar_name);
    
    // 3. 获取 结构体对应的值
    ivar_getName(Ivar ivar)              // 获取 实例变量名
    ivar_getTypeEncoding(Ivar ivar);     // 获取 实例类型
    ivar_getOffset(Ivar ivar);           // 获取 实例偏移值
    
    // 4.获取/设置 变量
    object_setIvar(id obj, Ivar ivar, id value)     // 设置某个对象的 实例变量的值
    object_getIvar(id obj, Ivar ivar)               // 获取某个对象的 实力变量的值
    
    // 5.变量的添加 (无法在运行时添加)
    class_addIvar(Class cls, const char *name, size_t size,alignment, const char *types)
    

    这里有个注意点,很多人发现变量添加 会失败。因为 无法通过运行时添加ivar 或者说动态添加类的时,已经添加完成的类中,无法新增 Ivar
    所以注释中有说 class_addIvar 需要 objc_allocateClassPair 和 objc_registerClassPair 之间。

    ③ 属性 objc_property_t 和结构体 property_t / objc_property
    // <runtime.h> 源码中 (Objective-C 2.0 已废弃)
    typedef struct objc_property *objc_property_t;
    
    // <objc-private.h> 源码中的定义
    typedef struct property_t *objc_property_t;
    // <objc-runtime-new.h> 源码: 
    struct property_t {
        const char *name;               // 属性名
        const char *attributes;         // 属性的特性
    };
    
    • 常用方法:
    // 1. 获取 属性 的列表
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([Class class], &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = proList[i];
    }
    free(propertyList);
    
    // 2. 根据属性名 获取 objc_property_t
    class_getProperty(Class cls, const char* name)
    
    // 3. 获取结构体的内容
    property_getName(objc_property_t property)          // 获取属性名
    property_getAttributes(objc_property_t property)    // 获取属性的特性
    
    // 4. 对属性进行修改或新增
    class_replaceProperty(Class cls, 
                          const char* name, 
                          const objc_property_attribute_t* attributes, 
                          unsigned int attributeCount)      // 替换属性
    class_addProperty(Class cls, 
                      const char * name,
                      const objc_property_attribute_t*  attributes,
                      unsigned int attributeCount)          // 新增属性(同Ivar 一致)
    
    // 5.属性 的特性 相关
    // 获取属性的特性列表
    property_copyAttributeList(objc_property_t property, unsigned int *outCount)
    // 拷贝属性的特性的值
    property_copyAttributeValue(objc_property_t property, const char *attributeName)
    

    注意,这里的 新增属性方法 在动态添加类结束后 也会失败

    • 关于 attributes 特性 和 objc_property_attribute_t 结构体 :
      我们可以从结构体中看出 attributes 只是个字符串,但是其实从 replace 和 add 方法中可以看出 其实是个 objc_property_attribute_t 结构体的数组
      我们定义属性的时候用到了 类似 nonatomic、strong、assign 来形容属性,这些形容的内容都会 体现在 attributes 这个字符串中。
      /// 定义一个 attribute
      typedef struct {
          const char * _Nonnull name;             // attribute 的名字
          const char * _Nonnull value;            // attribute 的值 (经常是空的)
      } objc_property_attribute_t;
      
      // 新增 property 的时候, attributes 可以这么定义, 如:
      objc_property_attribute_t attr0 = { "T", "@\"NSString\"" };
      objc_property_attribute_t attr1 = { "&", "" };
      objc_property_attribute_t attr2  = { "V", "_newProperty" };
      objc_property_attribute_t attrs[] = { attr0, attr1, attr2};
      

      这里列出这里的 Name 所对应的意思:

    name attribute
    T 类型,如@"NSString"
    & retain
    W weak
    N nonatomic
    R readonly
    C copy
    G(name) getter=(name)
    S(name) setter=(name)
    D @dynamic
    V 实例变量Ivar
    P 用于垃圾回收机制
    关于 Ivar(实例) 和 Property(属性) 的区别

    这里有人会奇怪,Ivar (实例) 和 Property (属性) 有什么区别?
    当你定义个一个新的属性时, @property NSString* newProperty;
    你会发现多了一个实例变量 _newProperty 和两个方法 setter 和 getter 方法
    所以我们 其实可以看做 Property = Ivar + setter + getter

    ④ 分类 ==== Category 和结构体 category_t 和 objc_category
    // <runtime.h> 源码中 (Objective-C 2.0 已废弃)
    typedef struct objc_category *Category;
    struct objc_category {
        char * _Nonnull category_name                         OBJC2_UNAVAILABLE;
        char * _Nonnull class_name                            OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable instance_methods  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable class_methods     OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols       OBJC2_UNAVAILABLE;
    } OBJC2_UNAVAILABLE;
    

    上面的源码已经在 Objective-C 2.0 中废弃.

    // <objc-private.h> 和 <objc-runtime-new.h>源码中
    typedef struct category_t *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. 关于博客中的源码内容
    在查看 <objc/runtime.h> 的过程中,我发现头文件源码中很多都有用到宏定义 OBJC2_UNAVAILABLE 以及用 #if !__OBJC2__ endif 来区别是否是 Objective-C 2.0+ , 那么也就是说我们看到的这些方法其实很多都已经不适用于当前的 Objective-C 版本,最新的很多都看不到了。不过 runtime 的源代码苹果已经开源。我们可以从官网下载到源码 ->源码下载地址。在源码中我们可以看到这些对象里面都有些什么。

    注意: 正如上面所说很多 <runtime.h><objc.h> 的大部分都已经在 Objective-C 2.0 的时候过期了。所以我们 Objective-C 2.0 相对应的 <objc-runtime-new.h><objc-private.h>文件等。 objc4源码 中也可以看到

    2. 关于使用哪种方式来定义属性的类型
    我注意到在源码 <objc/runtime.h> 的注释中有写到 /* Use Class' instead of 'struct objc_class *' */, 可以推断,苹果其实都不建议直接用结构体指针来命名该类型类型的值。

    参考

    1. runtime官方源码下载地址
    2. 深入理解objc中的类与对象
    3. 轻松学习之 IMP指针的作用

    相关文章

      网友评论

        本文标题:iOS 底层 - 名词解析

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