美文网首页
类原理探索(objc_class)

类原理探索(objc_class)

作者: Nulll | 来源:发表于2021-06-20 22:49 被阅读0次
人活一世只有不断的去学习才不会被社会淘汰

前言

上一片我们用LLDB和源码探索类的底层实现,这篇文章主要补充一下上一篇以及一些问题,这里先留几个问题。

1、类在内存中会存放几份;
2、成员变量、实例变量和熟悉的关系;
3、为什么我们在 class_rw_t 里面打印的 firstSubclassnil
4、isKindOfClassisMemberOfClass 的区别;

我们先准备几个类信息,这里笔者就只给出头文件。

//1、CDPerson类
@interface CDPerson : NSObject
{
    NSString *_nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (void)sayNB;
+ (void)sayHello;

@end

//2、CDPerson 的扩展
@interface CDPerson (CateA)

@property (nonatomic, strong, nonnull) NSString *hobby;
@property (nonatomic, assign) CGFloat height;

- (void)sing;
+ (void)run;

@end

//3、子类CDTeacher
@interface CDTeacher : CDPerson

@property (nonatomic, copy) NSString *tName;

@end

//4、子类CDStudent
@interface CDStudent : CDPerson

@property (nonatomic, copy) NSString *num;

- (void)doStudy;
+ (void)playBall;

@end

类在内存中会存在几份

1、由于类在地城c/c++的实现也是对象;struct objc_class : objc_object 这个定义可以看出。由于每个对象都需要申请才有,类只会申请一次所以类对象在内存中的存在只有一份。
2、用例子证明:

    CDPerson *p1 = [CDPerson alloc];
    CDPerson *p2 = [CDPerson alloc];
    NSLog(@"%p", object_getClass(p1));
    NSLog(@"%p", [CDPerson class]);
    NSLog(@"%p", p1.class);
    NSLog(@"%p", p2.class);
    NSLog(@"%p", [CDPerson class]);

其他打印结果为:
0x1000084f8
0x1000084f8
0x1000084f8
0x1000084f8
0x1000084f8

通过我们验证得出,内在内存中存储的地址只有一份。

成员变量、实例变量和属性

1、成员变量:和结构体下面的变量是一模一样的,只要还原成 c/c++ 后又这么一个变量,那么这个变量就是成员变量。
2、实例变量:实例变量是一种特殊的成员变量,是由于对象类型的成员变量,即除开基本数据类型和 NSString 的成员变量都是实例变量。
3、属性:属性是由成员变量和 setter/getter 方法构成的。如果是只读属性就只有 getter 方法 。

@interface CDTest : NSObject
@end
@interface CDTest2 : NSObject
{
    NSInteger _a;   //成员变量
    CDTest *_b;     //实例变量(特殊的成员变量)
    id _c;          //实例变量(由于id 是 _objc_object* 类型)
}
@property(nonatomic, copy)NSString *d;
@end

属性的修饰符,copy、strong 的区别。我们都知道对属性的setter/getter 就是进行赋值和取之 的操作,那到底是怎么设计的呢?那我们设计一份源码编译来看看结果。

@interface CDTest2 : NSObject
{
    NSInteger _a;   //成员变量
    CDTest *_b;     //实例变量(特殊的成员变量)
    id _c;          //实例变量(由于id 是 _objc_object* 类型)
}
@property(nonatomic, copy)NSString *d;
@property(nonatomic, strong) NSString *e;
@property(nonatomic) NSString *f;
@property(copy) NSString *g;
@property(strong) NSString *h;

@end

编译成c++ 源码后为如下所示。

struct CDTest2_IMPL {
    struct NSObject_IMPL NSObject_IVARS;    // isa
    NSInteger _a;
    CDTest *_b;
    id _c;
    NSString *_d;
    NSString *_e;
    NSString *_f;
    NSString *_g;
    NSString *_h;
};

然后是实现文件的源码

//属性d 的getter的源码
static NSString * _I_CDTest2_d(CDTest2 * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_CDTest2$_d)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// 属性d 的setter方法的源码
static void _I_CDTest2_setD_(CDTest2 * self, SEL _cmd, NSString *d) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct CDTest2, _d), (id)d, 0, 1); }
//属性e的getter/setter方法
static NSString * _I_CDTest2_e(CDTest2 * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_CDTest2$_e)); }
static void _I_CDTest2_setE_(CDTest2 * self, SEL _cmd, NSString *e) { (*(NSString **)((char *)self + OBJC_IVAR_$_CDTest2$_e)) = e; }
//属性f的getter/setter
static NSString * _I_CDTest2_f(CDTest2 * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_CDTest2$_f)); }
static void _I_CDTest2_setF_(CDTest2 * self, SEL _cmd, NSString *f) { (*(NSString **)((char *)self + OBJC_IVAR_$_CDTest2$_f)) = f; }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);
//属性g的getter/setter
static NSString * _I_CDTest2_g(CDTest2 * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct CDTest2, _g), 1); }
static void _I_CDTest2_setG_(CDTest2 * self, SEL _cmd, NSString *g) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct CDTest2, _g), (id)g, 1, 1); }
//属性h的getter/setter
static NSString * _I_CDTest2_h(CDTest2 * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_CDTest2$_h)); }
static void _I_CDTest2_setH_(CDTest2 * self, SEL _cmd, NSString *h) { (*(NSString **)((char *)self + OBJC_IVAR_$_CDTest2$_h)) = h; }

