美文网首页ios底层探索
ios 类的结构分析

ios 类的结构分析

作者: Jeffery_zc | 来源:发表于2020-01-16 17:26 被阅读0次

1.类的结构定义

我们在main.m文件中写一段简单的代码:

   LGPerson *person = [LGPerson alloc]
   Class cls  = object_getClass(person);
   NSLog(@"preson ==== %@",person);

然后,我们打开终端cd到当前main.m的上层文件夹中,使用clang命令:

  clang -rewrite-objc main.m -o main.cpp

这时候会生成一个main.cpp的c++文件,打开main.cpp文件,可以看到这样一行代码:

   typedef struct objc_class *Class;

由此可以得出一个结论,Class是一个objc_class的结构体指针,接着,我们需要到objc源码探索一下objc_class的结构。我们在Xcode全局搜索objc_class,会发现有2个定义。第一个是旧版本,在OBJC2中已被废弃:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

在这个版本中,我们可以看到objc_class内的isa是自己定义的属性,并不是从父类继承,而在新版中,objc_class是继承objc_object,我们看到的isa也是来自于父类objc_object,由此可以得出,类也是一种类对象。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }

    //下面还有一些定义的函数方法,因为太占空间,跟本文无太大关系,所以就省略了
  .
  .
  .
};
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

2.类的属性以及方法存储

我们知道NSObject在底层都会被编译成objc_object的结构体,那么我们在NSObject定义的方法是存在哪里呢?通过类的结构定义,我们可以看到,objc_object内有
1.Class ISA;
2.Class superclass;
3.cache_t cache;
4.class_data_bits_t bits;

看过苹果官方文档的同学应该已经知道,isa是一种关联对象和类的指针,superclass是继承关系,剩下cache_t和class_rw_t,类的属性和方法的存储就只有可能存在这2个地方。我们继续阅读源码,点进去看cache_t发现这是一个结构体,我们继续看cache_t里的属性,发现bucket_t是一个字节为8的结构体指针,mask_t是字节为4的uint32_t类型的值,那么cache_t所占内存为16个字节,我们可以知道类的属性和方法是不可能存在cache_t里,那么,我们可以猜测一下,类的属性和方法存储在bits里。

struct cache_t {
    struct bucket_t *_buckets;   //8个byte
    mask_t _mask;
    mask_t _occupied;

};
typedef uint32_t  mask_t;    //4个byte

1.类的属性的存储

我们先创建一个LGPerson类,继承NSObject:

@interface LGPerson : NSObject{
    NSString *lastName;
}

@property (nonatomic, copy)NSString *firstName;


-(void)goodStudy;

+(void)daydayUp;

调试断点代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        LGPerson *person = [LGPerson alloc];
       
        Class cls  = object_getClass(person);
        NSLog(@"preson ==== %@",person);

    }
    return 0;
}

我们直接p/x 打印出当前cls的地址

(lldb) p/x cls
(Class) $2 = 0x0000000100002668 LGPerson

通过上面类的结构知道,cls里有isa,superclass,catch_t以及bit,其中isa和superclass占8个byte,cache_t占16个byte,那么bit需要$2偏移32个字节才能得到。

(lldb) p 0x0000000100002688
(long) $3 = 4294977160
p (class_data_bits_t *)0x0000000100002688
(class_data_bits_t *) $5 = 0x0000000100002688

通过指针偏移我们得到0x0000000100002688,直接打印发现打印不出来,我们通过源码知道0x0000000100002688是class_data_bits_t的指针,于是我们强转一下打印出来得到$5。

class_rw_t *data() { 
        return bits.data();
    }

class_rw_t中存储了类的属性和方法,我们看到class_rw_t是返回bits.data()这一函数所取的指针,所以我们用$5调用一下bits.data方法,直接取值得到class_rw_t。

(lldb) p $5->data()
(class_rw_t *) $6 = 0x000000010181f850
(lldb) p *$6
(class_rw_t) $7 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002558
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002490
        arrayAndFlag = 4294976656
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100002540
        arrayAndFlag = 4294976832
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000000000000
}

通过打印,我们很轻松找到了一个熟悉的词properties,这是一个property_array_t类型的二维数组,我们一层一层打印,最终在里面找到了firstName,由此也证实了属性存在rw里。

