OC中所有的實例對象、類對象和元類對象中都一個名為isa
的成員變量,他們通常把它叫isa指針
,既然是指針,那里面存儲的應該就是一個地址。在以前的32位
系統中,isa
確實就是存儲的一個地址,實例對象的isa
存儲的是其對應的類對象的地址,類對象的isa
存儲的是其對應的元類對象的地址,元類對象的isa
存儲的是根元類對象的地址。
但是在現在的64位系統(arm64架構)中,蘋果對isa
做了優化,里面除了存儲一個地址外還存儲了很多其他信息。一個指針占8個字節,也就是64位,蘋果只用了其中的33位
來存儲地址,其余31位用來存儲其他信息。下面我們來看下在arm64
架構中關于isa的定義:
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
上面信息中定義的像ISA_MASK
這種常量我們不用管,這些都是程序在操作isa
的過程中要用到的,比如我們將isa
和ISA_MASK
進行按位與運算isa & ISA_MASK
就可以得到isa中存儲的地址值。
我們主要關注一下uintptr_t
類型數據:
-
nonpointer
:(isa的第0位(isa的最后面那位),共占1位)。為0表示這個isa
只存儲了地址值,為1表示這是一個優化過的isa
。 -
has_assoc
:(isa的第1位,共占1位)。記錄這個對象是否是關聯對象,沒有的話,釋放更快。 -
has_cxx_dtor
:(isa的第2位,共占1位)。記錄是否有c++的析構函數,沒有的話,釋放更快。 -
shiftcls
:(isa的第3-35位,共占33位)。記錄類對象或元類對象的地址值。 -
magic
:(isa的第36-41位,共占6位),用于在調試時分辨對象是否完成初始化。 -
weakly_referenced
:(isa的第42位,共占1位),用于記錄該對象是否被弱引用或曾經被弱引用過,沒有被弱引用過的對象可以更快釋放。 -
deallocating
:(isa的第43位,共占1位),標志對象是否正在釋放內存。 -
has_sidetable_rc
:(isa的第44位,共占1位),用于標記是否有擴展的引用計數。當一個對象的引用計數比較少時,其引用計數就記錄在isa
中,當引用計數大于某個值時就會采用sideTable
來協助存儲引用計數。 -
extra_rc
:(isa的第45-63位,共占19位),用來記錄該對象的引用計數值-1(比如引用計數是5的話這里記錄的就是4)。這里總共是19位,如果引用計數很大,19位存不下的話就會采用sideTable
來協助存儲,規則如下:當19位存滿時,會將19位的一半(也就是上面定義的RC_HALF
)存入sideTable
中,如果此時引用計數又+1,那么是加在extra_rc
上,當extra_rc
又存滿時,繼續拿出RC_HALF
的大小放入sideTable
。當引用計數減少時,如果extra_rc
的值減少到了0,那就從sideTable
中取出RC_HALF
大小放入extra_rc
中。綜上所述,引用計數不管是增加還是減少都是在extra_rc
上進行的,而不會直接去操作sideTable
,這是因為sideTable
中有個自旋鎖,而引用計數的增加和減少操作是非常頻繁的,如果直接去操作sideTable
會非常影響性能,所以這樣設計來盡量減少對sideTable
的訪問。