ios 类的结构分析

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中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容