和OC一樣,Swift中也是通過引用計數(shù)的方式來管理對象的內(nèi)存的。在Swift類 結(jié)構(gòu)探究中,分析過引用計數(shù)refCounts,它是RefCounts類型,class類型,占8字節(jié)大小。
一、強引用
class Animal {
var age: Int = 10
var name: String = "dog"
}
var t = Animal()
var t1 = t
var t2 = t
斷點,查看t的的內(nèi)存,refCounts是0x0000000600000003:

1.1
從HeapObject開始分析引用計數(shù)的真正表示形態(tài),源碼 HeapObject -> InlineRefCounts:
struct HeapObject {
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
...
}
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
進(jìn)入InlineRefCounts定義,是RefCounts類型的別名,而RefCounts是模板類,真正決定的是傳入的類型InlineRefCountBits:
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
而InlineRefCountBits,是RefCountBitsT類的別名:
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
RefCountBitsT,有bits屬性:
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
...
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
...
BitsType bits;
...
}
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
//類型
typedef uint64_t Type;
typedef int64_t SignedType;
};
其中bits其實質(zhì)是將RefCountBitsInt中的type屬性取了一個別名,所以bits的真正類型是uint64_t,即64位整型數(shù)組。
1.2 對象創(chuàng)建
然后,繼續(xù)分析swift中對象創(chuàng)建的底層方法swift_allocObject,源碼:
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
...
new (object) HeapObject(metadata);
...
}
<!--構(gòu)造函數(shù)-->
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
進(jìn)入Initialized定義,是一個枚舉,其對應(yīng)的refCounts方法中,干事的是RefCountBits:
enum Initialized_t { Initialized };
//對應(yīng)的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
進(jìn)入RefCountBits定義,也是一個模板定義:
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
重點:最終,真正的初始化地方是下面這個,實際上是做了一個位域操作,根據(jù)的是Offsets:
LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
referCounts的本質(zhì)是RefCountBitsInt中的type屬性,而RefCountBitsInt又是模板類RefCountBitsT的模板類型T,所以RefCountBits(0, 1)實質(zhì)調(diào)用的是模板類RefCountBitsT的構(gòu)造方法,strongExtraCount傳值為0,unownedCount傳值為1。
RefCountsBit的結(jié)構(gòu)圖,如下所示:

isImmortal(0)
UnownedRefCount(1-31):unowned的引用計數(shù)
isDeinitingMask(32):是否進(jìn)行釋放操作
StrongExtraRefCount(33-62): 強引用計數(shù)
UseSlowRC(63)
其中需要重點關(guān)注UnownedRefCount和StrongExtraRefCount。至此,我們分析一下上面的引用計數(shù)值0x0000000600000003,用二進(jìn)制展示:

如圖,
33位置開始的強引用計數(shù)StrongExtraRefCount為0011,轉(zhuǎn)換成十進(jìn)制就是3。下面通過
sil驗證一下:
關(guān)于
copy_addr,sil文檔有解釋,其實現(xiàn)是對object的引用計數(shù)作+1操作。有興趣的可以自己去查一下。需要注意的是:
var t = Animal()此時已經(jīng)有了引用計數(shù),即,OC中創(chuàng)建實例對象時為0;Swift中創(chuàng)建實例對象時默認(rèn)為1。可以通過
CFGetRetainCount獲取引用計數(shù):
二、弱引用
class Animal {
var age: Int = 10
var name: String = "cat"
var dog: Dog?
}
class Dog {
var age = 20
var animal: Animal?
}
func test(){
var t = Animal()
weak var t1 = t
print("end")
}
test()
查看 t的引用計數(shù):

弱引用聲明的變量是一個可選值,因為在程序運行過程中是允許將當(dāng)前變量設(shè)置為nil的。
在t1處加斷點,查看匯編,發(fā)現(xiàn):swift_weakInit。查看源碼,這個函數(shù)是由WeakReference來調(diào)用的,相當(dāng)于weak字段在編譯器聲明過程中就自定義了一個WeakReference的對象,其目的在于管理弱引用。
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
進(jìn)入nativeInit:
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
進(jìn)入formWeakReference,創(chuàng)建sideTable:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
//創(chuàng)建 sideTable
auto side = allocateSideTable(true);
if (side)
// 如果創(chuàng)建成功,則增加弱引用
return side->incrementWeak();
else
return nullptr;
}
進(jìn)入allocateSideTable:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
// 1、先拿到原本的引用計數(shù)
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// Preflight failures before allocating a new side table.
if (oldbits.hasSideTable()) {
// Already have a side table. Return it.
return oldbits.getSideTable();
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
// Preflight passed. Allocate a side table.
// FIXME: custom side table allocator
//2、創(chuàng)建sideTable
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
// 3、將創(chuàng)建的地址給到InlineRefCountBits
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
// Already have a side table. Return it and delete ours.
// Read before delete to streamline barriers.
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
side->initRefCounts(oldbits);
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
總結(jié):
- 先拿到原本的引用計數(shù);
- 創(chuàng)建
sideTable;- 將創(chuàng)建的
sideTable地址給InlineRefCountBits,并查看其初始化方法,根據(jù)sideTable地址作了偏移操作并存儲到內(nèi)存,相當(dāng)于將sideTable直接存儲到了64位的變量中。
通過上面的底層流程分析,我們可以get到關(guān)鍵的2點:
一 通過HeapObjectSideTableEntry初始化散列表
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
...
}
上述源碼中可知,弱引用對象對應(yīng)的引用計數(shù)refCounts是SideTableRefCounts類型,而強引用對象的是InlineRefCounts類型。
接下來我們看看SideTableRefCounts:
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
繼續(xù)搜索SideTableRefCountBits

