SwiftSwift Swift基础

swift 进阶:内存管理 & Runtime

2021-03-01  本文已影响0人  欧德尔丶胡

swift 进阶之路:学习大纲

swift引用计数:

swift与OC强引用计数对比

1. Swift三大引用计数(strong、unowned、weak)

refCount内存布局

重点关注UnownedRefCountStrongExtraRefCount

总结

对于HeapObject来说,其refCounts有两种:

HeapObject {
    InlineRefCountBit {strong count + unowned count }

    HeapObjectSideTableEntry{
        HeapObject *object
        xxx
        strong Count + unowned Count(uint64_t)//64位
        weak count(uint32_t)//32位
    }
}

弱引用

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是一致的

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

class CJLTeacher {
    var age = 18
    deinit {
        print("CJLTeacher deinit")
    }
}

func test(){
    var t = CJLTeacher()
    let clourse = {
        t.age += 1
    }
    clourse()
}
test()

<!--运行结果-->
CJLTeacher deinit

运行结果发现,闭包对 t 并没有强引用

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,所以这里有循环引用

image

循环引用解决方法

有两种方式可以解决swift中的循环引用

func test(){
    var t = CJLTeacher()
    t.completionBlock = { [weak t] in
        t?.age += 1
    } 
}

func test(){
    var t = CJLTeacher()
    t.completionBlock = { [unowned t] in
        t.age += 1
    } 
}

捕获列表

请问下面代码的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

所以从结果中可以得出:对于捕获列表中的每个常量,闭包会利用周围范围内具有相同名称的常量/变量,来初始化捕获列表中定义的常量。有以下几点说明:

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()

运行结果如下,发现并没有打印方法和属性

image

结论

objc源码验证

(由于xcode12.2暂时无法运行objc源码,下列验证图片仅供参考)

#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;
}

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

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 { }

例如如果是结构体遵守协议,会报错

image

需要将struct修改成class

//继承自AnyObject,表示JSONMap协议只有类才可以遵守
protocol JSONMap: AnyObject {

}
class CJLJSONMap: JSONMap {

}

Any

//如果使用AnyObject会报错,而Any不会
var array: [Any] = [1, "cjl", "", true]

AnyClass

T.self & T.Type

//此时的self类型是  CJLTeacher.Type
var t = CJLTeacher.self

打印结果如下

var t = CJLTeacher()
//实例对象地址:实例对象.self 返回实例对象本身
var t1 = t.self
//存储metadata元类型
var t2 = CJLTeacher.self

image

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

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类型

总结

参考:https://www.jianshu.com/p/0cc765a325cb

上一篇 下一篇

猜你喜欢

热点阅读