Objective-C基础之一(深刻理解OC对象)

OC对象的本质

日常咱们使用Objective-C语法来编写代码,可是它的底层其实都是C或C++代码。Objective-C实际上是在C语言的基础上增长了面向对象的特性。咱们能够经过如下命令将Objective-C代码转换成C++代码:ios

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC文件 -o 输出目标cpp文件
复制代码

若是OC文件须要连接其它的框架,可使用-framework参数:面试

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC文件 -o 输出目标cpp文件 -framework 框架名称
复制代码

与此同时,还须要下载runtime的源码,经过objc源码地址下载最新版本的objc源码,以便于后续使用。数组

OC对象的底层实现

在开发过程当中,最经常使用到的就是OC的对象。几乎全部的类对象都是NSObject的子类,可是抛开OC的限制,NSObject底层是如何实现的呢?上文说到,全部的OC代码最后都会转换成C代码,因此咱们经过一个例子来认识NSObject的底层实现。缓存

  • 首先,建立一个XLPerson对象
@interface XLPerson : NSObject

@end

@implementation XLPerson


@end
复制代码
  • 进入XLPerson.m文件所在目录,使用以下指令,将XLPerson.m文件转换成XLPerson_cpp.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XLPerson.m -o XLPerson_cpp.cpp
复制代码
  • 在生成的XLPerson_cpp.cpp文件中搜索XLPerson,就能找到如下结构体定义
struct XLPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};
复制代码

XLPerson_IMPL中包含一个结构体成员NSObject_IVARS,它是NSObject_IMPL类型,查看NSObject_IMPL的代码以下:bash

struct NSObject_IMPL {
	Class isa;
};
复制代码

由此能够看出,OC中的对象其实就是经过结构体来实现的。在NSObject_IMPL包含了一个Class类型的成员isa。继续查看Class的定义:app

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
复制代码

能够发现其实Class就是一个objc_class类型的结构体指针。在最新的objc4的源码中的objc-runtime-new.h文件中,能够找到最新的objc_class的定义以下:框架

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();
    }
}
复制代码

objc_class继承自结构体objc_object,而结构体objc_object的具体定义以下,内部只有一个isa指针iphone

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
复制代码

因为继承关系,结构体objc_class天然也就继承了objc_object的isa指针,因此objc_class也能够转换成以下写法:函数

struct objc_class {
    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();
    }
}
复制代码
  • isa是继承自objc_object的属性(具体做用后文会说明)
  • superclass表示当前类的父类
  • cache则表明方法缓存。
  • bits是class_data_bits_t类型的属性,用来存放类的具体信息。

查看class_data_bits_t的具体实现以下:学习

//此处只列出核心的代码
struct class_data_bits_t {
    ......
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ......
}

复制代码

这时候发现了经过bits的内部函数data()能够拿到class_rw_t类型的数据,查看class_rw_t的源码以下:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;        //只读的属性ro

    method_array_t methods;      //方法列表
    property_array_t properties; //属性列表
    protocol_array_t protocols;  //协议列表

    Class firstSubclass;
    Class nextSiblingClass;
}

复制代码

在结构体class_rw_t中存放着

  • 方法列表methods
  • 属性列表properties
  • 协议列表protocols。
  • 一个class_ro_t类型的只读变量ro

继续查看class_ro_t的源码以下:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  //当前instance对象占用内存的大小
    const uint8_t * ivarLayout;
    const char * name;              //类名
    method_list_t * baseMethodList; //基础的方法列表
    protocol_list_t * baseProtocols;//基础协议列表
    const ivar_list_t * ivars;      //成员变量列表
    
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;//基本属性列表
}

复制代码

此处就不得不说class_rw_tclass_ro_t的区别了,class_ro_t中存放着类最原始的方法列表,属性列表等等,这些在编译期就已经生成了,并且它是只读的,在运行期没法修改。而class_rw_t不只包含了编译器生成的方法列表、属性列表,还包含了运行时动态生成的方法和属性。它是可读可写的。至于class_rw_tclass_ro_t更深层次的区别,我会放在介绍runtime的时候详细说明。

OC对象的内存分配

allocWithZone

在iOS中通常使用以下[[NSObject alloc] init]建立对象,其中[NSObject alloc]就是为NSObject分配内存空间,下面,咱们就从源码入手,来理解OC对象是如何分配内存的。

  • [NSObject alloc]实际上是调用allocWithZone方法来分配内存空间,因此咱们查看objc源码中的NSObject.mm文件。找到_objc_rootAllocWithZone函数:
