随着iPhone X的来到,iOS11的发布,Swift语言也更新到了第4个版本。在Swift4中,无论是代码风格还是编程理念都更进一步的融合了许多现代编程的思想。对于熟悉传统语言的开发者来说(尤其是Objective-C、Java和C++),可能会感觉这些特性并没有多大的价值反而非常不习惯,但是我们依然可以茶余饭后(没事干的时候),一窥Swift4语言的玩法,体验一下Swift语言的设计思想和编码风格。
一、独占内存访问权限
独占访问权限是Swift4中引入的一大新特性。然而大部分人都将这一特性误解了,如果你在百度上搜索 swift4 exclusive access to memory相关关键字,大部分博客或总结都会说这是一种编译器的编译时特性,可以在例如数组越界时、对遍历中的数组进行删添元素时产生编译异常。其实并非如此,独占内存访问权限特性是一种编译时和运行时的安全特性,其和数组也没有任何关系,当两个变量访问同一块内存时,会产生独占内存访问限制。
首先,在Swift中对内存的访问有读访问与写访问两种,例如:
//读访问
var name = "jaki"
//写访问
print(name)
在Swift4以前,程序对内存的读写访问并没有严格的控制,如果你在读内存时有写内存操作,或者写内存时有读操作并不会产生什么异常(当然,你自己要清楚读写后变量的值,以免产生逻辑歧义)。Swift4中则引入了独占内存访问权限的特性,如果复合如下3个条件,则程序会产生读写权限冲突:
1.至少有一个变量在使用写权限。
2.变量访问的是同一个内存地址。
3.持续时间有重叠。
在开发中,可能会产生读写权限冲突的情况有3种:
1.inout 参数读写权限冲突
一般情况下,值类型的传参总会产生复制操作。inout参数则使得函数内可以直接修改外部变量的值。inout参数是最容易产生读写冲突的场景,例如下面的代码:
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize//crash
}
increment(&stepSize)
上面的代码在Swift3中没有任何问题,在Swift4环境中运行则会直接crash。在函数中,inout参数从声明开始到函数的结束,这个变量始终开启着写权限,对应上面代码,number参数开启这写权限,stepSize则进行了读访问,如此则满足上面的权限冲突规则,会产生读写冲突。同样,如果对两个inout参数访问同一个内存地址,也会产生读写权限冲突,例如:
var stepSize = 1
func increment(_ number: inout Int,_ number2: inout Int) {
var a = number+number2
}
increment(&stepSize,&stepSize)
2.结构体中自修改函数的读写冲突
Swift语言中的结构体也是一种值类型,因此其也存在读写冲突的场景,例如如下代码:
struct Player {
var name: String
var health: Int
var energy: Int
let maxHealth = 10
mutating func shareHealth(_ player:inout Player) {
health = player.health
}
}
var play = Player(name: "jaki", health: 10, energy: 10)
play.shareHealth(&play)//产生错误
上面shareHealth函数中使用到的health是对self自身的读访问,而inout参数是写访问,会产生读写权限冲突。
3.值类型中属性的读写访问权限冲突
在Siwft语言中,像结构体,枚举和元组中都有属性的概念。由于其都是值类型,在对不同的属性进行访问时也会产生冲突,例如:
class Demo {
var playerInformation = (health: 10, energy: 20)
func balance(_ p1 :inout Int,_ p2 :inout Int) {
}
func test() {
self.balance(&playerInformation.health, &playerInformation.energy)//crash
}
}
let demo = Demo()
demo.test()
看到这里你一定觉得这太严格了,对不同属性的访问也会产生读写冲突。实际上,在开发中大部分的这种访问都会被认为是安全的,你需要满足下面3个条件:
1.你访问的是存储属性而不是计算属性。
2.你访问的是结构体局部变量(函数中的变量)而不是全局变量。
3.你的结构体不被闭包捕获,或者只是被非逃逸的闭包捕获。
将上面的playerInformation变量修改成局部的,程序就可以正常运行了:
class Demo {
func balance(_ p1 :inout Int,_ p2 :inout Int) {
}
func test() {
var playerInformation = (health: 10, energy: 20)
self.balance(&playerInformation.health, &playerInformation.energy)
}
}
let demo = Demo()
demo.test()
其实,Swfit4中的独占内存访问权限特性一般情况下我们都不会使用到,但是了解一下还是很有必要,Swift是一种安全性极高的语言,也是其设计的核心思想与方向,例如类构造方法的安全性检查特性,变量类型的安全限制特性等等都是将开发者编写代码的安全交给语言特性来负责,而不是开发者的经验。这让初学者可以更少的出错,语言运行时的不可控因素更少。
二、关联类型可以添加where约束子句
associatedtype是Swift协议中一个很有用的关键字,其也是Swift泛型编程思想的一种实现。在Swift3中,associatedtype从语法上是不能追加where子句的,Swift4增强了associatedtype的功能,其可以使用where子句进行更加精准的约束,看下面的代码:
//容器协议
protocol Container {
//约束item 泛型为 Int类型
associatedtype Item where Item == Int
func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
class MyIntArray: Container {
//这个地方必须指定为Int否则会报错
typealias Item = Int
func append(_ item: Int) {
self.innerArray.append(item)
}
var count: Int{
get{
return self.innerArray.count
}
}
subscript(i: Int) -> Int {
return self.innerArray[i]
}
var innerArray = [Int]()
}
三、可以创建多行字符串
在Swift4以前,字符串只能创建单行的,Swift4中引入了字面量创建多行文本的语法,例如:
var multiLineString = """
abcd
jaki
24
"""
print(multiLineString)
这种方式可以大大减少在创建字符串时人为添加换行符。
关于String操作的相关API,在Swift4中也有许多优化,例如字符串的下标操作与字符操作一直是Swift语言的硬伤,使用起来十分麻烦,在Swift4中都进行了优化。取字符串的子串的方式也更加规范。
四、增强区间运算符
Swift语言中的区间运算符使用起来十分方便,例如在Swift3中,我们若要遍历数组的范围,可以使用如下的代码:
//Swift3代码
let array = ["1","2","3"]
for item in array[0..<array.count]{
print(item)
}
Swift3中的...运算符只是作为闭区间运算符使用,在Swift4中,可以用它来取集合类型的边界,如字符串,数组等,看如下代码:
let array = ["1","2","3"]
for item in array[0...]{
print(item)
}
五、下标方法支持泛型
subscript方法可以为Swift中的类添加下标访问的支持,在Swift4中,subscript方法更加强大,其不只可以支持泛型,而且可以支持where子句进行协议中关联类型的约束,示例如下:
//下标协议
protocol Sub {
associatedtype T
func getIndex()->T
}
//实现下标协议的一种下标类
class Index:Sub {
init(_ index:Int) {
self.index = index
}
var index:Int
func getIndex() -> Int {
return self.index
}
}
class MyArray {
var array = Array<Int>()
func push(item:Int) {
self.array.append(item)
}
//泛型 并进行约束
subscript<T:Sub>(i:T)->Int where T.T == Int {
return self.array[i.getIndex()]
}
}
var a = MyArray()
a.push(item: 1)
print(a[Index(0)])