Swift3.1新特性初探

Apple最近发布了Xcode8.3, 以及Swift的一个小版本3.1。 不过不要担心, Swift3.1和Swift3是兼容的,这不会给你的Swift3项目造成太多的麻烦。不幸的是, Xcode8.3无情的去掉了对Swift2.3的支持, 所以, 如果你的项目在使用3.0之前的版本,个人建议还是不要着急更新。

数值类型的failable initialize

这个改动, 来自于SE-0080。Swift为所有的数字类型定义了failable initializer, 当构造失败的时候, 就会返回nil

Add a new family of numeric conversion initializers with the following signatures to all numeric types:
//  Conversions from all integer types.
init?(exactly value: Int8)
init?(exactly value: Int16)
init?(exactly value: Int32)
init?(exactly value: Int64)
init?(exactly value: Int)
init?(exactly value: UInt8)
init?(exactly value: UInt16)
init?(exactly value: UInt32)
init?(exactly value: UInt64)
init?(exactly value: UInt)

//  Conversions from all floating-point types.
init?(exactly value: Float)
init?(exactly value: Double)
#if arch(i386) || arch(x86_64)
init?(exactly value: Float80)
#endif

OK, 再让我们更直观的感受一下这个方法:

/// 行尾注释为运行结果
let a = 1.11
let b = Int(a)          //!< 1
let c = Int(exactly: a) //!< nil

let d = 1.0
let e = Int(exactly: d) //!< 1

在上面这段代码中, 我们可以看到1.11 -> Int(exactly:) -> c的结果为nil, 而1.0 -> Int() -> e的结果却是成功的。其实不难发现, Int(exactly:)Int()的精度检查更加严格, 不会允许精度丢失的情况。因此Int(exactly:)转化时,如果丢失精度, 会返回nil。

为什么要加这个特性呢?或者说这个特性的应用场景是什么呢?SE中是这么说的:

It is extremely common to receive loosely typed data from an external source such as json. This data usually has an expected schema with more precise types. When initializing model objects with such data runtime conversion must be performed. It is extremely desirable to be able to do so in a safe and recoverable manner. The best way to accomplish that is to support failable numeric conversions in the standard library.

这段话的大体意思就是,如果你要把一个类似Any这样的松散类型转换成数字类型的时候,像服务端返回的json数据,这个特性就会提现他的价值了。

Sequence中新添加的两个筛选元素的方法

这个特性是根据SE-0045改动的。初步意愿如下:

Modify the declaration of Sequence with two new members:

protocol Sequence {
  // ...
  /// Returns a subsequence by skipping elements while `predicate` returns
  /// `true` and returning the remainder.
  func drop(while predicate: (Self.Iterator.Element) throws -> Bool) rethrows -> Self.SubSequence
  /// Returns a subsequence containing the initial elements until `predicate`
  /// returns `false` and skipping the remainder.
  func prefix(while predicate: (Self.Iterator.Element) throws -> Bool) rethrows -> Self.SubSequence
}
Also provide default implementations on Sequence that return AnySequence, and default implementations on Collection that return a slice.

LazySequenceProtocol and LazyCollectionProtocol will also be extended with implementations of drop(while:) and prefix(while:) that return lazy sequence/collection types. Like the lazy filter(_:), drop(while:) will perform the filtering when startIndex is accessed.

所添加的两个新方法如下:

  • prefix(while:):从第一个元素开始,将符合while条件的元素添加进数组A,如果while条件不被满足,则终止判断,并返回数组A。
  • drop(while:):从第一个元素开始,跳过符合while条件的元素,如果while条件不被满足,则终止判断,并将剩余的元素装进数组返回。

具体看下面代码:

let arr = ["ab","ac","aa","ba","bc","bb"]

let a = arr.prefix{$0.hasPrefix("a")}
let b = arr.prefix{$0.hasPrefix("b")}
let c = arr.drop {$0.hasPrefix("a")}

print(a) //!< ["ab", "ac", "aa"]
print(b) //!< []
print(c) //!< ["ba", "bc", "bb"]

可以看到,a打印出了["ab", "ac", "aa"]arr中第0,1,2个元素都满足while条件,但是第3个元素开始不满足条件,所以,a收到的赋值是第0,1,2三个元素的一个数组。b打印的是一个[],因为arr中的第一个元素就不满足while的条件,所以判断直接终止,返回一个空数组。

c打印出了["ba", "bc", "bb"],因为arr中的第0,1,2个元素都满足while条件,前缀包含a,所以都被跳过,到第3个元素的时候,ba的前缀并不是a,所以第3个元素之后的所有元素"ba", "bc", "bb"都被装进数组返回。

通过available约束Swift版本

之前版本的swift语言,如果你要控制不同版本里面的API,可能需要像下面这样声明:

#if swift(>=3.1)
    func test() {}
#elseif swift(>=3.0)
    func test1() {}
#endif

