swift中?、!、weak与unowned的实际使用心得

相信很多人在一开始学swift的时候都像我一样对?,!,weak,unowned感到十分迷惑。
在这里大概讲述下这些东西在实际开发中的用处。

说真的,这些东西看中文翻译很多时候真的是然并卵,最好看英文的官方文档,然后不懂的查单词,慢点没所谓的,实在看不懂,多打几次代码就会熟悉很多了,这个是个人体会。
这里是官方文档

###swift变量的类型
swift变量可以理解为有3种:

  • 普通变量可选变量(optional)隐式解析可选变量(implicitly unwrapped optional)
  • 为了方便叙述,optional变量称为?变量,隐式解析可选变量称为!变量

####普通变量

  • 普通变量不能为nil,如果它是类,结构体中的属性,必须在init()函数中初始化。

####optional变量(?变量) 与 implicitly unwrapped optional 变量(!变量)

  • optional是这样定义的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum Optional<T> : Reflectable, NilLiteralConvertible {
case None
case Some(T)

/// Construct a `nil` instance.
init()

/// Construct a non-\ `nil` instance that stores `some`.
init(_ some: T)

/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
func map<U>(f: @noescape (T) -> U) -> U?

/// Returns `f(self)!` iff `self` and `f(self)` are not nil.
func flatMap<U>(f: @noescape (T) -> U?) -> U?

/// Returns a mirror that reflects `self`.
func getMirror() -> MirrorType

/// Create an instance initialized with `nil`.
init(nilLiteral: ())
}

关注点在这里:case,和some。

optional变量(?变量)为nil的时候,返回的是None

optional变量(?变量)不为nil的时候,返回的是Sonm(Type),即Type这个类型的普通变量

?变量使用的时候,可以接受nil值,也可以接受原本类型的值.

  • implicitly unwrapped optional是这样定义的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum ImplicitlyUnwrappedOptional<T> : Reflectable, NilLiteralConvertible {
case None
case Some(T)

/// Construct a `nil` instance.
init()

/// Construct a non-\ `nil` instance that stores `some`.
init(_ some: T)

/// Construct an instance from an explicitly unwrapped optional
/// (`T?`).
init(_ v: T?)

/// Create an instance initialized with `nil`.
init(nilLiteral: ())

/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
func map<U>(f: @noescape (T) -> U) -> U!

/// Returns `f(self)!` iff `self` and `f(self)` are not nil.
func flatMap<U>(f: @noescape (T) -> U!) -> U!

/// Returns a mirror that reflects `self`.
func getMirror() -> MirrorType
}

与optional类似,不过使用的时候它是自动在变量后面添加上一个!,默认对这个变进行unwrap,这种操作称为force unwrap。

implicitly unwrapped optional变量(!变量)为nil,返回None,并且编译器会报错。

implicitly unwrapped optional变量(!变量)不为nil,返回Some(Type),即Type类型的普通变量

这种变量使用起来比较方便,不用在变量后面添加?就可以对变量和函数进行调用,但是比较危险,一旦接受了nil值就会报错导致程序的崩溃。

  • 2个例子

最普通的Model定义:

1
2
3
4
5
6
7
8
9
10
11
12
class Model{
var a: String //普通变量不能为nil
var b: String

var c: String? //?变量不必初始化
var d: String! //!变量不用初始化

init(a: String , b: String) {
self.a = a //普通变量必须初始化
self.b = b
}
}

实在开发的时候,在ViewController中写变量,多把变量定义为!或者?类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class XXViewController : UIViewController {

var a: UILabel! //!类型告诉编译器这个变量不会为空,不用在init()中初始化
var b: UILabel? //?类型告诉编译器这个变量可能会为空,不用在init()中初始化
// var c: UILabel //VC中不是每次都重写init(),直接定义为普通变量会强制要求初始化

override func viewDidLoad() {
super.viewDidLoad()

a = UILabel()
b = UILabel()

b?.text = "b" //b = nil时,?后面的语句不执行
b!.text = "b" //!会强制解释(force unwrap)出b?变量中的Some(Type)类型,即普通的UILable类型,b = nil的时候,程序崩溃,因为b!是普通类型,而b!不能为nil
a.text = "a" //a = nil时,程序会在这里崩溃,因为a是!类型,会将a强制解释为普通类型,而普通类型不能为nil
}
}
  • 开发中值得注意的一点是optional chaining中optional的传递
