swift引用计数:
-
swift对象都是以HeapObject为模板创建,其中HeapObject的模板中第二个元素,是refCount引用计数属性,该属性记录了strong(强引用计数)和unowned(弱引用计数)等信息。 -
weak修饰的对象,会另外生成WeakReference对象,内部HeapObjectSideTableEntry散列表类- - 在原heapObject类的基础上,重新记录了refCount(管理strong和unowned引用计数)并新增了weakBits弱引用计数。
swift与OC强引用计数对比
OC中创建实例对象时为0
swift中创建实例对象时默认为1
1. Swift三大引用计数(strong、unowned、weak)

- 不管是哪种引用,持有的都是原对象(从p到p5内存地址可以看出)
- 在每一行执行完后,x/4g打印p对象内存信息,在第二个地址上,可以清晰感受到,
强引用和无主引用的引用计数在有规律的增加,而弱引用却没有变化。
refCount内存布局

isImmortal(0)
UnownedRefCount(1-31): unowned的引用计数
isDeinitingMask(32):是否进行释放操作
StrongExtraRefCount(33-62): 强引用计数
UseSlowRC(63)
重点关注UnownedRefCount和StrongExtraRefCount

总结
对于HeapObject来说,其refCounts有两种:
- 无弱引用:
strongCount+unownedCount - 有弱引用:
object+xxx+(strongCount + unownedCount)+weakCount
HeapObject {
InlineRefCountBit {strong count + unowned count }
HeapObjectSideTableEntry{
HeapObject *object
xxx
strong Count + unowned Count(uint64_t)//64位
weak count(uint32_t)//32位
}
}
弱引用
- 我们知道
swift是使用ARC(自动引用计数管理)的。如果产生循环引用,我们必须有弱引用机制去打破循环。
swift中的
弱引用,使用weak修饰。与OC不同的是:
OC:
弱引用计数是存放在全局维护的散列表中,isa中会记录是否使用了散列表。
在引用计数为0时,自动触发dealloc,会检查并清空当前对象的散列表计数。
swift:
弱引用计数也是存放在散列表中,但这个散列表不是全局的。* 如果对象`没有`使用`weak`弱引用,就是单纯的`HeapObject`对象,`没有散列表`。 * 如果使用`weak`弱引用,会变为`WeakReference`对象。这是一个`Optionl(可空对象)`。其结构中自带`散列表计数`区域。 但`swift`的`散列表`与`refCount`无关联。当`强引用计数`为`0`时,不会触发`散列表`的清空。而是在`下次访问`发现`当前对象不存在(为nil)`时,会清空`散列表计数`。
下面,我们通过案例和源码来分析swift的弱引用: WeakReference对象和内存结构
案例:
- 可以发现:
weak修饰前,p对象是HeapObject类型,可从refCount中看出强引用计数和无主引用计数。
weak修饰后,p对象的类型变了image
- 可以看到
weak修饰的p1对象,变成了optinal可选值。
(不难理解,weak修饰的对象,不会改变原对象的引用计数,只是多一层可空的状态)image
断点,汇编可以看到swift_weakInit初始化,swift_weakDestroy释放。image
常规对象与弱引用对象区别:image
2. 内存管理 - 循环引用
主要是研究闭包捕获外部变量,以下面代码为例
var age = 10
let clourse = {
age += 1
}
clourse()
print(age)
<!--打印结果-->
11
从输出结果中可以看出:闭包内部对变量的修改将会改变外部原始变量的值,主要原因是闭包会捕获外部变量,这个与OC中的block是一致的
- 定义一个类,在test函数作用域消失后,会执行init
class CJLTeacher {
var age = 18
//反初始化器(当前实例对象即将被回收)
deinit {
print("CJLTeacher deinit")
}
}
func test(){
var t = CJLTeacher()
}
test()
<!--打印结果-->
CJLTeacher deinit
- 修改例子,通过闭包修改其属性值
class CJLTeacher {
var age = 18
//反初始化器(当前实例对象即将被回收)
deinit {
print("CJLTeacher deinit")
}
}
var t = CJLTeacher()
let clourse = {
t.age += 1
}
clourse()
<!--打印结果-->
11
- 【修改1】将上面例子修改为如下,其中闭包是否对t有强引用?
class CJLTeacher {
var age = 18
deinit {
print("CJLTeacher deinit")
}
}
func test(){
var t = CJLTeacher()
let clourse = {
t.age += 1
}
clourse()
}
test()
<!--运行结果-->
CJLTeacher deinit
运行结果发现,闭包对 t 并没有强引用
- 【修改2】继续修改例子为如下,是否有强引用?
class CJLTeacher {
var age = 18
var completionBlock: (() ->())?
deinit {
print("CJLTeacher deinit")
}
}
func test(){
var t = CJLTeacher()
t.completionBlock = {
t.age += 1
}
}
test()
从运行结果发现,没有执行deinit方法,即没有打印CJLTeacher deinit,所以这里有循环引用