id 
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}
复制代码
  • 找到其中分配内存的方法class_createInstance(cls, 0);
id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
复制代码
  • 找到_class_createInstanceFromZone(cls, extraBytes, nil)方法,因为方法较长,此处只展现核心代码:
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    ......
    
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    
    ......
}
复制代码

能够看出,其中真正用来分配内存的是C函数calloc,calloc函数传入了两个参数,第一个参数表示对象的个数,第二个参数size表示对象占据的内存字节数。所以size就表示当前对象所须要的内存大小。

  • 这里的size变量表示当前对象所占用的内存大小,能够查看cls->instanceSize(extraBytes)属性内部实现,以下:
// May be unaligned depending on class's ivars. uint32_t unalignedInstanceSize() { assert(isRealized()); return data()->ro->instanceSize; } // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

复制代码

其中cls->unalignedInstanceSize()表示未进行内存对齐的内存大小,cls->alignedInstanceSize()是对未对齐的内存进行内存对齐操做,获得最终所需的内存大小。

这里有个细节,就是执行对齐操做获得的内存大小若是小于16个字节,那么最后分配的内存大小为16个字节,也就是说,咱们建立对象时,分配的内存最少是16个字节。

OC对象的内存分配

获取内存大小的方法?

在iOS中,咱们能够经过三种方式来获取一个对象的内存大小。

sizeof

sizeof,它其实不是一个函数,而是一个运算符,它和宏定义相似,在编译期就将传入的类型转换成具体的占用内存的大小。例如int是4个字节,那么sizeof(int)在编译期就会直接被替换成4

注意:sizeof须要传入一个类型过去,它返回的是一个类型所占用的内存空间

class_getInstanceSize

class_getInstanceSize(Class _Nullable cls),传入一个Class类型的对象就能获得当前Class所占用的内存大小。例如,class_getInstanceSize([NSObject class]),最后返回的是8,也就说明NSObject对象在内存中占用8个字节,并且因为NSObject最后会转化成结构体NSObject_IMPL,并且内部只有一个isa指针,因此也就能够理解为isa指针占用8个字节的存储空间。

class_getInstanceSize函数内部其实就是调用alignedInstanceSize函数获取到对象所须要的真实内存大小。

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
复制代码

在调用calloc函数进行内存分配的时候,是将alignedInstanceSize的值看成参数赋值给calloc函数,所以calloc函数能够有以下写法:

id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    ......
    size_t size = class_getInstanceSize(cls);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    ......
}
复制代码

由此能够看出,class_getInstanceSize(Class _Nullable cls)所返回的实际上是对象实际所须要的内存大小。

malloc_size

malloc_size(const void *ptr)函数,传入const void *类型的参数,就能够获取到当前操做系统所分配的内存大小。例如:仍是利用NSObject来进行测试,malloc_size((__bridge const void *)([[NSObject alloc] init])),将NSObject类型的实例对象做为参数,最后获得的值为16,和咱们以前使用class_getInstanceSize([NSObject class])获得的8不相同。

这是由于在iOS中,在分配内存时,若是对象所须要的内存大小小于16个字节,那么就分配给这个对象16个字节的内存空间。也就是每一个对象至少分配16个字节的内存空间

size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
复制代码

三种获取内存方法对比

  • 首先,建立对象XLPerson,添加3个属性,以下
@interface XLPerson : NSObject{
    int _height;
    int _age;
    long _num;
}
复制代码
  • 在main函数中建立XLPerson实例对象,而且使用上面的三种方式分别获取XLPerson类的内存大小:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

@interface XLPerson : NSObject{
    @public
    int _height;
    int _age;
    long _num;
}

@end

@implementation XLPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XLPerson *p = [[XLPerson alloc] init];
        p->_height = 10;
        p->_age = 20;
        p->_num = 25;
        
        NSLog(@"sizeof --> %lu", sizeof(p));
        
        NSLog(@"class_getInstanceSize --> %lu", class_getInstanceSize([XLPerson class]));
        
        NSLog(@"malloc_size --> %lu", malloc_size((__bridge const void *)(p)));
    }
    return 0;
}
复制代码
  • 运行程序,打印出结果以下:

能够看出此时sizeof(p)返回8个字节,class_getInstanceSize返回24个字节,malloc_size则返回32个字节。3个方法返回的内存大小都不同,这是为何呢?

sizeof(p)为何只返回8个字节呢?

其实sizeof(p)返回8个字节,这个很好理解,由于sizeof传入的是p,而p在此处表示的是一个指向XLPerson实例对象的一个指针,在iOS中,指针类型所占用的内存大小为8个字节。所以sizeof(p)所返回的并非XLPerson对象的内存大小。

要想使用sizeof获取到XLPerson对象的内存大小,就须要知道XLPerson最终会转换成什么类型。经过上文的学习,咱们知道,XLPerson内部实际上是一个结构体,经过xcrun指令将文件转换成.cpp文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
复制代码

分析main.cpp文件能够得出,XLPerson最终会转换成以下结构体类型

struct NSObject_IMPL {
    Class isa;
};

struct XLPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _height;
    int _age;
    long _num;
};
复制代码

此时调用sizeof(struct XLPerson_IMPL)就能够得出struct XLPerson_IMPL类型所占用的内存为24字节,其实也就是XLPerson所占用的内存是24个字节。

由此可看出运算符sizeof(struct XLPerson_IMPL)和函数class_getInstanceSize([XLPerson class]返回的是对象真正所须要的内存大小。

为什么malloc_size返回的内存大小和对象实际所需内存不一样?

在了解malloc_size函数以前,咱们先来分析一下XLPerson内部结构体所须要的真实内存大小。

struct XLPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _height;
    int _age;
    long _num;
};
复制代码
  • 首先咱们知道NSObject_IMPL内部其实只有一个isa指针,所以它所占用的内存是8个字节
  • int类型的变量_height占用4个字节
  • int类型的变量_age占用4个字节
  • long类型的变量_num占用8个字节

因此,单纯从结构体层面分析的话,咱们能够看出XLPerson_IMPL结构体所须要的内存是24个字节,这和上文的sizeof(struct XLPerson_IMPL)以及函数class_getInstanceSize([XLPerson class]返回的内存大小一致。因而可知,XLPerson所须要的内存就是24个字节。

但是为何malloc_size所返回的内存大小确是32个字节呢?这就要说到内存对齐操做

结构体的内存对齐操做

首先,咱们先将上文中提到的XLPerson属性进行修改,去掉其中的_age属性

@interface XLPerson : NSObject{
    @public
    int _height;
    long _num;
}
@end
复制代码

而后从新运行项目,能够看到XLPerson实际占用的内存仍是24个字节,而经过分析咱们能够发现XLPerson只须要20个字节的内存空间。

这就是结构体内存对齐操做所致使的,也就是上文中所说的alignedInstanceSize函数的做用。那么什么是结构体的内存对齐操做?

结构体不像数组,结构体中能够存放不一样类型的数据,它的大小也不是简单的各个数据成员大小之和,限于读取内存的要求,而是每一个成员在内存中的存储都要按照必定偏移量来存储,根据类型的不一样,每一个成员都要按照必定的对齐数进行对齐存储,最后整个结构体的大小也要按照必定的对齐数进行对齐。

结构体的内存对齐规则以下:

  • 第一个成员的首地址为0
  • 每一个成员的首地址是自身大小的整数倍
  • 结构体的内存总大小是其成员中所含最大类型的整数倍

这就是为什么XLPerson的内存大小为24个字节的缘由。

iOS系统的内存对齐操做

既然XLPerson的内存占用为24个字节,那么为何系统会给它分配32个字节呢?其实在iOS系统中也存在内存对齐操做。

咱们能够经过打印内存信息来查看是否分配了32个字节,依旧是使用上面的例子

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        XLPerson *p = [[XLPerson alloc] init];
        p->_height = 1;
        p->_num = 3;
    }
    return 0;
}
复制代码
  • 断点状态下,使用po p获取到p的内存地址为0x1005b4fd0
(lldb) po p
<XLPerson: 0x1005b4fd0>
复制代码
  • 打开Xcode下Debug->Debug Workflow->View Memory,输入刚刚获取到的内存地址,能够获得对象p的内存分配状况

