猿创征文|iOS经典面试题之深入解析objc对象的内存空间数据结构以及isa指针的理解
Posted ╰つ栺尖篴夢ゞ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了猿创征文|iOS经典面试题之深入解析objc对象的内存空间数据结构以及isa指针的理解相关的知识,希望对你有一定的参考价值。
一、objc 对象的 isa 的指针指向什么?有什么作用?
- isa 等价于 is kind of:
-
- 实例对象 isa 指向类对象;
-
- 类对象指 isa 向元类对象;
-
- 元类对象的 isa 指向元类的基类;
- isa 有两种类型:
-
- 纯指针,指向内存地址;
-
- NON_POINTER_ISA,除了内存地址,还存有一些其它信息。
- isa 指向它的类对象,从而可以找到对象上的方法,对象、类、元类之间的关系,如下所示:
- 说明如下:
-
- Root class(class) 其实就是 NSObject,NSObject 是没有超类的,因此 Root class(class) 的 superclass 指向 nil;
-
- 每个 Class 都有一个 isa 指针指向唯一的 Meta class;
-
- Root class(meta) 的 superclass 指向 Root class(class),也就是 NSObject,形成一个回路;
-
- 每个 Meta class 的 isa 指针都指向 Root class(meta)。
- 在 Runtime 源码查看 isa_t 是共用体,简化结构如下:
union isa_ t
Class cls;
uintptr_ t bits;
# if _arm64__ // arm64架构
# define ISA_MASK 0x80000000ffffffff8ULL // 用来取出33位内存地址使用(&)操作
# define ISA_MAGIC_MASK 0x0000003f000000001ULL
# define ISA_MAGIC_VALUE 0x0000001a000000001ULL
struct
uintptr_t nonpointer :1; // 0:代表普通指针,1:表示优化过的,可以存储更多信息
uintptr_t has_assoc :1; // 是否设置过关联对象,如果没设置过,释放会更快
uintptr t has_cxx_dtor :1; // 是否有C++的析构函数
uintptr t shiftcls :33; // MACH_VL_MAX_ADDRESS ox10000000000 内存地址值
uintptr_t magic :6; // 用于在调试时分解对象是否未完成初始化
uintptr_t weakly_referenced :1; // 是否有被弱引用指向过
uintptr_t deallocating :1; // 是否正在释放
uintptr_t has_sidetable_rc :1; // 引用计数器是否过大无法存储在ISA中,如果为1,那么引用计数会存储在一个叫ideTable的类的
uintptr_t extra_rc :19; // 里面存储的值是引用计数器减
define RC_ONE (1ULL<<45)
define RC_HALF (1ULL<<18)
;
elif _x86_64_ // arm86架构, 模拟器是arm86
define ISA_MASK 0x00007ffffffffff8ULL
define ISA_MAGIC_MASK 0x001f800000008001ULL
define ISA_MAGIC_VALUE 0x001d800000008001ULL
struct
uintptr_t nonpointer : 1;
uintptr_t has_assoc. : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic. : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
define RC_ONE (1ULL<<56)
define RC_HALF (1ULL<<7)
;
else
error unknown architecture for packed isa
endif
二、一个 NSObject 对象占用多少内存空间?
- 受限于内存分配的机制,一个 NSObject 对象都会分配 16byte 的内存空间。但是实际上在 64 位下,只使用了 8byte;在 32 位下,只使用了 4byte。
- 一个 NSObject 实例对象成员变量所占的大小,实际上是 8 字节。
#import <Objc/Runtime>
Class_getInstanceSize([NSObject class])
- 本质是:
size_t class_getInstanceSize(Class cls)
if (!cls) return 0;
return cls->alignedInstanceSize();
- 获取 ObjC 指针所指向的内存的大小,实际上是 16 字节:
#import <malloc/malloc.h>
malloc_size((__bridge const void *)objc);
- 对象在分配内存空间时,会进行内存对齐,所以在 ios 中,分配内存空间都是 16 字节的倍数。
三、实例对象的数据结构
- 在文件 objc-private.h 中,如下所示:
struct objc_object
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA(bool authenticated = false);
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasNonpointerIsa();
bool isTaggedPointer();
bool isTaggedPointerOrNil();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
uintptr_t overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// Controls what parts of rootRetain,Release to emit/inline
// - Full means the full (slow) implementation
// - Fast means the fastpaths only
// - FastOrMsgSend means the fastpaths but checking whether we should call
// -retain/-release or Swift, for the usage of objc_retain,release
enum class RRVariant
Full,
Fast,
FastOrMsgSend,
;
// Unified retain count manipulation for nonpointer isa
inline id rootRetain(bool tryRetain, RRVariant variant);
inline bool rootRelease(bool performDealloc, RRVariant variant);
id rootRetain_overflow(bool tryRetain);
uintptr_t rootRelease_underflow(bool performDealloc);
void clearDeallocating_slow();
// Side table retain count overflow for nonpointer isa
struct SidetableBorrow size_t borrowed, remaining; ;
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
SidetableBorrow sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
void sidetable_clearExtraRC_nolock();
#endif
// Side-table-only retain count
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain(bool locked = false);
id sidetable_retain_slow(SideTable& table);
uintptr_t sidetable_release(bool locked = false, bool performDealloc = true);
uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if DEBUG
bool sidetable_present();
#endif
;
- 本质上 objc_object 的私有属性只有一个 isa 指针,指向类对象的内存地址。
四、类对象的数据结构
- 类对象就是 objc_class,如下所示:
struct objc_class : objc_object
Class superclass;
const char *name;
uint32_t version;
uint32_t info;
uint32_t instance_size;
struct old_ivar_list *ivars;
struct old_method_list **methodLists;
Cache cache;
struct old_protocol_list *protocols;
// CLS_EXT only
const uint8_t *ivar_layout;
struct old_class_ext *ext;
void setInfo(uint32_t set)
OSAtomicOr32Barrier(set, (volatile uint32_t *)&info);
void clearInfo(uint32_t clear)
OSAtomicXor32Barrier(clear, (volatile uint32_t *)&info);
// set and clear must not overlap
void changeInfo(uint32_t set, uint32_t clear)
ASSERT((set & clear) == 0);
uint32_t oldf, newf;
do
oldf = this->info;
newf = (oldf | set) & ~clear;
while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&info));
bool hasCxxCtor()
// set_superclass propagates the flag from the superclass.
return info & CLS_HAS_CXX_STRUCTORS;
bool hasCxxDtor()
return hasCxxCtor(); // one bit for both ctor and dtor
// Return YES if the class's ivars are managed by ARC,
// or the class is MRC but has ARC-style weak ivars.
bool hasAutomaticIvars()
return info & (CLS_IS_ARC | CLS_HAS_WEAK_WITHOUT_ARC);
// Return YES if the class's ivars are managed by ARC.
bool isARC()
return info & CLS_IS_ARC;
bool hasCustomRR()
return true;
bool hasCustomAWZ()
return true;
bool forbidsAssociatedObjects()
// Old runtime doesn't support forbidding associated objects.
return false;
bool instancesHaveAssociatedObjects()
return info & CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
void setInstancesHaveAssociatedObjects()
setInfo(CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
bool shouldGrowCache()
return info & CLS_GROW_CACHE;
void setShouldGrowCache(bool grow)
if (grow) setInfo(CLS_GROW_CACHE);
else clearInfo(CLS_GROW_CACHE);
// +initialize bits are stored on the metaclass only
bool isInitializing()
return getMeta()->info & CLS_INITIALIZING;
// +initialize bits are stored on the metaclass only
void setInitializing()
getMeta()->setInfo(CLS_INITIALIZING);
// +initialize bits are stored on the metaclass only
bool isInitialized()
return getMeta()->info & CLS_INITIALIZED;
// +initialize bits are stored on the metaclass only
void setInitialized()
getMeta()->changeInfo(CLS_INITIALIZED, CLS_INITIALIZING);
bool isLoadable()
// A class registered for +load is ready for +load to be called
// if it is connected.
return isConnected();
IMP getLoadMethod();
bool isFuture();
bool isConnected();
const char *mangledName() return name;
const char *demangledName() return name;
const char *nameForLogging() return name;
bool isRootClass()
return superclass == nil;
bool isRootMetaclass()
return ISA() == (Class)this;
bool isMetaClass()
return info & CLS_META;
// NOT identical to this->ISA() when this is a metaclass
Class getMeta()
if (isMetaClass()) return (Class)this;
else return this->ISA();
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceStart()
// This is not simply superclass->instance_size.
// superclass->instance_size is padded to its sizeof() boundary,
// which may envelop one of this class's ivars.
// That in turn would break ARC-style ivar layouts.
// Instead, we use the address of this class's first ivar when possible.
if (!superclass) return 0;
if (!ivars || ivars->ivar_count == 0) return superclass->instance_size;
return ivars->ivar_list[0].ivar_offset;
// Class's instance start rounded up to a pointer-size boundary.
// This is used for ARC layout bitmaps.
uint32_t alignedInstanceStart()
return word_align(unalignedInstanceStart());
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize()
return instance_size;
// 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;
;
- 继承自 objc_object 结构体,因此包含 isa 指针:
-
- isa:指向元类;
-
- superClass:指向父类;
-
- Cache:方法的缓存列表;
-
- data:顾名思义,就是数据,是一个被封装好的 class_rw_t。
五、class_rw_t 的理解
- rw 代表可读可写,ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中:
// 可读可写
struct class_rw_t
// Be warned that Symbol ication knows the layout of this structure
uint32_t flags;
uint32_t version; ,
const class_ro_t *ro; // 指向只读的结构体,存故类初始信息
/*
这三个都是二维数组,是可读可写的,包含了类的初始内容,分类的内容
methods 中,存储 methods_list_t --> methods_t
二维数组,methods_list_t --> methods_t
这三个二维数组中的数据有一部分是从 class_ro_t 中合并过来
*/
method_array_t methods; // 方法列表(类对象存放对象方法,元类对象存放实例方法)
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
// ...
六、class_ro_t 的理解
- 存储了当前类在编译期就已经确定的属性、方法以及遵循的协议:
struct class_ro_t
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union
const uint8_t * ivarLayout;
Class nonMetaclass;
;
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// ...
七、objc 中向一个 nil 对象发送消息将会发生什么?
- 如果向一个 nil 对象发送消息,首先在寻找对象的 isa 指针时就是 0 地址返回,所以不会出现任何错误,也不会崩溃。
- 如果一个方法返回值是一个对象,那么发送给 nil 的消息将返回 0(nil);
- 如果方法返回值为指针类型,其指针大小为小于或者等于 sizeof(void*) ,float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回 0;
- 如果方法返回值为结构体,发送给 nil 的消息将返回 0,结构体中各个字段的值将都是 0;
- 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。
八、objc 在向一个对象发送消息时,发生了什么?
- objc 在向一个对象发送消息时,runtime 会根据对象的 isa 指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果一直到根类还没找到,转向拦截调用,执行消息转发机制,一旦找到 ,就去执行它的实现 IMP。
以上是关于猿创征文|iOS经典面试题之深入解析objc对象的内存空间数据结构以及isa指针的理解的主要内容,如果未能解决你的问题,请参考以下文章
猿创征文|iOS经典面试题之深入分析图像的解码渲染与基本原理
iOS经典面试题之深入解析objc对象的内存空间数据结构以及isa指针的理解
iOS经典面试题之深入解析Runtime如何通过selector找到对应的IMP地址