#if是通过编译器处理的,也就是说编译器要为每一个if条件独立编译一遍。也就是说如果我们的方法要在Swift3.03.1可用,那么编译器就得编译两遍。
这当然不是一个好的方法。一个更好的办法,应该是只编译一次,然后在生成的程序库包含每个API可以支持的Swift版本。

为此,在SE-0141中,Swift对@available进行了扩展,现在它不仅可以用于限定操作系统,也可以用来区分Swift版本号了。

首先,为了表示某个API从特定版本之后才可用,可以这样:

@available(swift 3.1)
func test() {}

其次,为了表示某个API可用的版本区间,可以这样:

@available(swift, introduced: 3.0, obsoleted: 3.1)
func test() {}

使用具体类型在extension中约束泛型参数

Swift3.1之前的版本中,如果想要在Int?类型添加一个方法的话,可能需要这样做:

protocol IntValue {
    var value: Int { get }
}
 
extension Int: IntValue {
    var value: Int { return self }
}
 
extension Optional where Wrapped: IntValue {
    func lessThanThree() -> Bool {
        guard let num = self?.value else { return false }
        return num < 3
    }
}

声明了一个协议,给Int写了一个扩展,就是为了给Int?添加lessThanThree()方法,很明显,这不是一个好的解决方法。

Swift 3.1里,我们有更优雅的实现方法:

extension Optional where Wrapped == Int {
    func lessThanThree() -> Bool {
        guard let num = self else { return false }
        return num < 3
    }
}

临时转换成可逃逸的closure

Swift3.0函数的closure类型参数默认从escaping变成了non-escaping。这很好理解,因为大多数用于函数式编程的closure参数的确都以non-escaping的方式工作。

但这样也遇到了一个问题,就是有时候,我们需要把non-escaping属性的closure,传递给需要escaping属性closure的函数。来看个例子:

func subValue(in array: [Int], with: () -> Int) {
    let subArray = array.lazy.map { $0 - with() }
    print(subArray[0])
}

注意,上面代码是不能编译通过的。因为lazy.map()escaping closure,而with()是默认的non-escaping closure。如果根据编译器的指引,我们可能会在with:的后面添加@escaping

func subValue(in array: [Int], with:@escaping () -> Int) {
    let subArray = array.lazy.map { $0 + with() }
    print(subArray[0])
}

这很明显不是我们想要的设计。幸运的是,Swift3.1给出了一个临时转换的方法:withoutActuallyEscaping(),我们的方法可以改写成下面这样:

func subValue(in array: [Int], with: () -> Int) {
   withoutActuallyEscaping(with) { (escapingWith) in
       let subArray = array.lazy.map { $0 + escapingWith() }
       print(subArray[0])
   }
}

withoutActuallyEscaping有两个参数,第一个参数表示转换前的non-escaping closure,第二参数也是一个closure,用来执行需要escaping closure的代码,它也有一个参数,就是转换后的closure。因此,在我们的例子里,escapingWith就是转换后的with

顺带说一句,这里面使用了array.lazy.map()而不是array.map(),是因为array.lazy.map()会延迟实现的时间,并且按需加载,上面的例子中,只有print(subArray[0])使用了一次subArray,所以闭包里的代码只会执行一次。而用array.map()则会遍历整个数组,具体的差异大家自己code试验吧。

关于内嵌类型的两种改进

Swift 3.1里,内嵌类型有了两方面变化:

  • 普通类型的内嵌类型可以直接使用其外围类型的泛型参数;
  • 泛型类型的内嵌类型可以拥有和其外围类型完全不同的泛型参数;

在之前的版本中,我们实现一个链表中的节点可能需要这样:

class A<T> {
    class B<T> {
        var value: T
        init(value: T) {
            self.value = value;
        }
    }
 }

但这里,就有一个问题了,在A<T>中使用的TB<T>中的T是同一个类型么?为了避开这种歧义,在Swift 3.1里,我们可以把代码改成这样:

class A<T> {
    class B {
        var value: T
        init(value: T) {
            self.value = value
        }
    }
 }

这就是内嵌类型的第一个特性,尽管B是一个普通类型,但它可以直接使用A<T>中的泛型参数,此时B.value的类型就是A中元素的类型

接下来,我们再来看一个内嵌类型需要自己独立泛型参数的情况。:

class A<T> {
    class C<U> {
        var value: U? = nil
    }
 }

这就是内嵌类型的第二个改进,内嵌类型可以和其外围类型有不同的泛型参数。这里我们使用了U来表示C.value的类型。其实,即便我们在C中使用T作为泛型符号,在C的定义内部,T也是一个全新的类型,并不是AT的类型,为了避免歧义,我们最好还是用一个全新的字母,避免给自己带来不必要的麻烦。

最后

感谢:
hackingwithswift
泊学网
提供的博客

原创作品,转载请注明出处:http://www.wzh.wiki

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容