文档章节

Swift 中变量的使用细节

铜锣烧的梦想
 铜锣烧的梦想
发布于 2016/01/29 23:15
字数 2621
阅读 5
收藏 0

变量(variable)是任何一门语言的构成基础, 对 Swift 也不例外. 大家可能会想, 这东西还有什么"细节"可言, 正常使用不就行了. 其实, 在 Swift 中, 变量使用起来还是有一些细节问题需要注意以及提防的, 作者就根据自己的实际经验总结了如下一些东西, 仅供大家参考. 由于这里探讨的是使用细节问题, 因此基本的变量声明、操作以及相关概念等此文便不再赘述. 本文将分成如下 3 部分进行说明. ####1. stored variable 和 computed variable ####2. Observer(观察器) ####3. 懒加载(重点说明!!!)

1. stored variable 和 computed variable

Swift 中的变量分为 stored variable 和 computed variable. stored variable 顾名思义, 就是我们一般意义上理解的可以进行赋值和取值的变量.

var number: Int = 10 上面的 number 就是一个 stored variable, 对其进行正常的读(取值)写(赋值)操作即可, 因为 stored variable 是可以存储值的.

computed variable, 字面意思为"计算型的变量", 虽然也叫"变量", 但要注意, 这个家伙是根本没法存储值的. 来看下面的例子, 这里定义了一个名为 number 的 computed variable:

var number: Int {
	get {
		return 10
	}
	set {
		print("number 被设置为了 \(newValue)")
	}
}

下面对 number 进行我们"想当然"的读写操作:

number = 100		// 输出结果为 " number 被设置为了 100 "
print(number)	   // 输出结果为 " 10 "

What? 明明给 number 赋了新值 100, 并且输出结果也明确指出 number 被设置为了 100, 为什么再次打印 number 的值时, 却还是 10 ???

切记, computed variable 根本不会存储我们主观上认为的"值"!!! 它实质上是一些待执行的代码!!!number 被赋值时, set 中的代码被执行. 同理, 当执行读取 number 的操作时, get 中的代码被执行.

再次重申, computed variable 只是一些待执行的代码!!!

在对 computer variable 赋值时, 我们能够取到赋给其的新值, 那就是 set 中的 newValue, 至于这个值要怎么用, 那就看实际的需要了. 但要注意, newValue 不会存储至 computed variable中! 毕竟, computed variable 只是一些待执行的代码! 重要的事情说三遍!!!

可能有人会想, 毕竟 computed variable 叫做"变量", 如果不能像 stored variable 那样进行很直观的读写操作, 总觉得很奇怪, 我下面给出了一种用 computed variable 实现 stored variable 效果的思路.

这里首先定义一个 PositiveNumber 类, 用来表示正数:

class PositiveNumber {
	private var _value: Double = 0.0
	var value: Double {
		get {
			return _value
		}
		set {
			_value = newValue
		}
	}
}

我为 PositiveNumber 类定义了两个属性, 分别为私有的store property _value 和可供外部访问的接口: 一个 computed property value. 是不是很面熟? 没错, 这跟 OC 中定义一个 @property 是完全相同的!

let number = PositiveNumber()
number.value = 10		// 利用 computed property 的 set 方法为 _value 赋值
print(number.value)	  // 利用 computed property 的 get 方法读取 _value 的值

看到这里, 你可能会想, 如此大费周折, 只做了一件 stored property 很容易就能做到的事儿, 这不是闲的吗?

我们仔细思考一下上例, number 是一个 PositiveNumber 的实例, 在上述实现方式下, 我们可以为 number.value 赋任意值, 如: number.value = -10 这在逻辑上明显是不合适的, 因为 number 是一个"正数", 所以我们要对赋给 number.value 的值进行一定的处理. 这种逻辑交给 computed property 就再合适不过了. 修改上述代码如下:

class PositiveNumber {
	private var _value: Double = 0.0
	var value: Double {
		get {
			return _value
		}
		set {
			if (newValue <= 0) {
				print("赋值不合理!")
			} else {
				_value = newValue
			}
		}
	}
}

这样, 若再执行 number.value = -10 则会执行判断的逻辑, 从而使对 _value 的赋值操作更加严谨.

当然, computed property 的作用不仅如此, 下面说到懒加载时还会提到.


2. Observer(观察器)