其中前8个字节存储着isa指针,蓝色框中的四个字节存放着_height=1,而绿色框中的8个字节存放着_num=3,这里由于结构体内存对齐原则,因此_num=3的内存地址从第17个字节开始,整个红色框的32个字节,就是系统分配给XLPerson实例对象的内存空间,这也证实了malloc_size((__bridge const void *)(p))返回的确实是系统分配给p对象的内存空间。

OC对象的分类

OC对象主要分为3种

  • instance实例对象
  • class类对象
  • mata-class元类对象

instance对象

instance对象就是经过alloc操做建立出来的对象,每次调用alloc操做都会建立出不一样的instance对象,它们拥有各自独立分配的内存空间。例如上文中使用的XLPerson的实例对象

XLPerson *p1 = [[XLPerson alloc] init];
XLPerson *p2 = [[XLPerson alloc] init];
复制代码

其中p一、p2就是实例对象,在内存中能够同时拥有多个同一类对象的实例对象。它们各自拥有一块内存空间,用来存储独有的信息。实例对象内部存放的内容以下(以XLPerson的实例对象为例):

XLPerson *p1 = [[XLPerson alloc] init];
p->_height = 10;
p->_num = 25;
复制代码
  • isa指针,指向它的类对象
  • _height = 10
  • _num = 25

由于经过[XLPerson alloc]就能建立一个实例对象,因此每一个实例对象内部会存放着一个isa指针,指向它的类对象,还存放着定义好的其它的成员变量的具体值。

class类对象

类对象是将具备类似属性和方法的对象抽象出来,从而造成类对象。它能够定义一些类似的方法和属性,不一样的实例对象去引用类对象的属性或者方法,能减小代码的重复率。

运行以下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *p1 = [[XLPerson alloc] init];
        XLPerson *p2 = [[XLPerson alloc] init];
        
        Class c1 = [XLPerson class];
        Class c2 = [p1 class];
        Class c3 = [p2 class];
        Class c4 = object_getClass(p1);
        Class c5 = object_getClass(p2);
        
        NSLog(@"\n c1 -> %p,\n c2 -> %p,\n c3 -> %p,\n c4 -> %p,\n c5 -> %p", c1, c2, c3, c4, c5);
    }
    return 0;
}
复制代码

能够获得结果为:

经过结果能够发现,全部的class对象的内存地址都是相同的,这也就说明在内存中只有一个class对象,不论是使用上面的哪一种方法获取到的class对象都是同一个。

class对象内部其实就是一个object_class的结构体,具体的结构定义在上文已经介绍过,这里只列举出class对象存储的主要信息:

  • isa指针
  • superClass
  • 属性信息(properties),存放着属性的名称,属性的类型等等,这些信息在内存中只须要存放一份
  • 对象方法信息(methods)
  • 协议信息(protocols)
  • 成员变量信息等等(ivars)

mata-class元类对象

元类其实也是一个class类型的对象,它内部的结构和类对象一致,可是元类对象中只存放了以下信息:

  • isa指针
  • superClass
  • 类方法信息(class method)

元类和类同样,在内存中只会存在一个元类对象。能够经过runtime的方法获取元类对象

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *p1 = [[XLPerson alloc] init];
        XLPerson *p2 = [[XLPerson alloc] init];
        
        Class c1 = object_getClass(p1);
        Class c2 = object_getClass(p2);
        
        Class mataC1 = object_getClass(c1);
        Class mataC2 = object_getClass(c2);
        
        BOOL c1_isMataClass = class_isMetaClass(c1);
        BOOL c2_isMataClass = class_isMetaClass(c2);
        BOOL mataC1_isMataClass = class_isMetaClass(mataC1);
        BOOL mataC2_isMataClass = class_isMetaClass(mataC2);
        NSLog(@"\n c1_isMataClass:%d,\n c2_isMataClass:%d,\n mataC1_isMataClass:%d,\n mataC2_isMataClass:%d"
              ,c1_isMataClass, c2_isMataClass, mataC1_isMataClass, mataC2_isMataClass);
        NSLog(@"\n c1 -> %p,\n c2 -> %p,\n mataC1 -> %p,\n mataC2 -> %p",
              c1, c2, mataC1, mataC2);
    }
    return 0;
}
复制代码

调用结果以下:

在上图中,c1和c2都是类对象,因此返回0,mataC1和mataC2都是元类对象,因此返回1。同时mataC1和mataC2的内存地址彻底相同,这也说明了元类对象在内存中确实只存在一份。

instance对象、class对象和mata-class对象的关系