里面包含了成員
uint32_t weakBits,即一個32位域的信息。
二 通過InlineRefCountBits初始化散列表的數(shù)據(jù)
LLVM_ATTRIBUTE_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
這里繼承的bits構(gòu)造方法,而bits定義
BitsType bits;
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
和強引用一樣,來到了RefCountBitsInt,這個之前分析過,就是uint64_t類型,存的是64位域信息。
綜合1 和 2兩點的論述可得出:64位 用于記錄 原有引用計數(shù);32位 用于記錄 弱引用計數(shù)。
為何t的引用計數(shù)是:0xc0000000200e08ba?
上述分析中我們知道,在 InlineRefCountBits初始化散列表的數(shù)據(jù)時,執(zhí)行了(reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits這句代碼,而
static const size_t SideTableUnusedLowBits = 3;
對side右移了3位,所以此時,將0xc0000000200e08ba 左移3位是0x1007045D0,就是散列表的地址。再x/8g查看:

三、循環(huán)引用
var age = 10
let clourse = {
age += 1
}
clourse()
print(age)
//打印結(jié)果
11
從輸出結(jié)果中可以看出:閉包內(nèi)部對變量的修改會改變外部原始變量的值,原因是閉包會捕獲外部變量,這個與OC中的block是一致的。
deinit
class Animal {
var age = 10
deinit {
print("Animal deinit")
}
}
func test(){
var t = Animal()
let clourse = {
t.age += 10
}
clourse()
print(t.age)
}
test()
//打印結(jié)果
//Animal deinit
可見,deinit是在當(dāng)前實例對象即將被回收時觸發(fā)。
接下來,我們把age放到類中,閉包中再去修改時會怎樣:
class Animal {
var age = 10
deinit {
print("Animal deinit")
}
}
var t = Animal()
let clourse = {
t.age += 10
}
clourse()
print(t.age)
//打印結(jié)果
//20
//Animal deinit
在Animal類增加閉包屬性:
class Animal {
var age = 10
var completionBlock: (() ->())?
deinit {
print("Animal deinit")
}
}
func test(){
var t = Animal()
t.completionBlock = {
t.age += 10
}
print(t.age)
}
test()
//打印結(jié)果
//10
從運行結(jié)果發(fā)現(xiàn),t.age還是1,并且沒有執(zhí)行deinit方法,這里存在循環(huán)引用。
循環(huán)引用的處理
1 weak修飾閉包傳入的參數(shù)。weak修飾后的變量是optional類型,所以t?.age += 1。
func test(){
var t = Animal()
t.completionBlock = { [weak t] in
t?.age += 10
}
print(t.age)
}
2 unowned修飾閉包參數(shù)
func test(){
var t = Animal()
t.completionBlock = { [unowned t] in
t.age += 10
}
print(t.age)
}
[weak t] 或 [unowned t],稱為捕獲列表。定義在參數(shù)列表之前,如果使用捕獲列表,那么即使省略參數(shù)名稱、參數(shù)類型和返回類型,也必須使用in關(guān)鍵字。
捕獲列表的值
func test(){
var age = 1
var height = 1.0
let clourse = {[age] in
print(age)
print(height)
}
age = 10
height = 1.85
clourse()
}
test()
//打印結(jié)果
//1
//1.85
如上,age值改變了,height未被捕獲,值未變。
捕獲列表特點: 捕獲列表中的常量是
值拷貝,而不是引用拷貝,因此,它是只讀的,即不可修改。
Runtime
Swift是一門靜態(tài)語言,本身不具備動態(tài)性,不像OC有Runtime運行時的機(jī)制(此處指OC提供運行時API供程序員操作)。但由于Swift兼容OC,所以可以轉(zhuǎn)成OC類和函數(shù),利用OC的運行時機(jī)制,來實現(xiàn)動態(tài)性。
class Animal {
var age: Int = 18
func eat(){
print("eat")
}
}
let t = Animal()
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(Animal.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodList?[i]{
let methodName = method_getName(method)
print("=======方法名稱:\(methodName)")
}else{
print("not found method")
}
}
var count: UInt32 = 0
let proList = class_copyPropertyList(Animal.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[i]{
let propertyName = String(utf8String: property_getName(property))
print("------成員屬性名稱:\(propertyName!)")
}else{
print("沒有找到你要的屬性")
}
}
print("test run")
}
test()
嘗試:
1、以上代碼,沒有打印。
2、給方法和屬性添加@objc修飾,可以打印。
3、類Animal繼承NSObject,不用@objc修飾。只打印了初始化方法,因為在swift.h文件中暴露出來的只有init方法。
注意:如果要讓OC調(diào)用,那么必須 繼承NSObject + @objc修飾。
4、去掉@objc修飾,改成dynamic修飾 + NSObject,同3。
5、@objc修飾 + dynamic修飾 + NSObject。
關(guān)于方法調(diào)用,參考Swift方法調(diào)用
補充
AnyObject:代表任意類的instance,類的類型,類遵守的協(xié)議,但struct?不行。
Any:代表任意類型,包括funcation類型或者Optional類型。
AnyClass:代表任意實例的類型。
T.self:如果T為實例對象,返回的就是它本身;T是類,返回的是Metadata。
type(of:):用于獲取一個值的動態(tài)類型,編譯期時,value的類型是Any類型;運行期時,type(of:)獲取的是真實類型。