1
2
3
4
5
6
7
8
var modelO: Model? = Model(a: "", b: "") //定义一个?变量
var model: Model! = Model(a: "", b: "") //定义一个!变量

modelO?.a = "1"
model.a = "2"

println(modelO?.a) //打印 "Optional("1")"
println(modelO!.a) //打印 "1"

Model类中a属性是一个普通变量,modelO是一个是一个?变量,使用optional chaining时,modelO?.a会是一个?变量,modelO的optional性质会传递到他的属性a中, 而modelO!.a则是解释成普通变量

weak、unowned与循环引用

  • weak对应optional,unowned 对应 implicitly unwrapped optional
  • weak,unowned定义的变量都是弱引用类型,用于打破循环引用

实际开发中循环引用一般容易出现在这2种情况:

1.block(swift里面称为闭包:closure) 
2.定义protocol的时候没有用weak来定义
  • 用 unowned 来打破 block 造成的循环引用例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func sendHttpRequestWithResponseJSON(block:( (json:String)->Void)? = nil ){
if let blockUW = block {
blockUW(json: "网络数据返回的是:呵呵哒")
}
}

class BViewController:UIViewController {

var resData: String!
var responseBlock: ((json:String)->Void )! //默认强指针

override func viewDidLoad() {
super.viewDidLoad()

responseBlock = {(json) -> Void in //错误写法,应该把self弄成弱类型,应在(json前面)加上[unowned self]
self.resData = json
println(self.resData)
}


//要进行网络请求了
sendHttpRequestWithResponseJSON(block: responseBlock)
}

deinit {
print("BVC deinit") //循环引用的时候,pop出去并不能打印这句
}
}

上面例子中,在BViewController中定义了responseBlock,即BViewController实例会用一个强指针指向了responseBlock,而responseBlock具体实现的时候引用了self,即内部有一个强指针指向了BViewController的实例,这样就造成了循环引用

当一个BViewController实例在navigation中Push进来,它就肯定不会为nil,除非被pop出去了。
所以BViewController实例的self肯定不为nil,可以使用 unowned 来打破循环引用,上面的Block应该如下实现:

1
2
3
4
responseBlock = {[unowned self](json) -> Void in  
self.resData = json
println(self.resData)
}

当然也可以用 weak 来打破循环引用,但是self就成为了?类型了

1
2
3
4
responseBlock = {[weak self](json) -> Void in  
self?.resData = json //也可以self!.resData = json
println(self?.resData) //同理println(self!.resData)
}
  • 用weak来打破 delegate 中的循环引用例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//定义一个Protocol
protocol GGTableViewDelegate: class {
func didSelectRowAtIndexPath(indexpath:NSIndexPath)
}

//自己定义一个tableView
class GGTableView: UIView{
var delegate: GGTableViewDelegate? //错误写法,这里要加weak
}

//在某一个viewController中使用这个tableView
class GGUiViewController: UIViewController {

var tableView: GGTableView!

override func viewDidLoad() {
super.viewDidLoad()

//1.初始化
tableView = GGTableView()

//2.设置代理
tableView.delegate = self
}

deinit {
println("ggVC deinit") //循环引用的时候,pop出去并不能打印这句
}
}

和block的例子原理相似,造成循环引用的原因是GGUiViewControllerGGTableView内部变量中相互持有对方,并且都是强引用。

只要这样就可以打破循环引用:

1
2
3
class GGTableView: UIView{
weak var delegate: GGTableViewDelegate?
}

总结

  • 在ViewController中多用?和!变量
  • 打循环引用的时候:weak对应?变量unowned对应!变量