循环引用解决方法
有两种方式可以解决swift中的循环引用
- 【方式一】使用
weak修饰闭包传入的参数,其中参数的类型是optional
func test(){
var t = CJLTeacher()
t.completionBlock = { [weak t] in
t?.age += 1
}
}
- 【方式二】使用
unowned修饰闭包参数,与weak的区别在于unowned不允许被设置为nil,即总是假定有值的
func test(){
var t = CJLTeacher()
t.completionBlock = { [unowned t] in
t.age += 1
}
}
捕获列表
[weak t] / [unowned t]在swift中被称为捕获列表定义在参数列表之前
【书写方式】捕获列表被写成用逗号括起来的表达式列表,并用方括号括起来
如果使用捕获列表,则即使省略参数名称、参数类型和返回类型,也必须使用
in关键字[weak t]就是取t的弱引用对象 类似weakself
请问下面代码的clourse()调用后,输出的结果是什么?
func test(){
var age = 0
var height = 0.0
//将变量age用来初始化捕获列表中的常量age,即将0给了闭包中的age(值拷贝)
let clourse = {[age] in
print(age)
print(height)
}
age = 10
height = 1.85
clourse()
}
<!--打印结果-->
0
1.85
所以从结果中可以得出:对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量/变量,来初始化捕获列表中定义的常量。有以下几点说明:
捕获列表中的常量是
值拷贝,而不是引用捕获列表中的常量的相当于复制了变量age的值
捕获列表中的常量是只读的,即不可修改
3、 swift中Runtime探索
请问下面代码,会打印方法和属性吗?
class CJLTeacher {
var age: Int = 18
func teach(){
print("teach")
}
}
let t = CJLTeacher()
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(CJLTeacher.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(CJLTeacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[I]{
let propertyName = property_getName(property)
print("属性成员属性:\(property)")
}else{
print("没有找到你要的属性")
}
}
print("test run")
}
test()
运行结果如下,发现并没有打印方法和属性

-
【尝试1】如果给属性 和 方法 都加上 @objc,可以打印吗?
image从运行结果看,是可以打印,但是由于类并没有暴露给OC,所以OC是无法使用的,这样做是没有意义的
-
【尝试2】如果swift的类
继承NSObject,没有@objc修饰属性和方法,是否可以打印全部属性+方法?image从结果发现获取的只有
init方法,主要是因为在swift.h文件中暴露出来的只有init方法 -
如果想让OC能使用,必须类
继承NSObject+@objc修饰属性、方法image -
如果
去掉@objc修饰属性,将方法改成dynamic修饰,是否可以打印方法?image从结果可以看出,依旧不能被OC获取到,需要修改为
@objc dynamic修饰image
结论
对于纯swift类来说,没有
动态特性dynamic(因为swift是静态语言),方法和属性不加任何修饰符的情况下,已经不具备runtime特性,此时的方法调度,依旧是函数表调度即V_Table调度对于纯swift类,方法和属性添加
@objc标识的情况下,可以通过runtime API获取到,但是在OC中是无法进行调度的,原因是因为swift.h文件中没有swift类的声明对于
继承自NSObject类来说,如果想要动态的获取当前属性+方法,必须在其声明前添加@objc关键字,如果想要使用方法交换,还必须在属性+方法前添加dynamic关键字,否则当前属性+方法只是暴露给OC使用,而不具备任何动态特性
objc源码验证
(由于xcode12.2暂时无法运行objc源码,下列验证图片仅供参考)
-
进入
class_copyMethodList源码,断住,查看此时的cls,其中data()存储类的信息image -
进入
data,打印bits、superclassimage从这里可以得出
swift中有默认基类,即_SwiftObject -
打印methods
image swift源码中搜索
_SwiftObject,继承自NSObject,在内存结构上与OC基本类似的
#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
@private
Class isa;
//refCounts
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}
-
在swift类的结构中,其中
TargetAnyClassMetadata继承自TargetHeapMetaData,其中只有一个属性kind,TargetAnyClassMetadata有四个属性:isa、superclass、cacheData、data即bitsimage所以swift为了保留和OC交互,其在底层存储的数据结构上和OC是一致的
objc源码中搜索
swift_class_t,继承自objc_class,保留了OC模板类的4个属性,其次才是自己的属性
struct swift_class_t : objc_class {
uint32_t flags;
uint32_t instanceAddressOffset;
uint32_t instanceSize;
uint16_t instanceAlignMask;
uint16_t reserved;
uint32_t classSize;
uint32_t classAddressOffset;
void *description;
// ...
void *baseAddress() {
return (void *)((uint8_t *)this - classAddressOffset);
}
};
问题:为什么继承NSObject?:必须通过NSObject声明,来帮助编译器判断,当前类是一个和OC交互的类
4、元类型、AnyClass、Self
AnyObject
-
AnyObject:代表任意类的instance、类的类型、仅类遵守的协议
class CJLTeacher: NSObject {
var age: Int = 18
}
var t = CJLTeacher()
//此时代表的就是当前CJLTeacher的实例对象
var t1: AnyObject = t
//此时代表的是CJLTeacher这个类的类型
var t2: AnyObject = CJLTeacher.self
//继承自AnyObject,表示JSONMap协议只有类才可以遵守
protocol JSONMap: AnyObject { }
例如如果是结构体遵守协议,会报错