上文屡次提到,在Class对象内部都会有一个isa指针,那么这个isa指针的做用是什么呢?其实isa指针是instance对象、class对象和mata-class对象之间的桥梁。

isa指针的做用

  • instance对象的isa指针指向class对象,并且上文也说到,在instance对象中只存储了isa指针和具体的属性的值,当咱们调用instance对象的实例方法时,实际上是经过isa指针找到它的类对象,在类对象的对象方法列表(methods)中查找到方法的实现,并调用。
  • class对象的isa指针指向mata-class对象,当调用类方法时,会经过类对象的isa指针找到它的元类对象mata-class,而后在mata-class的方法列表中找到对应类方法的实现并执行。

superClass指针的做用

superClass其实就是指向class对象或者mata-class对象的父类,下面咱们以一个简单的例子来具体说明:

@interface XLPerson : NSObject

- (void)run;
+ (void)sleep;

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        XLPerson *p1 = [[XLPerson alloc] init];
        [p1 run];
        [XLPerson sleep];
    }
}
复制代码

class对象中superClass指针的做用

XLPerson继承自NSObject,而且声明了一个类方法sleep()和一个对象方法run(),当p1调用对象方法run()时

  • 首先会经过实例对象p1内部的的isa指针找到它的类对象。在类对象的方法列表中查找run()方法。
  • 若是类对象的方法列表中没有此方法的实现,那么会经过类对象的superClass指针找到它的父类对象,在此处就是NSObject对象,而且在NSObject对象的方法列表中寻找run()方法的实现,而后调用。

mata-class对象中superClass指针的做用

仍是以上面的例子来讲明,当XLPerson调用类方法sleep()时

  • 首先经过XLPerson的isa指针找到它的元类对象,在元类对象的方法列表中寻找sleep()方法。
  • 若是在XLPerson的元类对象中没有找到sleep()方法,那么会经过XLPerson的元类对象的superClass指针找到XLPerson的父类对象的元类对象,此处就是NSObject的元类对象,在NSObject元类对象的方法列表中找到sleep()方法并执行。

isa、superClass总结

首先先看一张很是经典的描述instance对象、类对象以及元类对象之间关系的图片。途中虚线表明isa指针,实线表明superClass指针。

  • instance对象的isa指针指向它的class对象
  • class对象的isa指针指向它的mata-class对象
  • mata-class对象的isa指向基类对象mata-class
  • class对象的superClass指向它的父类的class对象,基类的class对象的superClass指向nil
  • mata-class的superClass指向父类对象的mata-class,基类对象的mata-class的superClass指向基类对象自身(此处是比较特殊的地方)
  • 实例方法查找路线
    • 首先会经过isa指针到class对象中找
    • 若是找不到,经过superClass到父类的class对象中找
    • 若是还找不到,再到基类的class对象中查找
  • 类方法的查找路线
    • 首先会经过类对象的isa指针到mata-class中查找
    • 若是mata-class中找不到,经过superClass到父类的元类对象中查找
    • 若是在父类的元类对象中找不到,就到基类对象的元类对象中查找
    • 若是基类的元类对象中找不到,那么会到基类对象中查找。

OC对象面试题

一个NSObject对象占用了多少内存?

系统给一个NSObject对象分配了16个字节的内存空间(经过malloc_size函数申请内存),可是NSObject对象内部只有一个isa指针,因此它实际使用到了8个字节的内存,而因为ios的内存对齐原则,系统最少分配16个字节的内存空间。

能够经过class_getInstanceSize函数来获取NSObject占用内存大小

对象的isa指向哪里?有什么做用?

  • 实例对象的isa指针指向class对象
  • class对象的isa指针指向mata-class对象
  • meta-class对象的isa指针指向基类的meta-class对象

OC的类的信息(方法、属性、成员变量等等)分别存放在哪?

  • OC中实例对象的方法、属性、成员变量、协议信息等等存放在class对象中
  • 类方法存放在mata-class中
  • 成员变量的具体值存放在实例对象中,由于成员变量的描述信息好比它的类型是int等等,在内存中只需存储一份,因此将属性描述信息存放在类对象中,可是成员变量的值每一个实例变量都不相同,因此每一个实例对象存放一份

结束语

以上内容纯属我的理解,若是有什么不对的地方欢迎留言指正。

一块儿学习,一块儿进步~~~

相关文章
相关标签/搜索