Observer(即 willSet 和 didSet 方法) 是用在 stored variable 上的, 毕竟只有能存储值的东西才有被观察的价值! 基本的使用方法这里就不赘述了, 只对使用时的一些细节做一下说明.

  • 当 stored variable 被初始化时, 观察器方法不会被调用
  • 如果在 didSet 方法中改变了 stored variable 本身的值, 观察器方法也不会被调用. 这个逻辑很容易理通, 因为若在观察器方法中修改 stored variable 的值还会调用观察器方法的话, 那么便会导致观察器方法的无限循环调用!
  • 在 willSet 方法中去操作 stored variable, 其实是对旧值进行操作, 新值为 newValue
  • 在 didSet 方法中去操作 stored variable, 其实是对新值进行操作, 旧值为 oldValue

参考下面的代码

class PositiveNumber {
	var value: Double {
		willSet {
			print("willSet 方法被调用")
			print("在 willSet 中, value = \(value), newValue = \(newValue)")
		}
		didSet {
			print("didSet 方法被调用")
			print("在 didSet 中, value = \(value), oldValue = \(oldValue)")
		}
	}
	
	init(value: Double) {
		self.value = value
	}
}

此处测试一下 let number = PositiveNumber(value: 10.0) 此时, 控制台未输出任何信息! 可见这个初始化的操作并没有调用观察器方法.

再来执行 number.value = 20

控制台输出信息
willSet 方法被调用
在 willSet 中, value = 10.0, newValue = 20.0
didSet 方法被调用
在 didSet 中, value = 20.0, oldValue = 10.0

由上例可以看到观察器调用时的一些细节, 大家在使用时注意一下就好.


3. 懒加载

对于开发者来说,懒加载最被人熟知的优点就在于只在需要某个 variable 时, 才去进行加载.其实, 懒加载还能处理一些普通的初始化方法处理不了的情况. 以初始化一个类的实例为例, 在普通的初始化方法中, 若该实例的初始化过程还未结束, 那么开发者是无法在该初始化方法中引用该实例的属性和方法的. 而懒加载则可以解决这个问题, 因为当需要懒加载某个属性时, 该实例变量已经初始化完毕, 因此开发者可以在懒加载的流程中去任意使用该实例的属性以及方法!

下面说明一下懒加载的使用细节.

  • 全局变量默认都是懒加载的! 其实仔细想想, 苹果的这种设计还是非常合理的. 对于一个全局变量来说, 何时加载它最合适呢? 显然程序一启动就加载的方式不太合理, 于是乎, 还是在需要它时再加载吧!
  • static 的属性默认都是懒加载的! static 的属性某种程度上同全局变量很类似, 例如一个 struct 中含有 static 的属性, 那么这个属性是属于这个 struct 类型的, 而不是属于某个具体的 struct 变量! 于是问题又来了, 何时加载这个属性最合适? 依然还是需要它时再加载吧!
  • 实例属性默认都不是懒加载的! 注意! 都不是懒加载的! 若要令其变为懒加载, 那就在声明时加 lazy, 并且这个实例属性必须是 var, 而不能是 let. 同时, 懒加载的实例属性也无法实现观察器方法, 即没有 willSet 和 didSet 方法.

虽然 Swift 已经提供给了我们一种超级给力的实现懒加载的方式, 即只要一句 lazy 就搞定了, 但上述最后一条细节还是留给了我们很多遗憾!

考虑这样一种需求: 某个实例属性一定要采用懒加载的方式, 而且要对其实现观察器的功能, 即可观测其值的变化并进行一定的逻辑处理. 再考虑另一种需求: 某个实例属性一定要采用懒加载的方式, 并且加载完成后是只读的, 不能对其进行修改.

乍一看, 这些需求明显是在给 Swift 原生的懒加载方式找茬嘛! 上述需求, 原生的 lazy var 一条都实现不了!!! 首先, 懒加载的实例属性本身就无法实现观察器方法, 同时 lazy var 这种形式的声明又导致该实例变量可以被修改, 此时会想, 如果有个 lazy let 就好了...

但是, 上述需求并不过分, 开发中也会碰到, 怎么办? 只能手写一个满足上述需求的懒加载了. 下面只提供了一种思路, 仅供大家参考

class PositiveNumber {
	private var token: dispatch_once_t = 0
	private var _value: Double = 0.0
	var value: Double {
    	get {
        	dispatch_once(&token) {
            	// 一些非常耗时的初始化 _value 的流程
        	}
        	return _value
    	}
    	set {
        	_value = value
    	}
	}
}

其实仔细想想, 懒加载在某种程度上同 computed variable 是相同的, 即只有需要时才调用! 那么就可以利用 computed variable 的这些特点来人造一个懒加载方式.

上段代码采用了本文中第 1 点提到的方式, 实现了通过一个 computed variable 来操作一个 stored variable 的功能, 然后利用 GCD 的一次性代码实现了懒加载的**"只加载一次"**操作. 至此, 我们手写了一个与使用 lazy 来实现懒加载操作相同的方法.