需要将struct修改成class
//继承自AnyObject,表示JSONMap协议只有类才可以遵守
protocol JSONMap: AnyObject {
}
class CJLJSONMap: JSONMap {
}
Any
-
Any:代表任意类型,包括 function类型 或者Optional类型,可以理解为AnyObject是Any的子集
//如果使用AnyObject会报错,而Any不会
var array: [Any] = [1, "cjl", "", true]
AnyClass
-
AnyClass:代表任意实例的类型,类型是AnyObject.Type- 查看定义,是
public typealias AnyClass = AnyObject.Type
- 查看定义,是
T.self & T.Type
-
T.self:如果T是
实例对象,返回的就是它本身如果
T是类,那么返回的是MetaData
T.Type:一种类型,T.self是T.Type类型
//此时的self类型是 CJLTeacher.Type
var t = CJLTeacher.self
打印结果如下

- 查看t1、t2存储的是什么?
var t = CJLTeacher()
//实例对象地址:实例对象.self 返回实例对象本身
var t1 = t.self
//存储metadata元类型
var t2 = CJLTeacher.self

type(of:)
-
type(of:):用来获取一个值的动态类型
<!--demo1-->
var age = 10 as NSNumber
print(type(of: age))
<!--打印结果-->
__NSCFNumber
<!--demo2-->
//value - static type 静态类型:编译时期确定好的
//type(of:) - dynamic type:Int
var age = 10
//value的静态类型就是Any
func test(_ value: Any){
print(type(of: value))
}
test(age)
<!--打印结果-->
Int
实践
demo1
请问下面这段代码的打印结果是什么?
class CJLTeacher{
var age = 18
var double = 1.85
func teach(){
print("LGTeacher teach")
}
}
class CJLPartTimeTeacher: CJLTeacher {
override func teach() {
print("CJLPartTimeTeacher teach")
}
}
func test(_ value: CJLTeacher){
let valueType = type(of: value)
value.teach()
print(value)
}
var t = CJLPartTimeTeacher()
test(t)
<!--打印结果-->
CJLPartTimeTeacher teach
CJLTest.CJLPartTimeTeacher
demo2
请问下面代码的打印结果是什么?
protocol TestProtocol {
}
class CJLTeacher: TestProtocol{
var age = 18
var double = 1.85
func teach(){
print("LGTeacher teach")
}
}
func test(_ value: TestProtocol){
let valueType = type(of: value)
print(valueType)
}
var t = CJLTeacher()
let t1: TestProtocol = CJLTeacher()
test(t)
test(t1)
<!--打印结果-->
CJLTeacher
CJLTeacher
- 如果将test中参数的类型修改为泛型,此时的打印是什么?
func test<T>(_ value: T){
let valueType = type(of: value)
print(valueType)
}
<!--打印结果-->
CJLTeacher
TestProtocol
从结果中发现,打印并不一致,原因是因为当有协议、泛型时,当前的编译器并不能推断出准确的类型,需要将value转换为Any,修改后的代码如下:
func test<T>(_ value: T){
let valueType = type(of: value as Any)
print(valueType)
}
<!--打印结果-->
CJLTeacher
CJLTeacher
demo3
在上面的案例中,如果class_getClassMethod中传t.self,可以获取方法列表吗?
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(t.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(CJLTeacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[I]{
let propertyName = property_getName(property)
print("属性成员属性:\(property)")
}else{
print("没有找到你要的属性")
}
}
print("test run")
}
test()
从结果运行看,并不能,因为t.self是实例对象本身,即CJLTeacher,并不是CJLTeacher.Type类型
总结
当无弱引用时,
HeapObject中的refCounts等于strongCount + unownedCount当有弱引用时,
HeapObject中的refCounts等于object + xxx + (strongCount + unownedCount) + weakCount循环应用可以通过
weak / unowned修饰参数来解决swift中闭包的
捕获列表是值拷贝,即深拷贝,是一个只读的常量swift由于是
静态语言,所以属性、方法在不加任何修饰符的情况下时是不具备动态性即Runtime特性的,此时的方法调度是V-Table函数表调度如果想要
OC使用swift类中的方法、属性,需要class继承NSObject,并使用@objc修饰如果想要使用方法交换,除了
继承NSObject+@objc修饰,还必须使用dynamic修饰Any:任意类型,包括function类型、optional类型AnyObject:任意类的instance、类的类型、仅类遵守的协议,可以看作是Any的子类AnyClass:任意实例类型,类型是AnyObject.TypeT.self:如果T是实例对象,则表示它本身,如果是类,则表示metadata.T.self的类型是T.Type













