美文网首页
OC类的初探

OC类的初探

作者: Nulll | 来源:发表于2021-06-17 21:42 被阅读0次

前言

每个对象都会有一个它所属的类,这是面向对象的基本概念。但是在OC中,这对所有数据结构有效。任何数据结构,只要在恰当的位置具有一个指针指向一个Class,那么,它都可以被认为是一个对象。
在之前的文章中,笔者也简单分析了一下OC对象的原理,那么带着同样的问题我们来反洗一下什么是类,这里先抛出几个问题:

什么是类,元类;
OC类的本质是什么;
OC类里面有什么成员变量;
类里面方法、协议、 扩展优势如何存储的;

在之前对象的分析过程中,我们通过LLDB的一些命令(比如:x/4gx p)获取了一个对象的内存结构,然后通过和ISA_MASK取与(&)操作得到了我们的类。
那么在这里有没有这样一个问题,如果我再对这个类进行相同的操作会有什么样的结果呢?


类的探究分析

LLDB调试

  1. 那我们按照对象的思路继续探索类的下面是什么,依然先创建一个类CDPerson
@interface CDPerson : NSObject
{
    NSString *_nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
- (void)sayNB;
+ (void)sayHello;
@end

@implementation CDPerson
- (void)sayNB {
}
+ (void)sayHello {   
}
@end

//然后初始化一下
CDPerson *person = [CDPerson alloc];
按照对象的思路去打印相关的isa的内存地址

从上图可以看到,$1$3 虽然都是 CDPerson,但是内存地址确实截然不同的。那到底哪个才是我们的 实例对象 person 的类呢?
这里我们用结果来说明。

    CDPerson *p = [CDPerson alloc];
    Class cls1 = p.class;
    Class cls2 = object_getClass(p);
    Class cls3 = [CDPerson class];
    Class cls4 = [[CDPerson alloc] class];
    Class cls5 = [CDPerson class];
    
    NSLog(@"\ncls1-%p\ncls2-%p\ncls3-%p\ncls4-%p\ncls5-%p\n", cls1, cls2, cls3, cls4, cls5);

//结果如下
cls1-0x1000083a0
cls2-0x1000083a0
cls3-0x1000083a0
cls4-0x1000083a0
cls5-0x1000083a0    

从这个结果验证来看,0x00000001000083a0 才是我们的 实例对象 person 的类,那么这个 0x0000000100008378 又是什么那?
然而我们通过打印 NSObject 的类

p/x [NSObject class]
(Class) $5 = 0x00000001efaa4f48 NSObject

很明显这个 $3 也不是 NSObject

  1. 这里借助一个二进制分析工具 MachOView 分析工具分析一些我们的可执行文件,里面也可以很直观的看到谁才是我们的 CDPerson 类。
    CDPerson 类的真正地址
OBJC_METACLASS

同理,CDStudent 也是一样的

CDStudent 的类和原类
  1. 小结:到这里我们就得到一个结论 对象 --isa--> 类类 --isa--> 元类
    既然上面都有了原类的概念了,那么我们的元类的 isa 又该指向谁呢。这里还是用一个例子来解释。
    Class cls1 = [CDPerson class];
    Class metaCls1 = object_getClass(cls1);
    Class rootCls1 = object_getClass(metaCls1);
    Class rootRootCls1 = object_getClass(rootCls1);
    ///都知道,NSObject 就是我们的根类
    Class cls2 = [NSObject class];
    Class cls3 = object_getClass(cls2);
    Class cls4 = object_getClass(cls3);
    
    NSLog(@"\n类:%p \n元类:%p \n根元类:%p \n根根元类%p \n根类:%p \n根元类:%p \n根根元类:%p", cls1, metaCls1, rootCls1, rootRootCls1, cls2, cls3, cls4);

打印的结果如下:


打印

从这个打印也可以得出结论:

非根类的isa 指向元类,然后元类的isa指向根元类,而根元类的isa依然指向根元类;
根类的isa 指向根元类;

更具这个结论就得到如下的一张isa 的指向图。


isa 指向图

源码分析

那么接下来我们就看看源码到底是如何定义的。

//这里可以看到我们平时的Class 就是一个  objc_class 的结构体指针
typedef struct objc_class *Class;

//这里可以知道,objc_class 也是继承自我们的 objc_object,所以类也是一个对象(类对象)
struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;

//这儿是一个隐藏参数isa,来自objc_object 这个结构体的唯一成语啊变量。
    // Class ISA;

//这里有一个指针(Class)的superclass,从之前的类的内存问题也可以看到,第一个8字节是isa,第二个8字节是个NSObject 之类的。
    Class superclass;   
    cache_t cache;             // formerly cache pointer and vtable 
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
......
//此处省略一堆方法

}

从上面的源码定义里面就可以知道,我们的类也是一个对象,只是里面比对象多了几个成员变量 superclasscachebits;那接下来我们就来看看 cachebits 到底是什么。

  1. superclass
    这个就是指向父类的指针。

  2. cache (cache_t)
    通过分析 cache_t de 结构,我们可以得出 cache_t 这个占用的内存空间为16字节。所以按照内存平移的规则我们可以知道class_data_bits_t 和类首地址之间的差距是32 字节(isa:8字节,superclass:8字节,cache:16字节)。

  3. bits(class_data_bits_t)
    在之前对象里面我们也有一个 bits 的成员变量;

typedef unsigned long           uintptr_t;
uintptr_t bits;

