问题: 简述一下内存的几大区域?
iOS的内存分布如下图所示 .主要分为
-
代码段
- 代码段主要存储编译后的代码
-
数据段
- 字符串常量
- 初始化完成的全局变量和静态变量
- 未初始化的全局变量和静态变量
-
堆区
- 内存地址分配由低到高
- 通过 alloc malloc calloc 进行内存分配
-
栈区
- 内存地址分配由高到底
- 函数调用开销,比如局部变量
-
内核区
问题: 简述一下Targged Pointer?
在 ARM 64 操作系统下, iOS引入了 Tagged Pointer 技术,用于存储长度较短的 NSNumber NSString NSDate. Targged Pointer数据存放于常量区.
使用 Tagged Pointer 之后, NSNumber NSString NSDate 的指针里面存储的数据变为 Tag + Data, 也就是将数据直接存储在指针当中.
在iOS中, 当对象的指针最高有效位为1的时候,则该指针为 Tagged Pointer 类型数据.
#define _OBJC_TAG_MASK (1UL<<63)
MacOS中, 当对象的指针最低有效位为1的时候,则该指针为 Tagged Pointer 类型数据.
#define _OBJC_TAG_MASK (1UL<<63)
当指针不够存储数据时,才会动态地在堆区分配内存进行数据的创建与存储.
问题: 简述一下拷贝相关内容?
iOS中拷贝根据方法分类主要与 copy
和 mutableCopy
, 根据是否产生新对象分为 深拷贝
和 浅拷贝
.
四者的关系如下表示.
不可变对象 | 可变对象 | |
---|---|---|
copy (返回不可变对象) | 浅拷贝 | 深拷贝 |
mutableCopy (返回可变对象) | 深拷贝 | 深拷贝 |
问题: 简述一下引用计数的存储位置?
-
arm64架构下, 由于isa指针是一个共用体, 引用计数主要存储在
extra_rc
的位域中. -
extra_rc
存储的是引用计数 - 1 的值. -
如果
extra_rc
空间不足, 则has_sidetable_rc
的值为 1, 引用计数则会存储到sidetable类中RefcountMap
类型 refcnts.
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
问题: 简述一下weak指针的实现原理?
-
ARM64架构下,如果当前对象曾经被weak指针修饰过,那么它的ISA指针就会
weakly_referenced
的值就为1,代表对象曾经被weak指针修饰过. -
在释放对象的过程中,主要进入的调用的函数是 objc_object 的
rootDealloc
函数. 函数中会判断当前对象是否是被关联对象,是否含有C++的析构函数,是否被weak修饰过等,这些信息都是通过 ISA 指针来获取.如果上述任何条件都不成立,那么直接调用free
函数进行释放.inline void objc_object::rootDealloc() { if (isTaggedPointer()) return; // fixme necessary? if (fastpath(isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && #if ISA_HAS_CXX_DTOR_BIT !isa.has_cxx_dtor && #else !isa.getClass(false)->hasCxxDtor() && #endif !isa.has_sidetable_rc)) { assert(!sidetable_present()); free(this); // 没有任何额外信息 直接释放 } else { object_dispose((id)this); } }
-
如果当前对象含有被关联对象信息、弱引用信息、C++的析构函数,其主要操作函数是
objc_destructInstance
函数.void *objc_destructInstance(id obj) { if (obj) { Class isa = obj->getIsa(); if (isa->hasCxxDtor()) { object_cxxDestruct(obj); } if (isa->instancesHaveAssociatedObjects()) { _object_remove_assocations(obj); } objc_clear_deallocating(obj); } return obj; }
问题: 简述一下@autoreleasepool的实现原理?
-
@autoreleasepool
的本质实际上是一个会生成一个C++的结构体__AtAutoreleasePool
,最终自动释放池的管理是由AutoreleasePoolPage
来进行实现的.-
每一个 AutoreleasePoolPage 对象占用4096个字节内存,除了存储自身的成员变量外, 剩下的空间用来存储 autorelease 对象的地址. 可以通过
begin
和end
函数获取内存地址区间.id * begin() { return (id *) ((uint8_t *)this+sizeof(*this)); } id * end() { return (id *) ((uint8_t *)this+SIZE); }
-
所有的 AutoreleasePoolPage 对象是通过
双向链表
的形式连接在一起的. 这个是可以通过 AutoreleasePoolPage的父类 AutoreleasePoolPageData 结构体可以得到答案.class AutoreleasePoolPage : private AutoreleasePoolPageData { ... }; struct AutoreleasePoolPageData { ... AutoreleasePoolPage * const parent; // 上一个Page对象 AutoreleasePoolPage *child; // 下一个Page对象 ... };
-
-
在开始时会调用结构体的构造函数,构造函数中会通过
objc_autoreleasePoolPush
生成一个 pool 对象. -
在结束时会调用结构体的析构函数,析构函数中会
objc_autoreleasePoolPop
释放该pool 对象. -
objc_autoreleasePoolPush 函数内部会调用 C++的
AutoreleasePoolPage
类的中 push 方法,会生成并返回一个释放边界值POOL_BOUNDARY
.static inline void *push() { id *dest; if (slowpath(DebugPoolAllocation)) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }
- 如图所示,这个边界值会插入到当前的
AutoreleasePoolPage
内存中. 所以 @autorelease的并不一定创建新的AutoReleasePage
,但一定会创建一个边界值POOL_BOUNDARY
. - 可以通过
_objc_autoreleasePoolPrint
函数来确认.
- 如图所示,这个边界值会插入到当前的
-
任意对象的 autorelease 操作都会通过
AutoReleasePage 的 autoreleaseFast
方法将 该对象的内存地址存放到 链表结构中,也就是说会入栈.static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } }
-
objc_autoreleasePoolPop 函数内部会调用 C++的
AutoreleasePoolPage
类的中 pop 方法.释放的过程是 从最后往前释放,并且根据边界值POOL_BOUNDARY
来确认是否停止.
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
page = hotPage();
if (!page) {
return setHotPage(nil);
}
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
} else {
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
问题: 简述一下autorelease与RunLoop的关系.
autorelease的 Push 和 Pop 是通过主线程监听 RunLoop的状态来实现的,一共是两个Observer.
-
第一个Observer 用来监听
RunLoopEntry
状态,这时候会处理 autorelease 的objc_autoreleasePoolPush
. -
第二个Observer 用来监听
RunLoopExit | RunLoopBeforeWaiting
状态.- RunLoopBeforeWaiting状态下,会调用一次
objc_autoreleasePoolPop
和objc_autoreleasePoolPush
. - RunLoopExit 状态下,会调用一次
objc_autoreleasePoolPop
.
- RunLoopBeforeWaiting状态下,会调用一次
Comments | 0 条评论