类的原理分析(下)

一. 成员变量

类的原理分析(上)通过LLDB调试打印了方法属性协议,它们都存储在类的bits变量所指向的内存区

class_data_bits_t *bits

通过调用bits->data()方法获得class_rw_t的指针,通过这个指针分别调用properties()methods()protocols(),获得属性列表,方法列表和协议列表。但是成员变量iVars和类的方法(+前缀修饰的方法)却没有找到。

成员变量和属性的区别是,属性是由下划线+属性名的成员变量,setter和getter方法组成。
下面通过LLDB调试来打印一下iVars

(lldb) p/x LGPerson.class
(Class) $29 = 0x0000000100008720 LGPerson

(lldb) p (class_data_bits_t*)0x0000000100008740
(class_data_bits_t *) $30 = 0x0000000100008740

(lldb) p $30->data()
(class_rw_t *) $31 = 0x0000000101d053d0

(lldb) p *$31
(class_rw_t) $32 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000360
    }
  }
  firstSubclass = LGTeacher
  nextSiblingClass = NSUUID
}

(lldb) p $32.ro()
(const class_ro_t *) $33 = 0x0000000100008128

(lldb) p *$33
(const class_ro_t) $34 = {
  flags = 0
  instanceStart = 8
  instanceSize = 32
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "LGPerson" {
      Value = 0x0000000100003ea8 "LGPerson"
    }
  }
  baseMethodList = 0x0000000100008170
  baseProtocols = 0x0000000100008220
  ivars = 0x0000000100008238
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000082a0
  _swiftMetadataInitializer_NEVER_USE = {}
}

(lldb) p $34.ivars
(const ivar_list_t *const) $35 = 0x0000000100008238

(lldb) p *$35
(const ivar_list_t) $36 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}

