和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 InlineRefCounts;
template
class RefCounts {
std::atomic refCounts;
...
}
而InlineRefCountBits
,是RefCountBitsT
类的别名:
typedef RefCountBitsT InlineRefCountBits;
RefCountBitsT
,有bits
属性:
template
class RefCountBitsT {
...
typedef typename RefCountBitsInt::Type
BitsType;
...
BitsType bits;
...
}
template <>
struct RefCountBitsInt {
//类型
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
class RefCounts {
std::atomic 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::formWeakReference()
{
//创建 sideTable
auto side = allocateSideTable(true);
if (side)
// 如果创建成功,则增加弱引用
return side->incrementWeak();
else
return nullptr;
}
进入allocateSideTable
:
template <>
HeapObjectSideTableEntry* RefCounts::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 object;
SideTableRefCounts refCounts;
...
}
上述源码中可知,弱引用对象对应的引用计数refCounts
是SideTableRefCounts
类型,而强引用对象的是InlineRefCounts
类型。
接下来我们看看SideTableRefCounts
:
typedef RefCounts SideTableRefCounts;
继续搜索SideTableRefCountBits
里面包含了成员
uint32_t weakBits
,即一个32位域
的信息。
二 通过InlineRefCountBits
初始化散列表的数据
LLVM_ATTRIBUTE_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
: bits((reinterpret_cast(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
这里继承的bits
构造方法,而bits
定义
BitsType bits;
typedef typename RefCountBitsInt::Type
BitsType;
和强引用
一样,来到了RefCountBitsInt
,这个之前分析过,就是uint64_t
类型,存的是64位域
信息。
综合1
和 2
两点的论述可得出:64位
用于记录 原有引用计数
;32位
用于记录 弱引用计数
。
为何t
的引用计数是:0xc0000000200e08ba
?
上述分析中我们知道,在 InlineRefCountBits
初始化散列表的数据时,执行了(reinterpret_cast
这句代码,而
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..
尝试:
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:)
获取的是真实类型
。