和OC一样,Swift中也是通过引用计数的方式来管理对象的内存的。在Swift类 结构探究中,分析过引用计数refCounts,它是RefCounts类型,class类型,占8字节大小。
一、强引用
class Animal {
var age: Int = 10
var name: String = "dog"
}
var t = Animal()
var t1 = t
var t2 = t
断点,查看t的的内存,refCounts是0x0000000600000003:

1.1
从HeapObject开始分析引用计数的真正表示形态,源码 HeapObject -> InlineRefCounts:
struct HeapObject {
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
...
}
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
进入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其实质是将RefCountBitsInt中的type属性取了一个别名,所以bits的真正类型是uint64_t,即64位整型数组。
1.2 对象创建
然后,继续分析swift中对象创建的底层方法swift_allocObject,源码:
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
...
new (object) HeapObject(metadata);
...
}
<!--构造函数-->
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
进入Initialized定义,是一个枚举,其对应的refCounts方法中,干事的是RefCountBits:
enum Initialized_t { Initialized };
//对应的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
进入RefCountBits定义,也是一个模板定义:
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
重点:最终,真正的初始化地方是下面这个,实际上是做了一个位域操作,根据的是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的本质是RefCountBitsInt中的type属性,而RefCountBitsInt又是模板类RefCountBitsT的模板类型T,所以RefCountBits(0, 1)实质调用的是模板类RefCountBitsT的构造方法,strongExtraCount传值为0,unownedCount传值为1。
RefCountsBit的结构图,如下所示:

isImmortal(0)
UnownedRefCount(1-31):unowned的引用计数
isDeinitingMask(32):是否进行释放操作
StrongExtraRefCount(33-62): 强引用计数
UseSlowRC(63)
其中需要重点关注UnownedRefCount和StrongExtraRefCount。至此,我们分析一下上面的引用计数值0x0000000600000003,用二进制展示:

如图,
33位置开始的强引用计数StrongExtraRefCount为0011,转换成十进制就是3。下面通过
sil验证一下:
关于
copy_addr,sil文档有解释,其实现是对object的引用计数作+1操作。有兴趣的可以自己去查一下。需要注意的是:
var t = Animal()此时已经有了引用计数,即,OC中创建实例对象时为0;Swift中创建实例对象时默认为1。可以通过
CFGetRetainCount获取引用计数:
二、弱引用
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的引用计数:

弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil的。
在t1处加断点,查看汇编,发现:swift_weakInit。查看源码,这个函数是由WeakReference来调用的,相当于weak字段在编译器声明过程中就自定义了一个WeakReference的对象,其目的在于管理弱引用。
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
进入nativeInit:
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
进入formWeakReference,创建sideTable:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
//创建 sideTable
auto side = allocateSideTable(true);
if (side)
// 如果创建成功,则增加弱引用
return side->incrementWeak();
else
return nullptr;
}
进入allocateSideTable:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
// 1、先拿到原本的引用计数
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、创建sideTable
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
// 3、将创建的地址给到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;
}
总结:
- 先拿到原本的引用计数;
- 创建
sideTable;- 将创建的
sideTable地址给InlineRefCountBits,并查看其初始化方法,根据sideTable地址作了偏移操作并存储到内存,相当于将sideTable直接存储到了64位的变量中。
通过上面的底层流程分析,我们可以get到关键的2点:
一 通过HeapObjectSideTableEntry初始化散列表
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
...
}
上述源码中可知,弱引用对象对应的引用计数refCounts是SideTableRefCounts类型,而强引用对象的是InlineRefCounts类型。
接下来我们看看SideTableRefCounts:
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
继续搜索SideTableRefCountBits

里面包含了成员
uint32_t weakBits,即一个32位域的信息。
二 通过InlineRefCountBits初始化散列表的数据
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构造方法,而bits定义
BitsType bits;
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
和强引用一样,来到了RefCountBitsInt,这个之前分析过,就是uint64_t类型,存的是64位域信息。
综合1 和 2两点的论述可得出:64位 用于记录 原有引用计数;32位 用于记录 弱引用计数。
为何t的引用计数是:0xc0000000200e08ba?
上述分析中我们知道,在 InlineRefCountBits初始化散列表的数据时,执行了(reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits这句代码,而
static const size_t SideTableUnusedLowBits = 3;
对side右移了3位,所以此时,将0xc0000000200e08ba 左移3位是0x1007045D0,就是散列表的地址。再x/8g查看:

三、循环引用
var age = 10
let clourse = {
age += 1
}
clourse()
print(age)
//打印结果
11
从输出结果中可以看出:闭包内部对变量的修改会改变外部原始变量的值,原因是闭包会捕获外部变量,这个与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()
//打印结果
//Animal deinit
可见,deinit是在当前实例对象即将被回收时触发。
接下来,我们把age放到类中,闭包中再去修改时会怎样:
class Animal {
var age = 10
deinit {
print("Animal deinit")
}
}
var t = Animal()
let clourse = {
t.age += 10
}
clourse()
print(t.age)
//打印结果
//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()
//打印结果
//10
从运行结果发现,t.age还是1,并且没有执行deinit方法,这里存在循环引用。
循环引用的处理
1 weak修饰闭包传入的参数。weak修饰后的变量是optional类型,所以t?.age += 1。
func test(){
var t = Animal()
t.completionBlock = { [weak t] in
t?.age += 10
}
print(t.age)
}
2 unowned修饰闭包参数
func test(){
var t = Animal()
t.completionBlock = { [unowned t] in
t.age += 10
}
print(t.age)
}
[weak t] 或 [unowned t],称为捕获列表。定义在参数列表之前,如果使用捕获列表,那么即使省略参数名称、参数类型和返回类型,也必须使用in关键字。
捕获列表的值
func test(){
var age = 1
var height = 1.0
let clourse = {[age] in
print(age)
print(height)
}
age = 10
height = 1.85
clourse()
}
test()
//打印结果
//1
//1.85
如上,age值改变了,height未被捕获,值未变。
捕获列表特点: 捕获列表中的常量是
值拷贝,而不是引用拷贝,因此,它是只读的,即不可修改。
Runtime
Swift是一门静态语言,本身不具备动态性,不像OC有Runtime运行时的机制(此处指OC提供运行时API供程序员操作)。但由于Swift兼容OC,所以可以转成OC类和函数,利用OC的运行时机制,来实现动态性。
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调用,那么必须 继承NSObject + @objc修饰。
4、去掉@objc修饰,改成dynamic修饰 + NSObject,同3。
5、@objc修饰 + dynamic修饰 + NSObject。
关于方法调用,参考Swift方法调用
补充
AnyObject:代表任意类的instance,类的类型,类遵守的协议,但struct❌不行。
Any:代表任意类型,包括funcation类型或者Optional类型。
AnyClass:代表任意实例的类型。
T.self:如果T为实例对象,返回的就是它本身;T是类,返回的是Metadata。
type(of:):用于获取一个值的动态类型,编译期时,value的类型是Any类型;运行期时,type(of:)获取的是真实类型。
