iOS底层-Runtime系列一

简介

  1. Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
  2. Objective-C的动态性是由Runtime API来支撑的
  3. Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

isa

位域

  1. 代码示例: Person类

     @interface Person : NSObject
     @property (nonatomic, assign) BOOL tall;
     @property (nonatomic, assign) BOOL rich;
     @property (nonatomic, assign) BOOL handsome;
     @end
    
    1. 如果当前Person使用3个BOOL属性,那么这3个属性将占据3个字节,那么我们可以不可以用一个字节来表达这3个属性的值呢?以便于节省内存
    2. 可以申请一个char成员变量,占据1个字节(8个二进制位),可以指定其中的某3个二进制位分别表示3个属性的值,比如后三位: 0b 0000 0111
    3. 那么如何取出相应的二进制位呢?
      1. 使用二进制按位与运算:&
    4. 如何设置相应的二进制位呢?
      1. 使用二进制按位或运算:|
  2. 优化代码:

     @interface Person : NSObject
     - (void)setTall:(BOOL) tall;
     - (BOOL)isTall;
        
     - (void)setRich:(BOOL) rich;
     - (BOOL)isRich;
        
     - (void)setHandsome:(BOOL) handsome;
     - (BOOL)isHandsome;
     @end
        
     //Mask掩码,一般用来与&运算
     #import "Person.h" 
        
     //#define TallMask 1
     //#define RichMask 2
     //#define HandsomeMask 4
        
     //#define TallMask 0b00000001
     //#define RichMask 0b00000010
     //#define HandsomeMask 0b00000100
        
     #define TallMask  (1<<0)      //1左移0位
     #define RichMask  (1<<1)      //1左移1位
     #define HandsomeMask  (1<<2)  //1左移2位
        
     @interface Person ()
     {
         char _tallRichHandsome;//二级制后3位分别代表:handsome、rich、tall, 0b 0000 0111
     }
        
     @end
        
     @implementation Person
        
     - (instancetype)init
     {
         self = [super init];
         if (self) {
             _tallRichHandsome = 0b00000111;
         }
         return self;
     }
        
     - (void)setTall:(BOOL) tall{
         if (tall) {
             _tallRichHandsome |= TallMask;
         }else{
             _tallRichHandsome &= ~TallMask;
         }
            
     }
     - (BOOL)isTall{
            
         return !!(_tallRichHandsome & TallMask);
     }
        
     - (void)setRich:(BOOL) rich{
         if (rich) {
             _tallRichHandsome |= RichMask;
         }else{
             _tallRichHandsome &= ~RichMask;
         }
     }
     - (BOOL)isRich{
            
          return !!(_tallRichHandsome & RichMask);
     }
        
     - (void)setHandsome:(BOOL) handsome{
            
         if (handsome) {
             _tallRichHandsome |= HandsomeMask; // _tallRichHandsome= _tallRichHandsome | HandsomeMask
         }else{
             //先将HandsomeMask按位取反,然后在于_tallRichHandsome,进行&运算
             _tallRichHandsome &= ~HandsomeMask;
         }
     }
     /**
      0000 0000
      &
      0000 1000
      */
     - (BOOL)isHandsome{
            
          return !!(_tallRichHandsome & HandsomeMask);
     }
     @end
        
     //测试
     Person *person = [[Person alloc] init];
     [person setRich:NO];
     NSLog(@"%d=%d=%d",person.isTall,person.isRich,person.isHandsome);
    
  3. 使用结构体的位域功能

     #import "Person.h"
    
     @interface Person ()
     {
         //定义一个结构体类型成员变量
         //结构体支持位域技术
         struct{
             char tall : 1; //代表只占用1个二进制位,不用管左边的char类型,以右边的 :1 为准
             char rich : 1;
             char handsome : 1;
         }_tallRichHandsome;//变量整体占用一个字节:0b00000000,第一个成员在最后一位,往下依次
     }
     @end
        
     @implementation Person
        
        
     - (void)setTall:(BOOL) tall{
            
         _tallRichHandsome.tall = tall;
     }
     - (BOOL)isTall{
         //如果tall值为1,为何返回为-1?
         //_tallRichHandsome.tall是一个二进制位1,而返回值是BOOL,如果强制转换为BOOL,那么它会将剩余的7位全部补为1
             //就变成: 0b 1111 1111
     //    return _tallRichHandsome.tall;
         //2次取反可以解决这个问题: !_tallRichHandsome.tall 返回值一定是一个BOOL,非0即真。 再次!就是原值0或1
         return  !!_tallRichHandsome.tall;
     }
        
     - (void)setRich:(BOOL) rich{
        _tallRichHandsome.rich = rich;
     }
     - (BOOL)isRich{
            
         return !!_tallRichHandsome.rich;
     }
        
     - (void)setHandsome:(BOOL) handsome{
            
         _tallRichHandsome.handsome = handsome;
     }
        
     - (BOOL)isHandsome{
            
         return !!_tallRichHandsome.handsome;
     }
     @end
        
     //使用        
     Person *person = [[Person alloc] init];  
     [person setRich:YES];
     [person setHandsome:YES];
     //0=-1=-1 为何是-1呢?
     NSLog(@"%d=%d=%d",person.isTall,person.isRich,person.isHandsome);
    