但是类里面的 bits 和 对象里面的 bits 不是一个概念。
先来看看 class_data_bits_t 这个结构

struct class_data_bits_t {
 
//友元类:C++中的friend关键字其实做这样的事情:在一个类中指明其他的类(或者)函数能够直接访问该类中的private和protected成员。
 friend objc_class; 
    // Values are the FAST_ flags above.
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) const
    {
        return bits & bit;
    }
......

public: 
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
......
}

可以看到,在里面有一个 bits 成员(Values are the FAST_ flags above),还有一个 class_rw_t* 返回值的 data() 方法。
再看看 class_rw_t 这个结构的定义

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
   ......
public:
    ......
//
const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }
// 返回值为 method_array_t 的一个方法列表
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }
// 属性列表
    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }
// 协议列表
    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }
}

然后通过 LLDB 调试一下这个 class_data_bits_t 这个数据类型。看看里面到底是不这样。

  • 首先:通过 LLDB 找到元类

    LLDB打印元类
  • 打印 class_data_bits_t 数据类型

(lldb) p (class_data_bits_t *)0x00000001007277f4
(class_data_bits_t *) $4 = 0x00000001007277f4
  • 获取 class_rw_t 并且查看具体内容
p $4->data()
(class_rw_t *) $5 = 0x0000000101a0d1f0

(lldb) p *$5
(class_rw_t) $6 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000920
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
  • 查看 class_rw_t 的相成员

    class_rw_t 的属性、方法和协议
  • 继续查看一个属性到底是不是我们定义的

(lldb) p $7.list.ptr
(property_list_t *const) $8 = 0x0000000100008488
(lldb) p *$8
(property_list_t) $9 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p *($8)
(property_list_t) $10 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}

(lldb) p $10.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $10.get(1)
(property_t) $10 = (name = "age", attributes = "Tq,N,V_age")

从上面的调试结果来看,的确也得到了我们的属性,和相关的编码等都可以看到
同理也可以查看方法、和协议等


类的继承关系

上面我们分析了 isa 的问题,那么继承的问题呢?接下来我们就来分析一下继承的问题。
...... 待完善,先放一张结论图

类的继承关系图

类方法,ivar 在哪里

成员变量ivar

从上面的 properties() 里面我们发现这个CDPerosn 类只有两个,那么我们的成员变量又在什么地方呢?
于是乎我们在当前文件里面搜索了一下 ivars,发现了在 class_ro_t 里面有这么一个定义 const ivar_list_t * ivars;

struct class_ro_t {
...... 
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
......
}

然后在回到我们的 class_rw_t 里面去发现了这么一段定义

 const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }

于是乎,按照 properties() 相同的思路去调试

ro的结构
然后继续打印看结果,果然我们的成员变量和属性对应的成员变量都在这里。
(lldb) p $15.ivars
(const ivar_list_t *const) $16 = 0x0000000100008420
(lldb) p $16.get(0)
(ivar_t) $17 = {
  offset = 0x00000001000084f8
  name = 0x0000000100003eaf "_nickName"
  type = 0x0000000100003f6e "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  Fix-it applied, fixed expression was: 
    $16->get(0)
(lldb) p $16.get(1)
(ivar_t) $18 = {
  offset = 0x0000000100008500
  name = 0x0000000100003e77 "_name"
  type = 0x0000000100003f6e "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  Fix-it applied, fixed expression was: 
    $16->get(1)

类方法

因为前面我们分析了,类方法存在元类里面,所以我们就先拿到我们的当前类的元类在去分析。

  1. 通过 LLDB 获取元类的 class_rw_t

    获取元类的class_rw_t
  2. 通过 class_rw_t 结构,获取方法列表。

(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008338
      }
      arrayAndFlag = 4295000888
    }
  }
}
(lldb) p $5.list.ptr
(method_list_t *const) $6 = 0x0000000100008338
(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)
(method_t) $8 = {}
(lldb) p $8.big
(method_t::big) $9 = {
  name = "sayHello"
  types = 0x0000000100003f66 "v16@0:8"
  imp = 0x0000000100003d10 (KCObjcBuild`+[CDPerson sayHello])
}
  Fix-it applied, fixed expression was: 
    $8.big()

总结

通过上面的分析和调试,我们得出如下结论:

1、实例变量(对象)的 isa 指向 class
2、类 Class 也是一个对象;
3、类的 isa 指向元类;
4、类里面存放了属性、成员变量、实例方法、协议等;
5、类方法存放在元类列面,和对象与类的关系一模一样。

最直观的就是下面这张图


isa流程图

补充

friend 友元类

对于一个没有定义public访问权限的类,能够让其他的类操作它的私有成员往往是有用的。例如你写了一段binary tree的代码,Node是节点类,如果能够让连接多个节点的函数不需要调用public方法就能够访问到Node的私有成员的话,一定是很方便的。

C++中的friend关键字其实做这样的事情:在一个类中指明其他的类(或者)函数能够直接访问该类中的private和protected成员。

struct str_a {
    
    int area_length;
    
    struct str_b *sb;
    
    int testFunc();
    
};

struct str_b {
    //如果这里没有这个friend,那么str_a 的sb 成员无法访问private 成员变量。
    friend str_a;
    
private:
    int age;
    
public:
    double height;
};

int str_a::testFunc() {
    
    if (sb->age > 10) {
        return 20;
    }
    return 10;
}

相关文章

网友评论

      本文标题:OC类的初探

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