可以发现在上面的一堆方法里面定义了两个特殊的方法 objc_setProperty 和 objc_getProperty。

可以看出,copy 修饰的属性setter 方法都是用objc_setProperty 实现的,而strong 就是直接对地址赋值。这也就是copy 和 strong 的最大的区别。对于objc_setProperty 方法在底层都是 reallySetProperty 这个方法的实现

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }
///这里就是处理原子和非原子的操作
    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

对于 objc_getProperty 这个方法,尝试了很多 属性得出在copy的基础上如果是 atomic 才会使用这个方法,否者就是直接通过内存偏移直接取值。

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

:如果每个属性都编译成一份setXxx 和 getXxx 的形式,系统底层必然是无法实现的,因为每个开发者写的东西不一样而且很多。所以在这里就有一个中间封装的操作,把所有的成员变量的相应的值按照参数进行传递。

为什么我们在 class_rw_t 里面打印的 firstSubclass 为nil

编写如下一段代码和调试流程

CDPerson *person = [CDPerson alloc];
person.name = @"CD";

//LLDB 的调试
(lldb) p/x CDPerson.class
(Class) $1 = 0x00000001000084f8 CDPerson
(lldb) x/6gx 0x00000001000084f8
0x1000084f8: 0x00000001000084d0 0x0000000100375140
0x100008508: 0x0001000100875110 0x0002802900000000
0x100008518: 0x00000001008750d4 0x00000001000998e0
(lldb) p (class_data_bits_t *)0x100008518
(class_data_bits_t *) $2 = 0x0000000100008518
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001008750d0
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000736
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

这里我们可以看到 firstSubclassnil
接下来我们在实例化一个对象 CDStudent *stu = [CDStudent alloc];然后再来调试一下。

(lldb) p *$3
(class_rw_t) $5 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000736
    }
  }
  firstSubclass = CDStudent
  nextSiblingClass = NSUUID
}

可以看到为CDStudent
然后我们在实例化一个 CDTeacher 对象 CDTeacher *tea = [CDTeacher alloc]; 然后再来调试。

(lldb) p *$3
(class_rw_t) $6 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000736
    }
  }
  firstSubclass = CDTeacher
  nextSiblingClass = NSUUID
}

很明显,结果变成了CDTeacher。然后从源码里面发现了这样一段代码。