(lldb) p $36.get(0)
(ivar_t) $37 = {
  offset = 0x00000001000086b8
  name = 0x0000000100003d39 "subject"
  type = 0x0000000100003edb "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $36.get(1)
(ivar_t) $38 = {
  offset = 0x00000001000086c0
  name = 0x0000000100003d41 "_name"
  type = 0x0000000100003edb "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $36.get(2)
(ivar_t) $39 = {
  offset = 0x00000001000086c8
  name = 0x0000000100003d47 "_hobby"
  type = 0x0000000100003edb "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) 

通过调试输出结果知道,成员变量iVars通过class_rw_t结构指针调用ro()方法,获得class_ro_t结构的指针,这个结构里有一个ivars指针,指向ivars列表。

二.类方法

从前面的分析知道,对象的方法,属性和成员变量信息存储在类里,那么类的方法信息又存在哪里呢?前面提到过对象的isa指向了类,类的isa指向元类,那么类方法是不是存在元类里呢?

(lldb) x/4gx LGPerson.class
0x100008408: 0x0000000100008430 0x000000010036a140
0x100008418: 0x0000000101456fb0 0x0002802800000007

(lldb) p/x 0x0000000100008430 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x0000000100008430

(lldb) po $1
LGPerson

(lldb) p (class_data_bits_t*)0x0000000100008450
(class_data_bits_t *) $2 = 0x0000000100008450

(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001013406f0

(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4315156257
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff8008eeb0
}

(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008340
      }
      arrayAndFlag = 4295000896
    }
  }
}

(lldb) p $5.list.ptr
(method_list_t *const) $6 = 0x0000000100008340

(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}

(lldb) p $7.get(0).big()
(method_t::big) $8 = {
  name = "sayNB"
  types = 0x0000000100003f6a "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`+[LGPerson sayNB])
}
(lldb) 

从输出结果知道,类方法+[LGPerson sayNB]的信息存在元类里

三. 类型编码

从上面方法的输出结果里,有没有注意到一个比较有意思的东西

(method_t::big) $8 = {
  name = "sayNB"
  types = 0x0000000100003f6a "v16@0:8"
  imp = 0x0000000100003d40 (KCObjcBuild`+[LGPerson sayNB])
}

nameimp都比较好理解,但types是什么呢?它的值是字符串"v16@0:8",这其实是方法的编码,编码意思如下

v表示方法返回类型是void
16表示方法总长度是16个字节
@表示一个object对象地址类型的参数
0表示object参数存储的起始位置
:表示一个SEL类型的参数
8表示SEL参数存放的起始位置

详细的类型编码

也可以用NSLog()打印出各种类型的编码

    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));

四. 属性getter和setter方法的实现

LGPerson有下面6个属性

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;

我们通过clang把OC代码翻译成c++代码

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

static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }

static NSString * _I_LGPerson_nnickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)); }
static void _I_LGPerson_setNnickName_(LGPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)) = nnickName; }

static NSString * _I_LGPerson_anickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)); }
static void _I_LGPerson_setAnickName_(LGPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)) = anickName; }

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }

static NSString * _I_LGPerson_aname(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)); }
static void _I_LGPerson_setAname_(LGPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)) = aname; }

五个属性的getter方法都通过内存平移来访问属性的

{(*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name;}

self指针内存平移来访问属性
其中有四个属性的setter方法也是通过内存平移来访问属性的

@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;

但是两个使用了copy修饰符的属性

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;

nickNamegetter方法使用内存平移,setter使用objc_setProperty
acnickNamegetter方法使用objc_getPropertysetter方法使用objc_setProperty

//  内存平移
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); } 

//  objc_setProperty
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

//  objc_getProperty
static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }

//  objc_setProperty
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }

copy修饰符是属性方法是否调用objc_getPropertyobjc_setProperty来实现的重要依据,可以从llvm的clang的CodeGen源码看到

/// Pick an implementation strategy for the given property synthesis.
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
                                     const ObjCPropertyImplDecl *propImpl) {
  const ObjCPropertyDecl *prop = propImpl->getPropertyDecl();
  ObjCPropertyDecl::SetterKind setterKind = prop->getSetterKind();

  IsCopy = (setterKind == ObjCPropertyDecl::Copy);
  IsAtomic = prop->isAtomic();
  HasStrong = false; // doesn't matter here.

  // Evaluate the ivar's size and alignment.
  ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
  QualType ivarType = ivar->getType();
  auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
  IvarSize = TInfo.Width;
  IvarAlignment = TInfo.Align;

  // If we have a copy property, we always have to use getProperty/setProperty.
  // TODO: we could actually use setProperty and an expression for non-atomics.
  if (IsCopy) {
    Kind = GetSetProperty;
    return;
  }
...
}

五. isKindOfClassisMemberOfClass分析

void lgKindofDemo(void){
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //  YES:根元类的super class是NSObject.class
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //  NO: 元类不等于类
    BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //  NO: 元类的继承链没有LGPerson.class
    BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //  NO: 元类不等于类
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //  YES:实例的类继承链中有NSObject.class
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //  YES:实例的类类等于NSObject.class
    BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //  YES:实例的类继承链中有LGPerson.class
    BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //  YES:实例的类类等于LGPerson.class
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

输出结果

 re1 :1
 re2 :0
 re3 :0
 re4 :0
 re5 :1
 re6 :1
 re7 :1
 re8 :1
(lldb) 

打个断点看看汇编

KCObjcBuild`lgKindofDemo:
    0x100003910 <+0>:   pushq  %rbp
    0x100003911 <+1>:   movq   %rsp, %rbp
    0x100003914 <+4>:   pushq  %rbx
    0x100003915 <+5>:   pushq  %rax
->  0x100003916 <+6>:   movq   0x4a0b(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x10000391d <+13>:  callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x100003922 <+18>:  movq   %rax, %rbx
    0x100003925 <+21>:  movq   0x49fc(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x10000392c <+28>:  callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x100003931 <+33>:  movq   %rbx, %rdi
    0x100003934 <+36>:  movq   %rax, %rsi
    0x100003937 <+39>:  callq  0x100003d8a               ; symbol stub for: objc_opt_isKindOfClass
    0x10000393c <+44>:  movb   %al, -0x10(%rbp)
    0x10000393f <+47>:  movq   0x49e2(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x100003946 <+54>:  callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x10000394b <+59>:  movq   %rax, %rbx
    0x10000394e <+62>:  movq   0x49d3(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x100003955 <+69>:  callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x10000395a <+74>:  movq   0x49af(%rip), %rsi        ; "isMemberOfClass:"
    0x100003961 <+81>:  movq   %rbx, %rdi
    0x100003964 <+84>:  movq   %rax, %rdx
    0x100003967 <+87>:  callq  *0x693(%rip)              ; (void *)0x00000001002ea2c0: objc_msgSend
    0x10000396d <+93>:  movb   %al, -0xf(%rbp)
    0x100003970 <+96>:  movq   0x49b9(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x100003977 <+103>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x10000397c <+108>: movq   %rax, %rbx
    0x10000397f <+111>: movq   0x49aa(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x100003986 <+118>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x10000398b <+123>: movq   %rbx, %rdi
    0x10000398e <+126>: movq   %rax, %rsi
    0x100003991 <+129>: callq  0x100003d8a               ; symbol stub for: objc_opt_isKindOfClass
    0x100003996 <+134>: movb   %al, -0xe(%rbp)
    0x100003999 <+137>: movq   0x4990(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x1000039a0 <+144>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x1000039a5 <+149>: movq   %rax, %rbx
    0x1000039a8 <+152>: movq   0x4981(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x1000039af <+159>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x1000039b4 <+164>: movq   0x4955(%rip), %rsi        ; "isMemberOfClass:"
    0x1000039bb <+171>: movq   %rbx, %rdi
    0x1000039be <+174>: movq   %rax, %rdx
    0x1000039c1 <+177>: callq  *0x639(%rip)              ; (void *)0x00000001002ea2c0: objc_msgSend
    0x1000039c7 <+183>: leaq   0x642(%rip), %rdi         ; @" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n"
    0x1000039ce <+190>: movb   %al, -0xd(%rbp)
    0x1000039d1 <+193>: movsbl -0x10(%rbp), %esi
    0x1000039d5 <+197>: movsbl -0xf(%rbp), %edx
    0x1000039d9 <+201>: movsbl -0xe(%rbp), %ecx
    0x1000039dd <+205>: movsbl -0xd(%rbp), %r8d
    0x1000039e2 <+210>: movb   $0x0, %al
    0x1000039e4 <+212>: callq  0x100003d60               ; symbol stub for: NSLog
    0x1000039e9 <+217>: movq   0x4938(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x1000039f0 <+224>: callq  0x100003d66               ; symbol stub for: objc_alloc
    0x1000039f5 <+229>: movq   %rax, %rbx
    0x1000039f8 <+232>: movq   0x4929(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x1000039ff <+239>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x100003a04 <+244>: movq   %rbx, %rdi
    0x100003a07 <+247>: movq   %rax, %rsi
    0x100003a0a <+250>: callq  0x100003d8a               ; symbol stub for: objc_opt_isKindOfClass
    0x100003a0f <+255>: movb   %al, -0xc(%rbp)
    0x100003a12 <+258>: movq   0x490f(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x100003a19 <+265>: callq  0x100003d66               ; symbol stub for: objc_alloc
    0x100003a1e <+270>: movq   %rax, %rbx
    0x100003a21 <+273>: movq   0x4900(%rip), %rdi        ; (void *)0x000000010036a140: NSObject
    0x100003a28 <+280>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x100003a2d <+285>: movq   0x48dc(%rip), %rsi        ; "isMemberOfClass:"
    0x100003a34 <+292>: movq   %rbx, %rdi
    0x100003a37 <+295>: movq   %rax, %rdx
    0x100003a3a <+298>: callq  *0x5c0(%rip)              ; (void *)0x00000001002ea2c0: objc_msgSend
    0x100003a40 <+304>: movb   %al, -0xb(%rbp)
    0x100003a43 <+307>: movq   0x48e6(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x100003a4a <+314>: callq  0x100003d66               ; symbol stub for: objc_alloc
    0x100003a4f <+319>: movq   %rax, %rbx
    0x100003a52 <+322>: movq   0x48d7(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x100003a59 <+329>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x100003a5e <+334>: movq   %rbx, %rdi
    0x100003a61 <+337>: movq   %rax, %rsi
    0x100003a64 <+340>: callq  0x100003d8a               ; symbol stub for: objc_opt_isKindOfClass
    0x100003a69 <+345>: movb   %al, -0xa(%rbp)
    0x100003a6c <+348>: movq   0x48bd(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x100003a73 <+355>: callq  0x100003d66               ; symbol stub for: objc_alloc
    0x100003a78 <+360>: movq   %rax, %rbx
    0x100003a7b <+363>: movq   0x48ae(%rip), %rdi        ; (void *)0x00000001000083b0: LGPerson
    0x100003a82 <+370>: callq  0x100003d84               ; symbol stub for: objc_opt_class
    0x100003a87 <+375>: movq   0x4882(%rip), %rsi        ; "isMemberOfClass:"
    0x100003a8e <+382>: movq   %rbx, %rdi
    0x100003a91 <+385>: movq   %rax, %rdx
    0x100003a94 <+388>: callq  *0x566(%rip)              ; (void *)0x00000001002ea2c0: objc_msgSend
    0x100003a9a <+394>: leaq   0x58f(%rip), %rdi         ; @" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n"
    0x100003aa1 <+401>: movb   %al, -0x9(%rbp)
    0x100003aa4 <+404>: movsbl -0xc(%rbp), %esi
    0x100003aa8 <+408>: movsbl -0xb(%rbp), %edx
    0x100003aac <+412>: movsbl -0xa(%rbp), %ecx
    0x100003ab0 <+416>: movsbl -0x9(%rbp), %r8d
    0x100003ab5 <+421>: movb   $0x0, %al
    0x100003ab7 <+423>: callq  0x100003d60               ; symbol stub for: NSLog
    0x100003abc <+428>: addq   $0x8, %rsp
    0x100003ac0 <+432>: popq   %rbx
    0x100003ac1 <+433>: popq   %rbp
    0x100003ac2 <+434>: retq 

从汇编我们发现isKindOfClass调用objc_opt_isKindOfClassisMemberOfClass通过objc_msgSendisMemberOfClass:发送消息。我们调试到runtime源码里看看

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        //  实际运行走的这里
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// NSObject
// tcls = 根元类 父类 NSObject
// LGPerson
// 元类 -> 根元类 -> NSObject -> nil

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

// objc
// NSObject = tcls  VS NSObject

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

isKindOfClass是取到isa指向的类/元类,再以这个类/元类的继承链向上搜索。
如果调用的是对象,那么搜索的路径是类的继承链:

类 > 父类 ... > NSObject > nil

如果调用的是类,那么搜索的是元类的继承链:

元类 > 父元类 ... > 根元类 > NSObject > nil

isMemberOfClass是取到isa指向的类/元类,和传入的参数进行比较。通过对原码的分析,之前lgKindofDemo函数的输出结果就比较明了了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。