Swift3到Swift4的一些坑

一、Substring

Swift 4 中有一个很大的变化就是String可以当做Collection来用,并不是因为String实现了Collection协议,而是String本身增加了很多Collection协议中的方法,使得String 在使用时看上去就是个Collection

1
2
3
4
5
let string = "IamBaylee"
print(string.prefix(3)) //"Iam"
print(string.suffix(6)) //"Baylee"
print(string.dropFirst()) //"amBaylee"
print(string.dropLast()) //"IamBayle"

比如上面的样例,我们使用一些Collection协议的方法对字符串进行截取,只不过它们的返回结果不是String类型,而是 Swift4 新增的Substring类型。

为什么要引入Substring?

既然我们想要得到的就是字符串,那么直接返回String就好了,为什么要返回一个新增的Substring类型呢?答案是:性能。

  • 当我们用一些Collection的方式得到String里的一部分时,创建的都是SubstringSubstring 与原 String 是共享一个 Storage。这意味我们在操作这个部分的时候,是不需要频繁的去创建内存,从而使得 Swift 4 的 String 相关操作可以获取比较高的性能。
  • 而当我们显式地将 Substring 转成 String 的时候,才会 Copy 一份 String 到新的内存空间来,这时新的 String 和之前的 String 就没有关系了。
使用Substring的注意事项
  • 由于Substring与原String是共享存储空间的,只要我们使用了Substring,原 String 就会存在内存空间中。只有 Substring 被释放以后,整个 String 才会被释放。
  • Substring 类型无法直接赋值给需要 String 类型的地方,我们必须用 String() 包一层。当然这时系统就会通过复制创建出一个新的字符串对象,之后原字符串就会被释放。

二、swap方法的变迁

Swift4之前我们可以用swap(_:_:)来将两个变量的值进行交换

1
2
3
4
var a = 1
var b = 2
swap(&a, &b)
print(a, b) // 2 1

Swift4之后,swap(_:_:)方法被废弃,我们可以使用元组(tuple)来实现值交换:

1
2
3
4
var a = 1
var b = 2
(b, a) = (a, b)
print(a, b) // 2 1

使用tuple方式的好处是,多个变量值也可以进行交换

1
2
3
4
5
var a = 1
var b = 2
var c = 3
(a, b ,c) = (b, c, a)
print(a, b, c) //2 3 1

三、减少隐式@objc自动推断

Swift3中的使用方式

在项目中如果想把Swift的API暴露给Objective-C调用,需要增加@objc。在Swift3中,编译器会在很多地方为我们隐式加上@objc。比如当一个类继承于NSObject,那么这个类的所有方法都会被隐式地加上@objc

1
2
3
4
class MyClass: NSObject {
func print(){} //包含隐式的@objc
func show(){} //包含隐式的@objc
}

但这样做导致很多并不需要暴露给Objective-C的API也被加上了@objc。而大量的@objc会导致二进制文件的增加。

Swift4中的使用方式
  1. 在Swift4中隐式@objc自动推断只会发生在下面这种必须使用@objc的情况:

    • 覆盖父类的Objective-C方法
    • 符合一个Objective-C的协议
  2. 大多数地方必须显式地加上@objc

    1
    2
    3
    4
    class MyClass: NSObject {
    @objc func print(){} //显式地加上@objc
    @objc func show(){} //显式地加上@objc
    }
  3. 如果在类前加上@objcMembers,那么它、它的子类、扩展里的方法都会隐式加上@objc

    1
    2
    3
    4
    5
    6
    7
    8
    @objcMembers
    class MyClass: NSObject {
    func print(){} //包含隐式的@objc
    func show(){} //包含隐式的@objc
    }
    extension MyClass {
    fun makeName(){} //包含隐式的@objc
    }
  4. 如果在扩展前加上@nonobjc,那么该扩展的方法都不会隐式地加上@objc

    1
    2
    3
    4
    5
    6
    7
    8
    @objcMembers
    class MyClass: NSObject {
    func print(){} //包含隐式的@objc
    func show(){} //包含隐式的@objc
    }
    @nonobjc extension MyClass {
    fun makeName(){} //不包含隐式的@objc
    }