/***********************************************************************
* addSubclass
* Adds subcls as a subclass of supercls.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void addSubclass(Class supercls, Class subcls) 
{
        objc_debug_realized_class_generation_count++;
        subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
        supercls->data()->firstSubclass = subcls;
......
}

结论:firstSubclass 为某个类第一次创建的时候去寻找父类的时候绑定给父类的。


类的扩展信息存储在什么地方

前面一篇文章讨论了类的结构和元类,以及类中的属性,成员变量和方法存储位置。
那么我们的扩展里面的属性,方法又是怎么存储的呢?我们还是结合 LLDB和源码调试

  1. 我们还是按照之前的操作 打印出 class_rw_t 的结构。
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295003112
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
  1. 打印出方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100003428
      }
      arrayAndFlag = 4294980648
    }
  }
}
(lldb) p $4.list.ptr
(method_list_t *const) $5 = 0x0000000100003428
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 2147483660, count = 14)
}
  1. 打印出所有的方法
(lldb) p $6.get(0).name
(SEL) $11 = "hobby"
  Fix-it applied, fixed expression was: 
    $6.get(0).name()
(lldb) p $6.get(1).name
(SEL) $12 = "setHobby:"
  Fix-it applied, fixed expression was: 
    $6.get(1).name()
(lldb) p $6.get(2).name
(SEL) $13 = "height"
  Fix-it applied, fixed expression was: 
    $6.get(2).name()
(lldb) p $6.get(3).name
(SEL) $14 = "setHeight:"
  Fix-it applied, fixed expression was: 
    $6.get(3).name()
(lldb) p $6.get(4).name
(SEL) $15 = "sing"
  Fix-it applied, fixed expression was: 
    $6.get(4).name()
(lldb) p $6.get(5).name
(SEL) $16 = "sayNB"
  Fix-it applied, fixed expression was: 
    $6.get(5).name()
(lldb) p $6.get(6).name
(SEL) $17 = "p_method"
  Fix-it applied, fixed expression was: 
    $6.get(6).name()
(lldb) p $6.get(7).name
(SEL) $18 = "p_name"
  Fix-it applied, fixed expression was: 
    $6.get(7).name()
(lldb) p $6.get(8).name
(SEL) $19 = "setP_name:"
  Fix-it applied, fixed expression was: 
    $6.get(8).name()
(lldb) p $6.get(9).name
(SEL) $20 = "name"
  Fix-it applied, fixed expression was: 
    $6.get(9).name()
(lldb) p $6.get(10).name
(SEL) $21 = "setName:"
  Fix-it applied, fixed expression was: 
    $6.get(10).name()
(lldb) p $6.get(11).name
(SEL) $22 = "age"
  Fix-it applied, fixed expression was: 
    $6.get(11).name()
(lldb) p $6.get(12).name
(SEL) $23 = "setAge:"
  Fix-it applied, fixed expression was: 
    $6.get(12).name()
(lldb) p $6.get(13).name
(SEL) $24 = ".cxx_destruct"
  Fix-it applied, fixed expression was: 
    $6.get(13).name()

可以看到,扩展的方法在最前面,这也表明了我们的同名方法扩展的优先级别会高于元类的方法。

  1. 同理我们获取所有的属性
(lldb) p $3.properties()
(const property_array_t) $7 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008080
      }
      arrayAndFlag = 4295000192
    }
  }
}
(lldb) p *($7.list.ptr)
(property_list_t) $8 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 9)
}

然后一次打印

(lldb) p $8.get(0)
(property_t) $28 = (name = "hobby", attributes = "T@\"NSString\",&,N")
(lldb) p $8.get(1)
(property_t) $29 = (name = "height", attributes = "Td,N")
(lldb) p $8.get(2)
(property_t) $30 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(3)
(property_t) $31 = (name = "age", attributes = "Tq,N,V_age")
(lldb) p $8.get(4)
(property_t) $32 = (name = "p_name", attributes = "T@\"NSString\",C,N,Vp_name")
(lldb) p $8.get(5)
(property_t) $33 = (name = "hash", attributes = "TQ,R")
(lldb) p $8.get(6)
(property_t) $34 = (name = "superclass", attributes = "T#,R")
(lldb) p $8.get(7)
(property_t) $35 = (name = "description", attributes = "T@\"NSString\",R,C")
(lldb) p $8.get(8)
(property_t) $36 = (name = "debugDescription", attributes = "T@\"NSString\",R,C")

也可以看到,属性的也是扩展的在前面。

结论:扩展里面的属性和方法的优先级会高于原类的属性方法。


isKindOfClass 和 isMemberOfClass 的区别

这两个问题应该是面试的一个比较高频的问题了,那下面这个你能准确的回答出来么?

NSObject *obj = [NSObject alloc];
    CDPerson *person = [CDPerson alloc];
    BOOL ret1 = [obj isKindOfClass:[NSObject class]];
    BOOL ret2 = [obj isMemberOfClass:[NSObject class]];
    
    BOOL ret3 = [person isKindOfClass:[NSObject class]];
    BOOL ret4 = [person isKindOfClass:[CDPerson class]];
    
    BOOL ret5 = [person isMemberOfClass:[NSObject class]];
    BOOL ret6 = [person isMemberOfClass:[CDPerson class]];
    
    NSLog(@"****** \n %d - %d - %d - %d - %d - %d \n******", ret1, ret2, ret3, ret4, ret5, ret6);
    
    BOOL ret7 = [CDPerson isKindOfClass:[NSObject class]];
    BOOL ret8 = [CDPerson isKindOfClass:[CDPerson class]];
    
    BOOL ret9 = [CDPerson isMemberOfClass:[NSObject class]];
    BOOL ret10 = [CDPerson isMemberOfClass:[CDPerson class]];
    
    NSLog(@"****** \n %d - %d - %d - %d \n******",ret7, ret8, ret9, ret10);

打印的结果为 1 - 1 - 1 - 1 - 0 - 11 - 0 - 0 - 0
我们去查看底层源码的实现,结果为下面这样。

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

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

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

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

结合我们上篇文章的内容,无非就是成员方法的 isa 和 类方法的 isa 的问题。
可是结果真的就如同上面的源码一样么?我们如下打断点调试,到底是怎么走的呢?

端点调试
通过断电调试我们发现:isKindOfClass 压根就不走断点,只有 isMemberOfClass 才会走断点。这里就很奇怪了,为什么 isKindOfClass 不走断点呢?是底层或者编译器做了什么操作么?接下来我们就从汇编的角度来看看到底是什么原因。。

从汇编看函数

先看几个函数

1、+/- class]
2、isKindOfClass:
3、isMemberOfClass:

  • class 方法
    我们只看源码就是 + (Class)class { return self; },也的确源码就是如此实现的,那咱么到汇编去看看,到底是如何调用的呢?结果为如下一段代码。
    class 的汇编实现

从这个方法我们可以看出 +/- class 这个方法调用的 objc_opt_class 这个方法?那咱们搜索一下这个方法的实现。

objc_opt_class 的实现

通过源码可以发现:

  • isKindOfClass
    通过汇编可以看到 isKindOfClass 的汇编是调用的 objc_opt_isKindOfClass 这个方法。
    isKindOfClass 的汇编
    然后全局搜索一下这个方法,可以找到对应的实现。
    objc_opt_isKindOfClass 的实现
    这里我们再来分析一下下面这个方法 BOOL ret8 = [CDPerson isKindOfClass:[CDPerson class]];的原理。

1、由于这里的objCDPerson 类,所以 obj->getIsa() = 元类(CDPerson ), 虽然这里都是CDPerson 但是他们不是一个是类,一个是元类,所以不相等;
2、CDPersonsuperClassNSObject,所以NSObjectgetIsa()还是 NSObject;而otherClassCDPerson,所以第二次循环依然为 false;
3、由于 NSObject 是根类,所以 getSuperclass()nil,结束循环,直接返回 false。

  • isMemberOfClass
    isMemberOfClass 的汇编
    通过汇编我们发现 isMemberOfClass 的底层实现就是 objc_msgSend;所以直接去找源码,而上面的断点调试也是可以知道 isMemberOfClass确实走了断点。
    ret6 结果分析
    再看 BOOL ret5 = [person isMemberOfClass:[NSObject class]]; 这个

1、selfperson 对象,所以 classCDPerson(类);
2、[NSObject class] 获取得到的为 NSObject
3、CDPerson 不等于 NSObject,所以结果为:false

结论:这里分析了一部分结果,对于其他的结果,读者可以依据笔者的思路去分析其他的结果。但是无非就是 isa 的 super 的 关系而已。

附一张isa/super 流程图


isa流程图.png

相关文章

网友评论

      本文标题:类原理探索(objc_class)

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