但是, 要注意! 上段代码的灵活性更强一些, 包括:

  • 若想实现观察器方法, 只要在 set 方法中构建相应的逻辑即可. 例如, 在 _value = value 前添加 willSet 中希望具有的逻辑, 在 _value = value 后添加 didSet 中希望具有的逻辑即可
  • 若想实现 lazy let 形式的懒加载, 只需要把 set 方法去掉即可, 那么属性初始化后就变成只读的了, 外界无法对其进行任何修改
  • 采用了 GCD 的一次性代码, 可以保证线程安全. 根据 Apple 官方的《The Swift Programming Language》, 普通的采用 lazy var 形式的懒加载, 无法保证线程安全. 一旦某个线程对某一实例属性的懒加载过程未结束, 而另一个线程同时又操作了该实例属性, 那么会导致又一次加载该属性, 此时该属性便被初始化了多次, 已不再具有"懒"的特点了

© 著作权归作者所有

铜锣烧的梦想
粉丝 0
博文 1
码字总数 2621
作品 0
私信 提问
23.Swift学习之访问权限、异常

模块和源文件 模块——是单一的代码分配单元,一个框架或应用程序会作为的独立的单元构建和发布并且可以使用 Swift 的 import 关键字导入到另一个模块。 源文件是一个模块中的单个 Swift 源代...

YungFan
2018/11/20
0
0
《从零开始学Swift》学习笔记(Day 29)——访问级别

原创文章,欢迎转载。转载请注明:关东升的博客 访问级别: Swift提供了3种不同访问级别,对应的访问修饰符为:public、internal和private。这些访问修饰符可以修饰类、结构体、枚举等面向对...

智捷课堂
2015/10/30
0
0
Swift 在对 Objective-C 改进的 6 个方面

在 Atomic Object 的安娜堡办公室,我们做了一个观看2014年的WWDC主题演讲,毫不夸张地说,当Swift宣布的时候,我们感到十分激动。Swift,苹果正在推进的一个更现代的编程语言。我很高兴能获...

oschina
2014/06/14
8.1K
14
iOS - Swift Swift 语言新特性

1、Swift 2.0 带来哪些新变化 常规变化: 1、OS X 10.11、iOS 9 和 watchOS 2 SDK 采纳了一些 Objective-C 的特性用来提高 Swift 的编程体验, 如可空性、类型化集合和一些别的特性。 2、编译...

仟0123
2016/08/16
0
0
LuaScriptCore v1.3.1,移动端 Lua 桥接框架

LuaScriptCore旨在能够在多种平台上方便地使用Lua。其提供了与各种平台的功能交互,让开发者无须关心Lua与各个平台之间是实现交互的细节,只需要根据自己的业务需求,使用LuaScriptCore提供的...

vimfung
2017/04/19
1K
1

没有更多内容

加载失败,请刷新页面

加载更多

jenkins定时构建时间设置

举几个例子: 每隔5分钟构建一次 H/5 * * * * 每两小时构建一次 H H/2 * * * 每天中午12点定时构建一次 H 12 * * * 每天下午18点定时构建一次 H 18 * * * 在每个小时的前半个小时内的每10分钟...

shzwork
16分钟前
0
0
Myeclipse 问题记录

1. 创建maven聚合(pom) 工程 ,子项目会带上 maven app这些文字,删掉后会导致工程文件在working set里消失,解决办法:右键子项目 import as project 解决问题。...

无敌小学僧
43分钟前
0
0
《Chez Scheme初探》定义变量、递归、测试性能、并列代码编写

普通fib函数 (define (fact n) (if (= n 1) 1 (* n (fact (- n 1)) ) )) 尾递归fib函数 (define (fact-tail n) (fact-rec n n))(defi......

flash胜龙
44分钟前
0
0
任正非对华为热点问题的回应亮了,终于知道华为为什么能扛过这一次的冲击!

任正非对华为热点问题的回应亮了,终于知道华为为什么能扛过这一次的冲击! 如果你是华为的老板,看到一条传遍网络的“美国封锁华为”、“华为禁令”的消息,你会怎么办? 昨天上午,华为创始...

forespider
今天
2
0
Java HTTP 组件库选型看这篇就够了

最近项目需要使用 Java 重度调用 HTTP API 接口,于是想着封装一个团队公用的 HTTP client lib. 这个库需要支持以下特性: 连接池管理,包括连接创建和超时、空闲连接数控制、每个 host 的连...

Java面经
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部