(lldb) p $7.properties
(property_array_t) $8 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100002540
      arrayAndFlag = 4294976832
    }
  }
}
(lldb) p $8.list
(property_list_t *) $9 = 0x0000000100002540
(lldb) p *$9
(property_list_t) $10 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "firstName", attributes = "T@\"NSString\",C,N,V_firstName")
  }
}

或者
(lldb) p $9->first
(property_t) $12 = (name = "firstName", attributes = "T@\"NSString\",C,N,V_firstName")

2.类的成员变量的存储

(lldb) p/x cls
(Class) $0 = 0x0000000100002668 LGPerson
(lldb) p (class_data_bits_t *)0x0000000100002688
(class_data_bits_t *) $1 = 0x0000000100002688
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100f3a9d0
(lldb) p *$2
(class_rw_t) $3 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002558
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002490
        arrayAndFlag = 4294976656
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000100002540
        arrayAndFlag = 4294976832
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSDate
  demangledName = 0x0000000000000000
}

通过runtime我们知道object_getIvars是获取成员变量的API,但是我们没有在rw里看到ivar,于是我们探索一下ro,看看有没有我们想要的ivar。

(lldb) p $3.ro
(const class_ro_t *) $4 = 0x0000000100002558
(lldb) p *$4
(const class_ro_t) $5 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001d9e "\x02"
  name = 0x0000000100001d95 "LGPerson"
  baseMethodList = 0x0000000100002490
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000024f8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100002540
}

功夫不负有心人,我们看到了我们想要的ivars,继续走下去,我们最终看到了我们想看到的。

(lldb) p $5.ivars
(const ivar_list_t *const) $6 = 0x00000001000024f8
(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000025e0
      name = 0x0000000100001e62 "lastName"
      type = 0x0000000100001ece "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $7.get(1)
(ivar_t) $8 = {
  offset = 0x00000001000025e8
  name = 0x0000000100001e6b "_firstName"
  type = 0x0000000100001ece "@\"NSString\""
  alignment_raw = 3
  size = 8
}

至此,我们可以得出一个结论,属性存储在rw里,成员变量存储在ro里。

3.对象方法的存储

我们在查找属性的时候,在rw里发现了有methods这样一个数组,很容易就会联想到,方法是存在这里。我们直接接着上面进行lldb调试打印,

(lldb) p $3.methods
(method_array_t) $9 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002490
      arrayAndFlag = 4294976656
    }
  }
}
(lldb) p $9.list
(method_list_t *) $10 = 0x0000000100002490
(lldb) p *$10
(method_list_t) $11 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4
    first = {
      name = "goodStudy"
      types = 0x0000000100001e76 "v16@0:8"
      imp = 0x0000000100001be0 (LGTest`-[LGPerson goodStudy] at LGPerson.m:12)
    }
  }
}

我们在method_list_t里发现了第一个方法就是我们定义的对象方法,然后我们又看到count=4,于是可以猜想,会不会类方法在剩下的里面呢?于是我们将剩下的方法通通打印出来,

(lldb) p $11.get(0)
(method_t) $12 = {
  name = "goodStudy"
  types = 0x0000000100001e76 "v16@0:8"
  imp = 0x0000000100001be0 (LGTest`-[LGPerson goodStudy] at LGPerson.m:12)
}
(lldb) p $11.get(1)
(method_t) $13 = {
  name = ".cxx_destruct"
  types = 0x0000000100001e76 "v16@0:8"
  imp = 0x0000000100001cb0 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:10)
}
(lldb) p $11.get(2)
(method_t) $14 = {
  name = "firstName"
  types = 0x0000000100001e7e "@16@0:8"
  imp = 0x0000000100001c40 (LGTest`-[LGPerson firstName] at LGPerson.h:16)
}
(lldb) p $11.get(3)
(method_t) $15 = {
  name = "setFirstName:"
  types = 0x0000000100001e86 "v24@0:8@16"
  imp = 0x0000000100001c70 (LGTest`-[LGPerson setFirstName:] at LGPerson.h:16)
}
(lldb) p $11.get(4)
Assertion failed: (i < count), function get, file /Users/isoftstone/Desktop/005-源码流程跟踪/runtime/objc-runtime-new.h, line 117.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

