OC每一个方法名是一个SEL ,根据SEL可以查找到函数体存放的内存地址IMP。查找的原理请参考iOS OC运行时详解。
让我们跟着一个方法,一步步探究以下问题
- 函数执行前后做了什么
- 函数内部成员的内存如何管理
@interface TestObject :NSObject
@property (nonatomic , strong) NSArray *array;
@end
@implementation TestObject
- (instancetype)init
{
if (self = [super init]) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
self.array = [NSArray array];
NSLog(@"dispatch_after");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.array = [NSArray array];
NSLog(@"dispatch_after main");
});
}
return self;
}
- (void)dealloc
{
NSLog(@"dealloc %@",_array);
}
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self testTemporaryVariable];
}
- (void)testTemporaryVariable
{
TestObject *obj = [[TestObject alloc] init]; // 临时变量
obj.array = [NSArray arrayWithObject:@"test"]; // 引用一次
// 函数结束,obj会在什么时候释放?
}
@end
跟踪TestObject的dealloc调用栈发现objc_object::sidetable_release(bool)回调了dealloc
接下查看objc源码来看sidetable_release是做什么用的。
从objc_object结构体中可以发现这个函数。说明每个OC对象都有
id sidetable_retain();
uintptr_t sidetable_release(bool performDealloc = true);
两个函数
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
#define SIDE_TABLE_RC_SHIFT 2
#define SIDE_TABLE_FLAG_MASK (SIDE_TABLE_RC_ONE-1)
// rdar://20206767
// return uintptr_t instead of bool so that the various raw-isa
// -release paths all return zero in eax
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
// static StripedMap<SideTable>& SideTables() {
// return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
// }
SideTable& table = SideTables()[this];// 找到当前对象的管理表,包含引用计数map和当前对象的弱引用表
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);// 引用计数表中以当前对象的地址为Key查找到当前对象的引用计数表
if (it == table.refcnts.end()) { // 引用计数了
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.可能有弱引用,
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE; // 引用计数 -= SIDE_TABLE_RC_ONE
}
table.unlock();
if (do_dealloc && performDealloc) {// 在此处调用dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
struct SideTable {
spinlock_t slock;
// refcnt的详细解释
// ZeroValuesArePurgeable=true is used by the refcount table.
// A key/value pair with value==0 is not required to be stored in the refcount table; it could correctly be erased instead.
// For performance, we do keep zero values in the table when the true refcount decreases to 1: this makes any future retain faster.
// For memory size, we allow rehashes and table insertions to remove a zero value as if it were a tombstone.
RefcountMap refcnts;
weak_table_t weak_table;
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
通过上面的分析大致能够了解sideTable_release(bool)判断了对象的引用计数,达到释放阈值并进行标记。未达到释放阈值的计数器-1,不调用dealloc函数。直到当前对象调用release,并达到阈值,才会被释放。
这里可以在上面的代码中得到证实。延时函数内部强引用了obj对象,直到dispatch_after的block执行完成后才会继续走dealloc.
根据现有知识,OC调用某函数,其实给这个函数地址发消息。比如:
- ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
阅读objc-msg-arm64.s源码可以发现, objc_msgSend的汇编实现。
GetClassFromIsa_p16根据对象的isa指针缓存中获取类,缓存不命中去执行常规查找,找到执行完,加入方法缓存。
下面分析执行sideTable_release函数之前干了什么。调用来源有下面三个
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
........
}while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
........
}
// Base release implementation, ignoring overrides.
// Does not call -dealloc.
// Returns true if the object should now be deallocated.
// This does not check isa.fast_rr; if there is an RR override then
// it was already called and it chose to call [super release].
inline bool
objc_object::rootRelease()
{
if (isTaggedPointer()) return false;
return sidetable_release(true);
}
inline bool
objc_object::rootReleaseShouldDealloc()
{
if (isTaggedPointer()) return false;
return sidetable_release(false);
}
可以猜想一个对象调用了release操作,引用计数达到了阈值,就会回调dealloc。
让我们继续思考。MRC下函数的局部成员,谁分配谁释放。ARC把这个过程集成到了Clang中。Objective-C Automatic Reference Counting (ARC).
- 编译器到底什么时候把retain和release加入代码的?
这就涉及到llvm里讲的,所有权归属操作。
Methods in the `alloc`, `copy`, `init`, `mutableCopy`, and `new` [families](https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-method-families) are implicitly marked `__attribute__((ns_returns_retained))`. This may be suppressed by explicitly marking the method `__attribute__((ns_returns_not_retained))`.
It is undefined behavior if the method to which an Objective-C message send statically resolves has different retain semantics on its result from the method it dynamically resolves to. It is undefined behavior if a block or function call is made through a static type with different retain semantics on its result from the implementation of the called block or function.
// 我们 通过这个简单的函数看一下app可执行文件的方法到底变成了什么
- (id)testTemporaryVariable
{
TestObject *obj = [[TestObject alloc] init];
obj.array = [NSArray arrayWithObject:@"test"];
return obj;
}
工程run 成功后,hopper查看生成的可执行文件,找到对应方法如下图:
可以看出给obj赋值操作(setArray:)先调用了objc_retainAutoreleasedReturnValue方法,相当于帮我们retain一次,因为arc下的返回对象都是autorelease对象。该方法其实是对arrayWithObject:返回对象的引用计数管理,可以在NSObject.mm源码找到。如下
// Accept a value returned through a +0 autoreleasing convention for use at +1.
id
objc_retainAutoreleasedReturnValue(id obj)
{
if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;
return objc_retain(obj);
}
其实就是调用了objc_object::rootRetain(bool tryRetain, bool handleOverflow)方法。
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
.......
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
.......
}
这就走到了与sidetable_release()对应的sidetable_retain()方法。暂时略过这里。
函数最后执行了objc_storeStrong方法,相当于与调用了一次release。通过查看源码可以发现这个函数做了哪些操作。
void
objc_storeStrong(id *location, id obj)
{
id prev = *location; // 取出该地址的对象
if (obj == prev) { // 对象没有变化return
return;
}
objc_retain(obj); // retain新对象
*location = obj; // 新对象存入该地址
objc_release(prev);// 释放老对象
}
Autoreleasepool
Autorelease pool implementation 定义
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
Autoreleasepool 是一个以栈为节点的双向链表结构。
class AutoreleasePoolPage
{
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic;
id *next;
pthread_t const thread;
// 双向链表结构,AutoreleasePoolPage为每个节点结构
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
// SIZE-sizeof(*this) bytes of contents follow
static void * operator new(size_t size) {
return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
}
static void operator delete(void * p) {
return free(p);
}
......
}
在@autoreleasepool {}结构中alloc的对象会被加入自动释放池中
- (void)testAutoreleasePool
{
@autoreleasepool {
TestObject *obj = [[TestObject alloc] init];
}
}
可见先调用了objc_autoreleasePoolPush 函数执行完又调用了objc_autoreleasePoolPop 。
是push入栈了什么?push操作发生了什么?
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);
}
}
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
可见push的返回值是加在栈头的哨兵nil的地址,在这之后加入page的指针指向的对象都会在pop时释放掉。
那autoreleasepool和autorelease/runloop什么关系呢?
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。
加上Autorelease Pool后就是出了作用域就释放。