联合体、结构体位域表示

  1. 示例:

     #import "Person.h"
        
     @interface Person ()
     {
         //联合体成员_tallRichHandsome
         union{
             char bits;//占据一个字节
             //增加可读性,告诉bits这个字节数据哪些二进制位表示什么
             struct{
                 char tall : 1; //bits二进制位最后一位是tall
                 char rich : 1; //bits二进制位倒数第二位是rich
                 char handsome : 1;//bits二进制位倒数第三位是handsome
             };
         }_tallRichHandsome;
            
         //等价
         //    char _tallRichHandsome;
              
     }
     @end
        
     @implementation Person
        
     - (void)setTall:(BOOL) tall{
         //可以直接设置对应的二进制位
         _tallRichHandsome.tall = tall;
     }
     - (BOOL)isTall{
         return  !!_tallRichHandsome.tall;
     }
        
     - (void)setRich:(BOOL) rich{
         _tallRichHandsome.rich = rich;
     }
     - (BOOL)isRich{
            
         return !!_tallRichHandsome.rich;
     }
        
     - (void)setHandsome:(BOOL) handsome{
            
         _tallRichHandsome.handsome = handsome;
     }
        
     - (BOOL)isHandsome{
            
         return !!_tallRichHandsome.handsome;
     }
     @end
    
    1. 分析:
      1. 上面联合体成员变量_tallRichHandsome,等价于直接写一个 _tallRichHandsome成员char _tallRichHandsome;,那么为何偏要用一个union类型的呢?
      2. 因为如果仅仅是char _tallRichHandsome;成员变量,那么如何告诉别人这个字节的8位中哪一位分别代表什么呢?
      3. 联合体union的内存公用技术,结构体的struct位域技术,同时可以满足:不占用多余内存,又能直接说明二进制位每一位都代表什么
        1. union内部尽管有2个成员:一个是bits,一个是无名称的struct,bits是char类型占据1个字节,struct仅仅只是3个二进制位,内存对齐后则也是1个字节,因此union类型的成员整体还是占用一个字节
      4. union中的无名的struct成员,是什么作用呢?(重点!!!!经典!!!)
        1. 增加可读性,告诉bits这个字节数据哪些二进制位表示什么
        2. struct无名,可以直接通过联合体对象取出struct的成员,比如: _tallRichHandsome.handsome;
        3. 可以直接通过struct的成员访问修改bits的二进制位 图1

          1. 无名struct和bits公用一块内存,而且struct采用位域技术使得直接可以通过struct的成员访问修改到相应二进制位的数据
          2. 那么也就是相应修改了bits的数据
          3. 这就是使用union与无名struct结合的经典之处!!!
          4. 直接可以通过struct的成员修改bits相应的二进制位

