您的位置:首页 > 移动开发 > Swift

Swift中的循环引用测试验证

2016-03-08 16:17 323 查看
文中代码运行环境:

swift版本: 2.1.1

Xcode版本: 7.2.1 (7C1002)

系统版本: OS X 10.11.3 (15D21)

ARC: Automatic Reference Counting

MRC: Mannul Reference Counting

swift语言在设计时, 使用LLVM编译器提供了ARC循环计数引用, 减轻了程序员在早期OC时代MRC需要手动控制引用计数的负担.

最简单的描述循环引用的模型是创建两个简单的类, 其中持有一个指向B类对象的成员, 同时B类持有一个指向A类对象的成员. demo如下:

//
//  ViewController.swift
//  ReferenceLoop
//
//  Created by linx on 16/3/8.
//  Copyright © 2016年 linx. All rights reserved.
//

import UIKit

class ObjA {
var link: ObjB?

init() {
print("ObjA.init()")
}

deinit {
print("ObjA.deinit")
}
}

class ObjB {
var link: ObjA?

init() {
print("ObjB.init()")
}

deinit {
print("ObjB.deinit")
}

}

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

let oa = ObjA()
let ob = ObjB()
oa.link = ob
ob.link = oa
}
}


运行输出结果:

ObjA.init()
ObjB.init()


对比参照组使用同上例子, 并取消ObjA与ObjB对象的互相指向关系

修改ViewController.viewDidLoad实现代码:

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

let oa = ObjA()
let ob = ObjB()
}
}


打印输出结果:

ObjA.init()
ObjB.init()
ObjB.deinit
ObjA.deinit


swift中还有一种常见的循环引用是在闭包中, 因为闭包等同于执行一段代码片段, 因为闭包的执行特性, 该闭包的调用时机不一定是在声明的时候就会执行到, 可能会在某个消息触发后, 执行闭包中的内容, 所以闭包在这种机制下, 可能就设计成, 闭包中所使用自身资源时(也就是self中的属性和方法)都是一种强引用关系, 以保证闭包在调用时, 对象是未被系统ARC自动释放的. 测试demo如下:

//
//  ViewController.swift
//  ReferenceLoop
//
//  Created by linx on 16/3/8.
//  Copyright © 2016年 linx. All rights reserved.
//

import UIKit

class ObjA {

var block: () -> () = {
self.abc()
}

init() {
print("ObjA.init()")
}

deinit {
print("ObjA.deinit")
}

func abc() {
print("ObjA.abc()")
}
}

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

let oa = ObjA()
}
}


运行输出如下:

ObjA.init()
ObjA.deinit


此时, 修改ViewController.viewDidLoad函数实现, 增加ObjA对象在闭包中引用到自身对象的实例方法.

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

let oa = ObjA()
ob.block()
}
}


运行输出结果如下:

ObjA.init()


在第一种循环引用的例子中, ObjA与ObjB相互持有对象引用的对象, 解决方法是在其中任何一方声明变量时, 增加weak关键字, 即可解决对象互相引用的问题.

在第二种循环引用的例子中, 解决闭包循环引用自身的问题方法是, 在声明闭包时, 增加[weak self]声明, 以表示不增加对自身self的引用计数, 这也是网上提高的比较多的一种方法, 自己在实际写这篇博客的时候, 发现也可以将self作为对象, 以参数的方式传入到闭包中, 这时, 不加上[weak self]也是可行的, 只是这种方法在实际使用中可能会徒增繁琐.两种闭包避免循环引用的代码如下:

//[weak self]
var block: (Void) -> Void { [weak self] in
self?.abc()    // 因为采用弱引用self, 闭包严格上来说, 不拥有self对象, 所以使用self?.abc(),而不是self!.abc()
}

//将self作为参数传入闭包中
var block: (obj: ObjA) -> Void { (oa) in
oa.abc()
}
// 调用时
let oa = ObjA()
oa.block(obj: oa)


由上述闭包循环引用的第二种解决方式中引申出一个新的问题, 一个新的小白问题, 对象作为函数参数时, 对引用计数是否有影响. 如何影响?

使用代码进行验证, 设计测试代码:

var value: ObjA? = ObjA()
func abc(obj: ObjA) {
obj.output()     // output()是ObjA类的一个实例方法
// 如果在调用obj.output之前, 将obj的引用计数减1, 那么obj.output()方法能否顺利执行, 以此检验函数参数是否会影响对象的引用计数问题
}
func abc(obj: ObjA) {
self.value = nil    // s1.
obj.output()        // s2.
}


打印输出结果:

ObjA.init()
ObjA.output()
ObjA.deinit


结果证明, abc方法中的obj.output()方法能够顺利执行,经过单步调试验证, 进入到abc函数, s1执行前, self.value和obj指向同一个内存地址, 满足我们实验的前提条件, s1执行后, s2执行前, self.value的值是nil, obj的值不变, 继续单步执行, obj.output()顺利执行, debug输出”ObjA.output()”, 最后ObjA.deinit执行.

经过如上实验, 得出大致结论, 因为类对象是传引用方式, 所以会增加其引用计数, 以确保在函数执行过程中, 持有该对象且对象不会被ARC机制释放其资源, 函数执行完毕后, 参数的引用计数减1.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  swift 循环引用 闭包