OC每一個(gè)方法名是一個(gè)SEL ,根據(jù)SEL可以查找到函數(shù)體存放的內(nèi)存地址IMP。查找的原理請(qǐng)參考iOS OC運(yùn)行時(shí)詳解。
讓我們跟著一個(gè)方法,一步步探究以下問題
- 函數(shù)執(zhí)行前后做了什么
- 函數(shù)內(nèi)部成員的內(nèi)存如何管理
@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]; // 臨時(shí)變量
obj.array = [NSArray arrayWithObject:@"test"]; // 引用一次
// 函數(shù)結(jié)束,obj會(huì)在什么時(shí)候釋放?
}
@end
跟蹤TestObject的dealloc調(diào)用棧發(fā)現(xiàn)objc_object::sidetable_release(bool)回調(diào)了dealloc

接下查看objc源碼來看sidetable_release是做什么用的。
從objc_object結(jié)構(gòu)體中可以發(fā)現(xiàn)這個(gè)函數(shù)。說明每個(gè)OC對(duì)象都有
id sidetable_retain();
uintptr_t sidetable_release(bool performDealloc = true);
兩個(gè)函數(shù)
#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];// 找到當(dāng)前對(duì)象的管理表,包含引用計(jì)數(shù)map和當(dāng)前對(duì)象的弱引用表
bool do_dealloc = false;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);// 引用計(jì)數(shù)表中以當(dāng)前對(duì)象的地址為Key查找到當(dāng)前對(duì)象的引用計(jì)數(shù)表
if (it == table.refcnts.end()) { // 引用計(jì)數(shù)了
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; // 引用計(jì)數(shù) -= SIDE_TABLE_RC_ONE
}
table.unlock();
if (do_dealloc && performDealloc) {// 在此處調(diào)用dealloc
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
struct SideTable {
spinlock_t slock;
// refcnt的詳細(xì)解釋
// 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)判斷了對(duì)象的引用計(jì)數(shù),達(dá)到釋放閾值并進(jìn)行標(biāo)記。未達(dá)到釋放閾值的計(jì)數(shù)器-1,不調(diào)用dealloc函數(shù)。直到當(dāng)前對(duì)象調(diào)用release,并達(dá)到閾值,才會(huì)被釋放。
這里可以在上面的代碼中得到證實(shí)。延時(shí)函數(shù)內(nèi)部強(qiáng)引用了obj對(duì)象,直到dispatch_after的block執(zhí)行完成后才會(huì)繼續(xù)走dealloc.
根據(jù)現(xiàn)有知識(shí),OC調(diào)用某函數(shù),其實(shí)給這個(gè)函數(shù)地址發(fā)消息。比如:
- ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
閱讀objc-msg-arm64.s源碼可以發(fā)現(xiàn), objc_msgSend的匯編實(shí)現(xiàn)。
GetClassFromIsa_p16根據(jù)對(duì)象的isa指針緩存中獲取類,緩存不命中去執(zhí)行常規(guī)查找,找到執(zhí)行完,加入方法緩存。
下面分析執(zhí)行sideTable_release函數(shù)之前干了什么。調(diào)用來源有下面三個(gè)
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);
}
可以猜想一個(gè)對(duì)象調(diào)用了release操作,引用計(jì)數(shù)達(dá)到了閾值,就會(huì)回調(diào)dealloc。
讓我們繼續(xù)思考。MRC下函數(shù)的局部成員,誰(shuí)分配誰(shuí)釋放。ARC把這個(gè)過程集成到了Clang中。Objective-C Automatic Reference Counting (ARC).
- 編譯器到底什么時(shí)候把retain和release加入代碼的?
這就涉及到llvm里講的,所有權(quán)歸屬操作。
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.
// 我們 通過這個(gè)簡(jiǎn)單的函數(shù)看一下app可執(zhí)行文件的方法到底變成了什么
- (id)testTemporaryVariable
{
TestObject *obj = [[TestObject alloc] init];
obj.array = [NSArray arrayWithObject:@"test"];
return obj;
}
工程run 成功后,hopper查看生成的可執(zhí)行文件,找到對(duì)應(yīng)方法如下圖:

可以看出給obj賦值操作(setArray:)先調(diào)用了objc_retainAutoreleasedReturnValue方法,相當(dāng)于幫我們r(jià)etain一次,因?yàn)閍rc下的返回對(duì)象都是autorelease對(duì)象。該方法其實(shí)是對(duì)arrayWithObject:返回對(duì)象的引用計(jì)數(shù)管理,可以在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);
}
其實(shí)就是調(diào)用了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()對(duì)應(yīng)的sidetable_retain()方法。暫時(shí)略過這里。
函數(shù)最后執(zhí)行了objc_storeStrong方法,相當(dāng)于與調(diào)用了一次release。通過查看源碼可以發(fā)現(xiàn)這個(gè)函數(shù)做了哪些操作。
void
objc_storeStrong(id *location, id obj)
{
id prev = *location; // 取出該地址的對(duì)象
if (obj == prev) { // 對(duì)象沒有變化return
return;
}
objc_retain(obj); // retain新對(duì)象
*location = obj; // 新對(duì)象存入該地址
objc_release(prev);// 釋放老對(duì)象
}
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 是一個(gè)以棧為節(jié)點(diǎn)的雙向鏈表結(jié)構(gòu)。
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;
// 雙向鏈表結(jié)構(gòu),AutoreleasePoolPage為每個(gè)節(jié)點(diǎn)結(jié)構(gòu)
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 {}結(jié)構(gòu)中alloc的對(duì)象會(huì)被加入自動(dòng)釋放池中
- (void)testAutoreleasePool
{
@autoreleasepool {
TestObject *obj = [[TestObject alloc] init];
}
}

可見先調(diào)用了objc_autoreleasePoolPush 函數(shù)執(zhí)行完又調(diào)用了objc_autoreleasePoolPop 。
是push入棧了什么?push操作發(fā)生了什么?
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的指針指向的對(duì)象都會(huì)在pop時(shí)釋放掉。
那autoreleasepool和autorelease/runloop什么關(guān)系呢?
在沒有手加Autorelease Pool的情況下,Autorelease對(duì)象是在當(dāng)前的runloop迭代結(jié)束時(shí)釋放的,而它能夠釋放的原因是系統(tǒng)在每個(gè)runloop迭代中都加入了自動(dòng)釋放池Push和Pop。
加上Autorelease Pool后就是出了作用域就釋放。