isa详解

  1. 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
  2. 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
  3. 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
  4. arm64 isa类型源码如下:

     union isa_t
     {
         // 构造函数
         isa_t() { }
         isa_t(uintptr_t value) : bits(value) { }
            
         Class cls;
         uintptr_t bits;
            
     #if SUPPORT_NONPOINTER_ISA
            
     # if __arm64__
     #   define ISA_MASK        0x0000000ffffffff8ULL
     #   define ISA_MAGIC_MASK  0x000003f000000001ULL
     #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
            
         struct {
             uintptr_t indexed           : 1;
             uintptr_t has_assoc         : 1;
             uintptr_t has_cxx_dtor      : 1;
             uintptr_t shiftcls          : 33;
             uintptr_t magic             : 6;
             uintptr_t weakly_referenced : 1;
             uintptr_t deallocating      : 1;
             uintptr_t has_sidetable_rc  : 1;
             uintptr_t extra_rc          : 19;
            
     #       define RC_ONE   (1ULL<<45)
     #       define RC_HALF  (1ULL<<18)
         };
            
     # elif __x86_64__
     #   define ISA_MASK        0x00007ffffffffff8ULL
     #   define ISA_MAGIC_MASK  0x001f800000000001ULL
     #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
         struct {
             uintptr_t indexed           : 1;
             uintptr_t has_assoc         : 1;
             uintptr_t has_cxx_dtor      : 1;
             uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
             uintptr_t magic             : 6;
             uintptr_t weakly_referenced : 1;
             uintptr_t deallocating      : 1;
             uintptr_t has_sidetable_rc  : 1;
             uintptr_t extra_rc          : 8;
     #       define RC_ONE   (1ULL<<56)
     #       define RC_HALF  (1ULL<<7)
         };
            
     # else
         // Available bits in isa field are architecture-specific.
     #   error unknown architecture
     # endif
            
         // SUPPORT_NONPOINTER_ISA
     #endif
            
     };
    
    1. 分别对arm64、x86(真机、模拟器)做了适配你,如果仅分析真机,简化如下:

       //从C++角度分析,就相当于一个联合体类,类似于结构体类
       union isa_t
       {
           // 构造函数
           isa_t() { }
           //构造函数2,同时具有初始化列表
           isa_t(uintptr_t value) : bits(value) { }
                  
           Class cls;
           uintptr_t bits; 
       #   define ISA_MASK        0x0000000ffffffff8ULL //取33位1
       #   define ISA_MAGIC_MASK  0x000003f000000001ULL
       #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
                  
           struct {
               uintptr_t indexed           : 1;
               uintptr_t has_assoc         : 1;
               uintptr_t has_cxx_dtor      : 1;
               uintptr_t shiftcls          : 33;
               uintptr_t magic             : 6;
               uintptr_t weakly_referenced : 1;
               uintptr_t deallocating      : 1;
               uintptr_t has_sidetable_rc  : 1;
               uintptr_t extra_rc          : 19;
                      
       #       define RC_ONE   (1ULL<<45)
       #       define RC_HALF  (1ULL<<18)
           };
       };
      
      1. 从上面可以看出bits成员存放了许多信息,struct中描述的信息
      2. struct描述了每个信息占据多少二进制位
      3. 上面的ULL的意思是无符号长整型(unsigned long long),占据64位
      4. ISA_MASK宏与bits进行&运算,可以取出bits中(从右往左)第4位开始的33位二进制的数据
  5. struct位域 分别表示什么
    1. nonpointer
      1. 0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
      2. 1,代表优化过,使用位域存储更多的信息
      3. 这是什么意思呢?
        1. isa是isa_t的联合体类型,内存占据大小8个字节64位
        2. 如果nonpointer为0,也就是64位的最后一位为0,就代表当前的8个字节存储的仅仅是Class或者Meta-Class对象的内存地址,
        3. 不存在无名结构体中的复杂信息
        4. 反之,如果nonpointer为1,说明这8个字节存储的是无名结构体中的复杂信息,cls成员无用
        5. 总结:
          • isa_t可以看成是只有2个成员(因为无名结构体是用来说明bits各个位的内容以及取值、设置bits各个位的内容的)clas与bits
          • 而这2个成员共用一块8个字节的内存,那么如何判断这个内存中存的是clas还是bits呢?
          • 就看这个内存中最后一位(地址最低位)即nonpointer位,是1还是0.如果是1,该内存存储的是bits,如果是0该内存存储的是clas,即Class或者Meta-Class对象的内存地址
    2. has_assoc
      1. 是否有设置过关联对象,如果没有,释放时会更快
    3. has_cxx_dtor
      1. 是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
    4. shiftcls
      1. 存储着Class、Meta-Class对象的内存地址信息
      2. 那么想要获得内存地址:ISA_MASK & bits
      3. (真机)从ISA_MASK可以看出,一个对象的类对象、元类对象的地址值,最后3位二进制位一定是0
    5. magic
      1. 用于在调试时分辨对象是否未完成初始化
    6. weakly_referenced
      1. 是否有被弱引用指向过,如果没有,释放时会更快
    7. deallocating
      1. 对象是否正在释放
    8. extra_rc
      1. 里面存储的值是引用计数器减1
    9. has_sidetable_rc
      1. 引用计数器是否过大无法存储在isa中
      2. 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

位运算补充

  1. 我们常看见苹果系统中有些方法设置枚举参数可以使用|运算符连接,是怎么回事呢?

      self.view.autoresizingMask= UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth;
    
  2. 举例使用:

     //typedef enum {
     //    ZHOptionsOne = 1, //0b00000001
     //    ZHOptionsTwo = 2,//0b00000010
     //    ZHOptionsThree = 4//0b00000100
     //} ZHOptions;
     //或者
     typedef enum {
         ZHOptionsOne = 1<<0, //0b00000001
         ZHOptionsTwo = 1<<1,//0b00000010
         ZHOptionsThree = 1<<2//0b00000100
     } ZHOptions;
        
     @interface ViewController ()
        
     @end
        
     @implementation ViewController
        
     - (void)viewDidLoad {
         [super viewDidLoad];
    
         //用或运算 | 设置多种类型
         [self setOptions:ZHOptionsOne|ZHOptionsTwo];
     }
     //用与运算 & 解析多种类型
     -(void)setOptions:(ZHOptions)options{
         if (options & ZHOptionsOne) {
             NSLog(@"包含了ZHOptionsOne");
         }
         if (options & ZHOptionsTwo) {
             NSLog(@"包含了ZHOptionsTwo");
         }
         if (options & ZHOptionsThree) {
             NSLog(@"包含了ZHOptionsThree");
         }
     }
    

总结

  1. 苹果为何将isa这个本来是Class类型的指针(objc_class的结构体指针),现在换成isa_t联合体类型的成员了呢?
    1. isa占据内存大小不变:以前是Class类型占据8个字节,现在是isa_t联合体类型也占据8个字节(各个成员变量共用)
    2. isa_t联合体类型存储的东西更多,更加节省内存,可以用一个64位二进制保存对象的所有信息,包括原来Class指针指向的地址
    3. isa_t联合体类型完全兼容原来的Class类型,只要最后一位为0,此时的isa_t联合体类型存储的就是Class类型;是1,联合体类型存储的就是bits
Table of Contents