通过lldb调试,我们可以看到第一个方法是我们定义的对象方法,第二个方法是系统的c++方法,第三个和第四个分别是firstName的getter/setter方法,打印第五个的时候我们发现,数组越界了,并没有找到我们的类方法。

4.类方法的存储

通过对rw和ro的探索,我们知道类的属性以及对象方法存储在当前类的rw里,类的成员变量存储在ro里,那么类方法呢?通过lldb调试,我们发现rw和ro并没有适合存储类方法的位置,又通过类的结构知道,isa、superclass、catch_t也不可能存储类方法,那么类方法是不是可能存在元类当中呢?于是我们可以通过lldb调试,在元类中探索一下。在ios isa的初始化&指向分析中,我们已经知道了如何获取类的元类,直接进行打印

(lldb) x/4gx cls
0x100002668: 0x001d800100002641 0x0000000100aff140
0x100002678: 0x00000001003a2290 0x0000000000000000
(lldb) p 0x001d800100002641 & 0x00007ffffffffff8
(long) $1 = 4294977088
(lldb) p $1
(long) $1 = 4294977088
(lldb) po $1
LGPerson

我们以及得到元类$1,接下来按照对象的存储的探索方法进行lldb调试

(lldb) p/x $1
(long) $1 = 0x0000000100002640
(lldb) p (class_data_bits_t *)0x0000000100002660
(class_data_bits_t *) $2 = 0x0000000100002660
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100f8c770
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2685075456
  version = 7
  ro = 0x0000000100002448
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002428
        arrayAndFlag = 4294976552
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fffa2f48b28
  demangledName = 0x0000000100001d95 "LGPerson"
}
(lldb) p $4.methods
(method_array_t) $5 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002428
      arrayAndFlag = 4294976552
    }
  }
}
(lldb) p $5.list
(method_list_t *) $6 = 0x0000000100002428
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "daydayUp"
      types = 0x0000000100001e76 "v16@0:8"
      imp = 0x0000000100001c10 (LGTest`+[LGPerson daydayUp] at LGPerson.m:17)
    }
  }
}

最终,我们在元类找到了我们定义的类方法,也证实了类方法是存储在元类当中。

总结

1.万物皆对象,类也是一种类对象;

2.类的属性存储在rw里,类的成员变量存储在ro里;

3.对象方法存储在当前类的rw中,类方法存储在元类的rw中。

相关文章

  • iOS类结构:cache_t分析

    一、cache_t 内部结构分析 1.1 在iOS类的结构分析中,我们已经分析过类(Class)的本质是一个结构体...

  • iOS-底层分析之类的结构分析

    类的结构分析 本文主要分析iOS中的类以及类的结构,下面我们通过一个例子来探索类的结构 我们定义一个WPerson...

  • iOS 类的结构分析(下)

    在上一篇 iOS 类的结构分析(上) 分析了类的结构、isa 的走位以及类的内存分布(属性列表&实例方法列表),这...

  • iOS底层之cache_t探究

    我们在iOS底层之类的结构分析分析了类的内部结构,而类的C/C++底层实际是objc_class结构体,其中包含了...

  • [iOS] 类 & 类结构分析

    1. 类的分析 1.1 元类的引入 我们可能之前已经知道类其实也是一个对象,类的 isa 指针指向的是它的元类,...

  • iOS 类结构分析

    前言 通过本篇文章可以了解1.isa的走位2.类结构的分析3.什么是元类4.supclass的走位5.objc_c...

  • iOS类结构分析

    本文主要来探索一下iOS中类的结构,作为一个iOS开发者,我们有必要去了解关于类的底层知识。下面开始我们的探索。 ...

  • iOS - 类结构分析

    我们都知道,一个类可以创建多个不同实例对象,类自己也是对象(类对象),那么类在内存中会存在几份呢?看下面结果 得出...

  • iOS 类的结构分析

    在谈及面向对象编程的时候,总是离不开 对象 与 类 。对象 是对客观事物的抽象,类 是对 对象 的抽象。它们的关系...

  • iOS 类的结构分析

    1. 类的初探 在isa结构解析中,自定义LSPerson 类继承自NSObject,重写成C++代码如下 str...

网友评论

    本文标题:ios 类的结构分析

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