• Swift 4.0 新特性

    2017-08-15 19:37:46
    WWDC 2017 带来了很多惊喜,在这次大会上,Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,虽然正式版要8月底9月初才会公布,但很多强大的新特性正吸引我们去学习它。根据大会上已经开放的新特性,先一睹为快。...

    WWDC 2017 带来了很多惊喜,在这次大会上,Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,虽然正式版要8月底9月初才会公布,但很多强大的新特性正吸引我们去学习它。根据大会上已经开放的新特性,先一睹为快。

    体验

    Swift 4包含在Xcode 9中,您可以从Apple的开发者门户下载最新版本的Xcode 9(您必须拥有一个活跃的开发者帐户)。 每个Xcode测试版将在发布时捆绑最新的Swift 4快照。在阅读时,您会注意到[SE-xxxx]格式的链接。 这些链接将带您到相关的Swift Evolution提案。 如果您想了解有关任何主题的更多信息,请务必查看。

    版本迁移

    由于Swift 4新增了很多的新的语法特性,这些语法和思想完全区别于Swift 3及以下版本。因此,使用Swift迁移工具将为您处理大部分更改,在Xcode中,您可以导航到编辑/转换/到当前Swift语法…以启动转换工具。

    语法改进

    extension 中可以访问 private 的属性

    例如有如下代码:

    struct Date: Equatable, Comparable {
        private let secondsSinceReferenceDate: Double
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }

    上面代码定义了一个 Date 结构体,并实现 Equatable 和 Comparable 协议。为了让代码更清晰,可读性更好,一般会把对协议的实现放在单独的 extension 中,这也是一种非常符合 Swift 风格的写法。

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    extension Date: Comparable {
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }
    

    但是在 Swift 3 中,编译就报错了,因为 extension 中无法获取到 secondsSinceReferenceDate 属性,因为它是 private 的。所以,在 Swift 3 中必须把 private 改为 fileprivate。但是如果用 fileprivate,属性的作用域就会更大,可能会不小心造成属性的滥用。

    struct Date {
        fileprivate let secondsSinceReferenceDate: Double
    }
    ...

    而在 Swift 4 中,private 的属性的作用域扩大到了 extension 中,并且被限定在了 struct 和 extension 内部,这样就不需要再改成 fileprivate 了。

    类型和协议的组合类型

    考虑以下如下代码:

    protocol Shakeable {
        func shake()
    }
    
    extension UIButton: Shakeable { /* ... */ }
    extension UISlider: Shakeable { /* ... */ }
    
    func shakeEm(controls: [???]) {
        for control in controls where control.state.isEnabled {
        }
        control.shake()
    }
    

    ???处怎么写呢?在Swift 3中可以这么写。

    func shakeEm(controls: [UIControl]) {
        for control in controls where control.isEnabled {
            if control is Shakeable {
                (control as! Shakeable).shake()
            }
        }
    }

    在Swift 4中,如果将类型和协议用 & 组合在一起使用,代码就可以这么写了。

    protocol Shakeable {
        func shake()
    }
    
    extension UIButton: Shakeable { /* ... */ }
    extension UISlider: Shakeable { /* ... */ }
    
    func shakeEm(controls: [UIControl & Shakeable]) {
        for control in controls where control.state.isEnabled {
            control.shake()
        }// Objective-C API
    @interface NSCandidateListTouchBarItem<CandidateType> : NSTouchBarItem
    @property (nullable, weak) NSView <NSTextInputClient> *client;
    @end
    }

    Associated Type 追加Where 约束语句

    在 Swift 4 中可以在 associated type 后面声明的类型后追加 where 语句,其语法格式如下:

    associatedtype Element where <xxx>

    下面是 Swift 4 标准库中 Sequence 中 Element 的声明:

    protocol Sequence {
        associatedtype Element where Self.Element == Self.Iterator.Element
        // ...
    }

    它限定了 Sequence 中 Element 这个类型必须和 Iterator.Element 的类型一致。通过 where 语句可以对类型添加更多的约束,使其更严谨,避免在使用这个类型时做多余的类型判断。

    Key Paths 语法

    先来看看Swift 3的Key Paths语法:

    @objcMembers class Kid: NSObject {
        dynamic var nickname: String = ""
        dynamic var age: Double = 0.0
        dynamic var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 5.5)
    
    let kidsNameKeyPath = #keyPath(Kid.nickname)
    
    let name = ben.valueForKeyPath(kidsNameKeyPath)
    ben.setValue("Ben", forKeyPath: kidsNameKeyPath)
    
    

    在Swift 4中上面的代码可以这样写:

    struct Kid {
        var nickname: String = ""
        var age: Double = 0.0
        var friends: [Kid] = []
    }
    
    var ben = Kid(nickname: "Benji", age: 8, friends: [])
    
    let name = ben[keyPath: \Kid.nickname]
    ben[keyPath: \Kid.nickname] = "BigBen"
    
    

    相比 Swift 3,Swift 4 的 Key Paths 具有以下优势:

    1. 类型可以定义为 class、struct;
    2. 定义类型时无需加上 @objcMembers、dynamic 等关键字;
    3. 性能更好;
    4. 类型安全和类型推断,例如 ben.valueForKeyPath(kidsNameKeyPath) 返回的类型是
      Any,ben[keyPath: \Kid.nickname] 直接返回 String 类型;
    5. 可以在所有值类型上使用;

    下标支持泛型

    Swift 支持通过下标来读写容器中的数据,但是如果容器类中的数据类型定义为泛型,以前的下标语法就只能返回 Any,在取出值后需要用 as? 来转换类型。在Swift 4中,下标也可以使用泛型。

    struct GenericDictionary<Key: Hashable, Value> {
        private var data: [Key: Value]
    
        init(data: [Key: Value]) {
            self.data = data
        }
    
        subscript<T>(key: Key) -> T? {
            return data[key] as? T
        }
    }
    
    let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])
    
    let name: String? = dictionary["Name"] // 不需要再写 as? String
    
    

    字符串

    Unicode 字符串

    在 Unicode 中,有些字符是由几个其它字符组成的,比如 é 这个字符,它可以用 \u{E9} 来表示,也可以用 e 字符和上面一撇字符组合在一起表示 \u{65}\u{301}。例如:

    这里写图片描述
    这个 family 是一个由多个字符组合成的字符,打印出来的结果为 一个家庭。上面的代码在 Swift 3 中打印的 count 数是 4,在 Swift 4 中打印出的 count 是 1。

    更快的字符处理速度

    Swift 4 的字符串优化了底层实现,对于英语、法语、德语、西班牙语的处理速度提高了 3.5 倍。对于简体中文、日语的处理速度提高了 2.5 倍。

    去掉了 characters

    Swift 3 中的 String 需要通过 characters 去调用的属性方法,在 Swift 4 中可以通过 String 对象本身直接调用,例如:

    let values = "one,two,three..."
    var i = values.characters.startIndex
    
    while let comma = values.characters[i...<values.characters.endIndex].index(of: ",") {
        if values.characters[i..<comma] == "two" {
            print("found it!")
        }
        i = values.characters.index(after: comma)
    }
    
    

    在Swift 4 可以把上面代码中的所有的 characters 都去掉:

    let values = "one,two,three..."
    var i = values.startIndex
    
    while let comma = values[i...<values.endIndex].index(of: ",") {
        if values[i..<comma] == "two" {
            print("found it!")
        }
        i = values.index(after: comma)
    }
    

    One-sided Slicing

    Swift 4 新增了一个语法糖 … 可以对字符串进行单侧边界取子串。例如:

    let values = "abcdefg"
    let startSlicingIndex = values.index(values.startIndex, offsetBy: 3)
    let subvalues = values[startSlicingIndex...] // One-sided Slicing
    // defg
    
    

    将String 当做 Collection 来用

    Swift 4 中 String 可以当做 Collection 来用,并不是因为 String 实现了 Collection 协议,而是 String 本身增加了很多 Collection 协议中的方法,使得 String 在使用时看上去就是个 Collection。例如:
    翻转字符串

    let abc: String = "abc"
    print(String(abc.reversed()))
    // cba

    遍历字符

    let abc: String = "abc"
    for c in abc {
        print(c)
    }
    /*
    a
    b
    c
    */

    Map、Filter、Reduce

    // map
    let abc: String = "abc"
    _ = abc.map {
        print($0.description)
    }
    
    // filter
    let filtered = abc.filter { $0 == "b" }
    
    // reduce
    let result = abc.reduce("1") { (result, c) -> String in
        print(result)
        print(c)
        return result + String(c)
    }
    print(result)
    
    

    Substring

    这里写图片描述

    在 Swift 中,String 的背后有个 Owner Object 来跟踪和管理这个 String,String 对象在内存中的存储由内存其实地址、字符数、指向 Owner Object 指针组成。Owner Object 指针指向 Owner Object 对象,Owner Object 对象持有 String Buffer。当对 String 做取子字符串操作时,子字符串的 Owner Object 指针会和原字符串指向同一个对象,因此子字符串的 Owner Object 会持有原 String 的 Buffer。当原字符串销毁时,由于原字符串的 Buffer 被子字符串的 Owner Object 持有了,原字符串 Buffer 并不会释放,造成极大的内存浪费。
    在 Swift 4 中,做取子串操作的结果是一个 Substring 类型,它无法直接赋值给需要 String 类型的地方。必须用 String() 包一层,系统会通过复制创建出一个新的字符串对象,这样原字符串在销毁时,原字符串的 Buffer 就可以完全释放了。例如:

    let big = downloadHugeString()
    let small = extractTinyString(from: big)
    
    mainView.titleLabel.text = small // Swift 4 编译报错
    
    mainView.titleLabel.text = String(small) // 编译通过
    
    

    多行字符串字面量

    Swift 3 中写很长的字符串只能写在一行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = "Q: Why does \(name) have \(n) \(character)'s in their name?\nA: I don't know, why does \(name) have \(n) \(character)'s in their name?\nQ: Because otherwise they'd be called \(punchline)."
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")
    

    字符串中间有换行只能通过添加 \n 字符来代表换行。Swift 4 可以把字符串写在一对 “”” 中,这样字符串就可以写成多行。

    func tellJoke(name: String, character: Character) {
        let punchline = name.filter { $0 != character }
        let n = name.count - punchline.count
        let joke = """
            Q: Why does \(name) have \(n) \(character)'s in their name?
            A: I don't know, why does \(name) have \(n) \(character)'s in their name?
            Q: Because otherwise they'd be called \(punchline).
            """
        print(joke)
    }
    tellJoke(name: "Edward Woodward", character: "d")
    
    

    Swift 标准库

    Encoding and Decoding

    当需要将一个对象持久化时,需要把这个对象序列化,往常的做法是实现 NSCoding 协议,写过的人应该都知道实现 NSCoding 协议的代码写起来很痛苦,尤其是当属性非常多的时候。Swift 4 中引入了 Codable 帮我们解决了这个问题,这和Java等面向对象语言有异曲同工之妙。例如:

    struct Language: Codable {
        var name: String
        var version: Int
    }

    想让这个 Language 对象的实例持久化,只需要让 Language 符合 Codable 协议即可,Language 中不用写别的代码。符合了 Codable 协议以后,可以选择把对象 encode 成 JSON 或者 PropertyList。

    Encode操作

    let swift = Language(name: "Swift", version: 4)
    if let encoded = try? JSONEncoder().encode(swift) {
        // 把 encoded 保存起来
    }

    Decode操作

    if let decoded = try? JSONDecoder().decode(Language.self, from: encoded) {
        print(decoded.name)
    }

    Sequence

    在Swift 3中,

    protocol Sequence {
        associatedtype Iterator: IteratorProtocol
        func makeIterator() -> Iterator
    }

    由于 Swift 4 中的 associatedtype 支持追加 where 语句,所以 Sequence 做了这样的改进。

    protocol Sequence {
        associatedtype Element
        associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
        func makeIterator() -> Iterator
    }
    
    

    Swift 4 中获取 Sequence 的元素类型可以不用 Iterator.Element,而是直接取 Element。例如:

    protocol Sequence {
        associatedtype SubSequence: Sequence 
            where SubSequence.SubSequence == SubSequence,
                  SubSequence.Element == Element
    }
    
    

    通过 where 语句的限定,保证了类型正确,避免在使用 Sequence 时做一些不必要的类型判断。

    Protocol-oriented integers

    整数类型的协议也做了修改,新增了 FixedWidthInteger 等协议,具体的协议继承关系如下:
    这里写图片描述

    Dictionary and Set enhancements

    这里简单的罗列了Dictionary 和 Set 增强的功能点:

    • 通过 Sequence 来初始化;
    • 可以包含重复的 Key
    • Filter 的结果的类型和原类型一致
    • Dictionary 的 mapValues 方法
    • Dictionary 的默认值
    • Dictionary 可以分组
    • Dictionary 可以翻转

    NSNumber

    在 Swift 4 中,把一个值为 999 的 NSNumber 转换为 UInt8 后,能正确的返回 nil,而在 Swift 3 中会不可预料的返回 231。

    let n = NSNumber(value: 999)
    let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

    MutableCollection.swapAt(::)

    MutableCollection 现在有了一个新方法 swapAt(::) 用来交换两个位置的值,例如:

    var mutableArray = [1, 2, 3, 4]
    mutableArray.swapAt(1, 2)
    print(mutableArray)
    // 打印结果:[1, 3, 2, 4]

    Xcode改进

    New Build System

    Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings… 中选择开启。
    这里写图片描述

    预编译 Bridging Headers 文件

    对于 Swift 和 Objective-C 混合的项目,Swift 调用 Objective-C 时,需要建立一个 Bridging Headers 文件,然后把 Swift 要调用的 Objective-C 类的头文件都写在里面,编译器会读取 Bridging Headers 中的头文件,然后生成一个庞大的 Swift 文件,文件内容是这些头文件内的 API 的 Swift 版本。然后编译器会在编译每一个 Swift 文件时,都要编译一遍这个庞大的 Swift 文件的内容。

    有了预编译 Bridging Headers 以后,编译器会在预编译阶段把 Bridging Headers 编译一次,然后插入到每个 Swift 文件中,这样就大大提高了编译速度(苹果宣称 Xcode 9 和 Swift 4 对于 Swift 和 Objective-C 混合编译的速度提高了 40%)。

    COW Existential Containers

    Swift 中有个东西叫 Existential Containers,它用来保存未知类型的值,它的内部是一个 Inline value buffer,如果 Inline value buffer 中的值占用空间很大时,这个值会被分配在堆上,然而在堆上分配内存是一个性能比较慢的操作。

    Swift 4 中为了优化性能引入了 COW Existential Containers,这里的 COW 就代表 “Copy-On-Write”,当存在多个相同的值时,他们会共用 buffer 上的空间,直到某个值被修改时,这个被修改的值才会被拷贝一份并分配内存空间。

    移除未调用的协议实现

    struct Date {
        private let secondsSinceReferenceDate: Double
    }
    
    extension Date: Equatable {
        static func ==(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
        }
    }
    
    extension Date: Comparable {
        static func <(lhs: Date, rhs: Date) -> Bool {
            return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
        }
    }
    
    

    例如,上面的代码中,Date 实现了 Equatable 和 Comparable 协议。编译时如果编译器发现没有任何地方调用了对 Date 进行大小比较的方法,编译器会移除 Comparable 协议的实现,来达到减小包大小的目的。

    减少隐式 @objc 自动推断

    在项目中想把 Swift 写的 API 暴露给 Objective-C 调用,需要增加 @objc。在 Swift 3 中,编译器会在很多地方为我们隐式的加上 @objc,例如当一个类继承于 NSObject,那么这个类的所有方法都会被隐式的加上 @objc。这样很多并不需要暴露给 Objective-C 也被加上了 @objc。大量 @objc 会导致二进制文件大小的增加。

    class MyClass: NSObject {
        func print() { ... } // 包含隐式的 @objc
        func show() { ... } // 包含隐式的 @objc
    }

    在 Swift 4 中,隐式 @objc 自动推断只会发生在很少的当必须要使用 @objc 的情况,比如:

    1. 复写父类的 Objective-C 方法
    2. 符合一个 Objective-C 的协议

    其它大多数地方必须手工显示的加上 @objc。减少了隐式 @objc 自动推断后,Apple Music app 的包大小减少了 5.7%。

    兼容

    Xcode 9 中同时集成了 Swift 3.2 和 Swift 4。

    1. Swift 3.2 完全兼容 Swift 3.1,并会在过时的语法或函数上报告警告。
    2. Swift 3.2 具有 Swift 4 的一些写法,但是性能不如 Swift 4。
    3. Swift 3.2 和 Swift 4 可以混合编译,可以指定一部分模块用 Swift 3.2 编译,一部分用 Swift 4 编译。
    4. 迁移到 Swift 4 后能获得 Swift 4 所有的新特性,并且性能比 Swift 3.2 好。

    当 Xcode 正式版发布后,现有的 Swift 代码可以直接升级到 Swift 3.2 而不用做任何改动,后续可以再迁移到 Swift 4。或者直接迁移到 Swift 4 也可以,Swift 4 相比 Swift 3 的 API 变化还是不大的,很多第三方库都可以直接用 Swift 4 编译。Swift 1 到 2 和 Swift 2 到 3 的迁移的痛苦在 3 到 4 的迁移上已经大大改善了。

    参考资料:

    展开全文
  • Swift 4是苹果计划于2017年秋季推出的最新版本,其主要重点是提供与Swift 3代码的源兼容性,并努力实现ABI稳定性。 本文重点介绍对Swift的更改将对您的代码产生最大的影响。 而且,让我们开始吧!

    本文由陈云峰翻译,转载请注明。

    注意:本教程将使用Swift 4版本捆绑在Xcode 9 beta 1中。

    Swift 4

    Swift 4是苹果计划于2017年秋季推出的最新版本,其主要重点是提供与Swift 3代码的源兼容性,并努力实现ABI稳定性。

    本文重点介绍对Swift的更改将对您的代码产生最大的影响。 而且,让我们开始吧!

    入门

    Swift 4包含在Xcode 9中。您可以从Apple的开发者门户下载最新版本的Xcode 9(您必须拥有一个活跃的开发者帐户)。 每个Xcode测试版将在发布时捆绑最新的Swift 4快照。

    在阅读时,您会注意到[SE-xxxx]格式的链接。 这些链接将带您到相关的Swift Evolution提案。 如果您想了解有关任何主题的更多信息,请务必查看。

    我建议您在操场上尝试每个Swift 4功能或更新。 这将有助于巩固您的头脑中的知识,并使您有能力深入了解每个主题。 试图扩大/打破他们的例子来玩弄这些例子。 玩得开心!

    注意:本文将针对每个Xcode测试版进行更新。 如果您使用不同的Swift快照,这里的代码不能保证工作。

    迁移到Swift 4

    从Swift 3迁移到4将比从2.2到3更麻烦。一般来说, 大多数变化是相加的,不应该需要大量的个人感觉。 因此,Swift迁移工具将为您处理大部分更改。

    Xcode 9同时支持Swift 4以及Swift 3.2中的Swift 3中间版本。 您的项目中的每个目标可以是Swift 3.2或Swift 4,如果需要,您可以逐个迁移。 然而,转换为Swift 3.2并不是完全免费的 – 您可能需要更新代码部分才能与新SDK兼容,并且由于Swift尚未ABI稳定,因此您将需要使用Xcode 9重新编译依赖项。

    当您准备迁移到Swift 4时,Xcode再次提供了一个迁移工具来帮助您。 在Xcode中,您可以导航到编辑/转换/到当前Swift语法…以启动转换工具。

    选择要转换的目标后,Xcode将提示您对Objective-C推理的偏好。 选择推荐的选项通过限制引用来减少二进制大小(有关此主题的更多信息,请查看下面的限制@objc推断 )

    为了更好地了解您的代码中期望的更改,我们将首先介绍Swift 4中的API更改。

    API更改

    在跳转到Swift 4中介绍的补充之前,我们先来看看现有API所做的更改/改进。

    字符串

    String在Swift 4中获得了很多很好的爱。这个提案包含很多变化,所以让我们分解最大的。 [SE-0163] :

    如果你感觉怀旧,字符串再次收藏,就像他们是Swift 2.0之前一样。 此更改消除了对String上的String数组的需求。 您现在可以直接在String对象上进行迭代:

    let galaxy = "Milky Way "
    for char in galaxy {
      print(char)
    }

    是!

    您不仅可以通过String逻辑迭代,还可以从SequenceCollection获取所有的响铃和口哨:

    galaxy.count       // 11
    galaxy.isEmpty     // false
    galaxy.dropFirst() // "ilky Way "
    String(galaxy.reversed()) // " yaW ykliM"
    
    // Filter out any none ASCII characters
    galaxy.filter { char in
      let isASCII = char.unicodeScalars.reduce(true, { $0 && $1.isASCII })
      return isASCII
    } // "Milky Way "

    上面的ASCII示例显示了对Character 。 您现在可以直接从Character访问Character 。 以前,您需要实例化一个新的String [SE-0178] 。

    另外还有一个是StringProtocol 。 它声明了以前在String上声明的大部分功能。 这种变化的原因是改善切片如何工作。 Swift 4添加了Substring类型,用于引用String上的子序列。

    StringSubstring实现了StringProtocol使它们具有几乎相同的功能:

    // Grab a subsequence of String
    let endIndex = galaxy.index(galaxy.startIndex, offsetBy: 3)
    var milkSubstring = galaxy[galaxy.startIndex...endIndex]   // "Milk"
    type(of: milkSubstring)   // Substring.Type
    
    // Concatenate a String onto a Substring
    milkSubstring += ""     // "Milk"
    
    // Create a String from a Substring
    let milkString = String(milkSubstring) // "Milk"

    另一个很大的改进是String如何解释图形集合。 此解决方案来自于Unicode 9的改编。以前,由多个代码点组成的Unicode字符会导致count大于1.常见的情况是具有所选肤色的表情符号。 以下是几个示例,显示前后行为:

    "‍".count // Now: 1, Before: 2
    "".count // Now: 1, Before: 2
    "‍❤️‍‍".count // Now: 1, Before, 4

    这只是“ 字符串宣言”中提到的更改的一个子集。 您可以阅读有关将来希望看到的原始动机和提出的解决方案。

    词典和集合

    至于Collection类型, SetDictionary并不总是最直观的。 幸运的是,斯威夫特队给了他们一些非常需要的爱[SE-0165] 。

    基于序列的初始化
    列表首先是从一系列键值对(元组)创建一个字典的能力:

    let nearestStarNames = ["Proxima Centauri", "Alpha Centauri A", "Alpha Centauri B", "Barnard's Star", "Wolf 359"]
    let nearestStarDistances = [4.24, 4.37, 4.37, 5.96, 7.78]
    
    // Dictionary from sequence of keys-values
    let starDistanceDict = Dictionary(uniqueKeysWithValues: zip(nearestStarNames, nearestStarDistances)) 
    // ["Wolf 359": 7.78, "Alpha Centauri B": 4.37, "Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Barnard's Star": 5.96]

    重复键处理
    您现在可以使用重复的键来处理初始化字典的任何方式。 这有助于避免覆盖键值对,而不会有任何问题:

    // Random vote of people's favorite stars
    let favoriteStarVotes = ["Alpha Centauri A", "Wolf 359", "Alpha Centauri A", "Barnard's Star"]
    
    // Merging keys with closure for conflicts
    let mergedKeysAndValues = Dictionary(zip(favoriteStarVotes, repeatElement(1, count: favoriteStarVotes.count)), uniquingKeysWith: +) // ["Barnard's Star": 1, "Alpha Centauri A": 2, "Wolf 359": 1]

    上面的代码使用zip和速记+来通过添加两个冲突的值来解析重复的键。

    注意:如果您不熟悉zip ,您可以在Apple的Swift文档中快速了解它

    过滤
    DictionarySet现在都可以将结果过滤到原始类型的新对象中:

    // Filtering results into dictionary rather than array of tuples
    let closeStars = starDistanceDict.filter { $0.value < 5.0 }
    closeStars // Dictionary: ["Proxima Centauri": 4.24, "Alpha Centauri A": 4.37, "Alpha Centauri B": 4.37]

    字典映射
    Dictionary获得了一个非常有用的方法来直接映射其值:

    // Mapping values directly resulting in a dictionary
    let mappedCloseStars = closeStars.mapValues { "\($0)" }
    mappedCloseStars // ["Proxima Centauri": "4.24", "Alpha Centauri A": "4.37", "Alpha Centauri B": "4.37"]

    字典默认值
    在Dictionary上访问某个值时,常见的做法是使用nil coalescing运算符给出默认值,以防数值为nil 。 在Swift 4中,这变得更加清洁,并允许您在线突变中做一些真棒:

    // Subscript with a default value
    let siriusDistance = mappedCloseStars["Wolf 359", default: "unknown"] // "unknown"
    
    // Subscript with a default value used for mutating
    var starWordsCount: [String: Int] = [:]
    for starName in nearestStarNames {
      let numWords = starName.split(separator: " ").count
      starWordsCount[starName, default: 0] += numWords // Amazing 
    }
    starWordsCount // ["Wolf 359": 2, "Alpha Centauri B": 3, "Proxima Centauri": 2, "Alpha Centauri A": 3, "Barnard's Star": 2]

    以前,这种类型的突变将需要在一个blo肿的if-let语句中包装。 在Swift 4中,可能是一条线!

    字典分组
    另一个令人惊讶的有用的补充是从Sequence Dictionary并将它们分组到桶中的能力:

    // Grouping sequences by computed key
    let starsByFirstLetter = Dictionary(grouping: nearestStarNames) { $0.first! }
    
    // ["B": ["Barnard's Star"], "A": ["Alpha Centauri A", "Alpha Centauri B"], "W": ["Wolf 359"], "P": ["Proxima Centauri"]]

    当通过特定模式对数据进行分组时,这很方便。

    预留容量
    SequenceDictionary现在都具有明确保留容量的能力。

    // Improved Set/Dictionary capacity reservation
    starWordsCount.capacity  // 6
    starWordsCount.reserveCapacity(20) // reserves at _least_ 20 elements of capacity
    starWordsCount.capacity // 24
    

    这些类型的重新分配可能是一项昂贵的任务。 使用reserveCapacity(_:)是一个简单的方法来提高性能,当您了解需要存储多少数据时。

    这是一大堆信息,所以绝对检查这两种类型,并寻找使用这些添加剂来调整代码的方法。

    私有访问修饰符

    Swift 3的一个元素,一些不太喜欢的是添加fileprivate 。 从理论上讲,这是非常好的,但实际上它的使用往往会令人困惑。 目标是在成员本身中使用private的,并且在您想要在同一文件中的成员共享访问的情况下很少使用fileprivate 。

    问题是Swift鼓励使用扩展将代码分解成逻辑组。 扩展被认为在原始成员声明范围之外,这导致对fileprivate的广泛需求。

    Swift 4通过在类型和所述类型的任何扩展之间共享相同的访问控制范围来实现原始意图。 这只适用于相同的源文件[SE-0169] :

    struct SpaceCraft {
      private let warpCode: String
    
      init(warpCode: String) {
        self.warpCode = warpCode
      }
    }
    
    extension SpaceCraft {
      func goToWarpSpeed(warpCode: String) {
        if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate
          print("Do it Scotty!")
        }
      }
    }
    
    let enterprise = SpaceCraft(warpCode: "KirkIsCool")
    //enterprise.warpCode  // error: 'warpCode' is inaccessible due to 'private' protection level
    enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"

    这允许您使用fileprivate作为其预期目的,而不是作为带状编码组织。

    新增API

    现在让我们来看看Swift 4的新功能。这些更改不应该打破你现有的代码,因为它们是简单的加法。

    归档和序列化

    谷物人

    到目前为止,在Swift中,为了序列化和归档您的自定义类型,您必须跳过一些环。对于class类型,您需要对NSObject进行子类化并实现NSCoding协议。

    structenum这样的值类型需要许多hacks,例如创建一个可以扩展NSObjectNSCoding的子对象。

    Swift 4通过将序列化到所有三种Swift类型[SE-0166]来解决这个问题:

    struct CuriosityLog: Codable {
      enum Discovery: String, Codable {
        case rock, water, martian
      }
    
      var sol: Int
      var discoveries: [Discovery]
    }
    
    // Create a log entry for Mars sol 42
    let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])

    在这个例子中,您可以看到,使Swift类型可Encodable和可Decodable所需的唯一Decodable是实现可编Codable协议。 如果所有属性都是Codable ,则协议实现由编译器自动生成。

    本文由陈云峰翻译,转载请注明。

    要实际编码对象,您需要将其传递给编码器。 Swift编码器正在Swift 4中积极实施。每个编码器根据不同的方案对您的对象进行编码[SE-0167] ( 注意:此提案的一部分仍在开发中):

    let jsonEncoder = JSONEncoder() // One currently available encoder
    
    // Encode the data
    let jsonData = try jsonEncoder.encode(logSol42)
    // Create a String from the data
    let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"

    这采取了一个对象,并自动将其编码为JSON对象。 确保查看JSONEncoder暴露的属性来自定义其输出。

    该过程的最后一部分是将数据解码为具体对象:

    let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder
    
    // Attempt to decode the data to a CuriosityLog object
    let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData)
    decodedLog.sol         // 42
    decodedLog.discoveries // [rock, rock, rock, rock]

    使用Swift 4编码/解码,您可以在Swift中获得预期的类型安全性,而不依赖于@objc协议的开销和限制。

    键值编码

    到目前为止,您可以参考函数而不调用它们,因为函数是Swift中的闭包。 你不能做的是保持对属性的引用,而不实际访问属性保存的底层数据。

    对Swift 4来说,令人兴奋的补充是能够引用类型的关键路径来获取/设置实例的基础值[SE-0161] :

    struct Lightsaber {
      enum Color {
        case blue, green, red
      }
      let color: Color
    }
    
    class ForceUser {
      var name: String
      var lightsaber: Lightsaber
      var master: ForceUser?
    
      init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
        self.name = name
        self.lightsaber = lightsaber
        self.master = master
      }
    }
    
    let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
    let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
    let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)

    在这里,您将通过设置他们的名字,光剑和主人来创建强制用户的几个实例。 要创建一个关键路径,您只需使用一个反斜杠后跟您感兴趣的属性:

    // Create reference to the ForceUser.name key path
    let nameKeyPath = \ForceUser.name
    
    // Access the value from key path on instance
    let obiwanName = obiwan[keyPath: nameKeyPath]  // "Obi-Wan Kenobi"

    在这种情况下,您正在为ForceUsername属性创建一个关键路径。 然后,通过将其传递给新的下标keyPath来使用此键路径。 默认情况下,此下标现在可用于每种类型。

    以下是使用关键路径深入到子对象,设置属性和构建关键路径引用的更多示例:

    // Use keypath directly inline and to drill down to sub objects
    let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color]  // blue
    
    // Access a property on the object returned by key path
    let masterKeyPath = \ForceUser.master
    let anakinMasterName = anakin[keyPath: masterKeyPath]?.name  // "Obi-Wan Kenobi"
    
    // Change Anakin to the dark side using key path as a setter
    anakin[keyPath: masterKeyPath] = sidious
    anakin.master?.name // Darth Sidious
    
    // Note: not currently working, but works in some situations
    // Append a key path to an existing path
    //let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
    //anakin[keyPath: masterKeyPath] // "Darth Sidious"

    Swift的关键路径的美丽在于它们是强类型的! 没有更多的Objective-C字符串风格混乱!

    多行字符串文字

    许多编程语言的一个非常常见的特征是能够创建多行字符串文字。 Swift 4通过在三个引号[SE-0168]中包装文本来添加这个简单而有用的语法:

    let star = "⭐️"
    let introString = """
      A long time ago in a galaxy far,
      far away....
    
      You could write multi-lined strings
      without "escaping" single quotes.
    
      The indentation of the closing quotes
           below deside where the text line
      begins.
    
      You can even dynamically add values
      from properties: \(star)
      """
    print(introString) // prints the string exactly as written above with the value of star

    这在构建XML / JSON消息或构建长格式的文本以在UI中显示时非常有用。

    单面范围

    为了减少冗长度并提高可读性,标准库现在可以使用单面范围[SE-0172]来推断起始和终点索引。

    派上用场的一种方法是创建一个从索引到集合的开始或结束索引的范围:

    / Collection Subscript
    var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
    let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex]
    let firstThree = planets[..<4]          // Before: planets[planets.startIndex..<4]

    如您所见,单面范围减少了明确指定开始索引或结束索引的需要。

    无限序列
    当起始索引为可数类型时,它们还允许您定义无限Sequence :

    // Infinite range: 1...infinity
    var numberedPlanets = Array(zip(1..., planets))
    print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")]
    
    planets.append("Pluto")
    numberedPlanets = Array(zip(1..., planets))
    print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")]

    模式匹配
    单面范围的另一个很好的用途是模式匹配:

    // Pattern matching
    
    func temperature(planetNumber: Int) {
      switch planetNumber {
      case ...2: // anything less than or equal to 2
        print("Too hot")
      case 4...: // anything greater than or equal to 4
        print("Too cold")
      default:
        print("Justtttt right")
      }
    }
    
    temperature(planetNumber: 3) // Earth

    通用下标

    下标是使数据类型以简洁方式可访问的重要组成部分。 为了提高其有用性,下标现在可以是通用的[SE-0148] :

    struct GenericDictionary<Key: Hashable, Value> {
      private var data: [Key: Value]
    
      init(data: [Key: Value]) {
        self.data = data
      }
    
      subscript<T>(key: Key) -> T? {
        return data[key] as? T
      }
    }

    在这个例子中,返回类型是通用的。 你可以使用这个通用的下标,如下所示:

    // Dictionary of type: [String: Any]
    var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1])
    
    // Automatically infers return type without "as? String"
    let name: String? = earthData["name"]
    
    // Automatically infers return type without "as? Int"
    let population: Int? = earthData["population"]

    返回类型不仅可以是通用的,而且实际的下标类型也可以是通用的:

    extension GenericDictionary {
      subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key {
        var values: [Value] = []
        for key in keys {
          if let value = data[key] {
            values.append(value)
          }
        }
        return values
      }
    }
    
    // Array subscript value
    let nameAndMoons = earthData[["moons", "name"]]        // [1, "Earth"]
    // Set subscript value
    let nameAndMoons2 = earthData[Set(["moons", "name"])]  // [1, "Earth"]

    在这个例子中,你可以看到,传递两个不同的Sequence类型( ArraySet )会导致一个数组的各自的值。

    更多更新

    它处理了Swift 4中最大的变化。现在让我们通过一些较小的位和块来更快速地进行一些。

    MutableCollection.swapAt(_:_ )

    MutableCollection现在具有mutate方法swapAt(_:_:) ,就像它的声音一样; 交换给定索引值[SE-0173] :

    // Very basic bubble sort with an in-place swap
    func bubbleSort<T: Comparable>(_ array: [T]) -> [T] {
      var sortedArray = array
      for i in 0..<sortedArray.count - 1 {
        for j in 1..<sortedArray.count {
          if sortedArray[j-1] > sortedArray[j] {
            sortedArray.swapAt(j-1, j) // New MutableCollection method
          }
        }
      }
      return sortedArray
    }
    
    bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4]

    相关类型限制

    您现在可以使用where子句来限制关联类型[SE-0142] :

    protocol MyProtocol {
      associatedtype Element
      associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
    }

    使用协议约束,许多associatedtype声明可以直接约束其值,而不必跳过环。

    类和协议存在

    最终将其从Objective-C转换为Swift的功能是定义符合类和一组协议的类型的能力[SE-0156] :

    protocol MyProtocol { }
    class View { }
    class ViewSubclass: View, MyProtocol { }
    
    class MyClass {
      var delegate: (View & MyProtocol)?
    }
    
    let myClass = MyClass()
    //myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?'
    myClass.delegate = ViewSubclass()

    限制@objc推论

    要将Objective-C或Swift API公开,请使用@objc编译器属性。 在许多情况下,Swift编译器为您推断出这一点。 质量推理的三个主要问题是:

      1. 潜在的二进制大小显着增加
      2. 知道何时@objc

    被推断不明显

    1. 不经意间创建Objective-C选择器碰撞的机会增加。

    Swift 4通过限制@objc [SE-0160]的推论来解决这个问题。 这意味着在需要Objective-C的完整动态调度功能的情况下,您需要使用@objc 。

    您需要进行这些更改的几个示例包括private方法, dynamic声明和NSObject子类的任何方法。

    NSNumber桥接

    NSNumber和Swift数字之间已经有很多时髦的行为,这些行为一直困扰着语言太久。 幸运的是,Swift 4压缩了这些错误[SE-0170] 。

    以下是一个示例演示示例:

    let n = NSNumber(value: 999)
    let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

    Swift 3中的奇怪行为表明,如果数字溢出,则从0开始。在此示例中,999%2 ^ 8 = 231。

    Swift 4通过强制可选的转换来解决问题,只有当数字可以在包含类型中被安全地表达时才返回值。

    Swift包管理器

    在过去几个月里,Swift Package Manager已经有了一些更新。 一些最大的变化包括:

    • 从分支或提交哈希采购依赖关系
    • 更多控制可接受的包版本
    • 用更为常见的解决方案替代非直观的钉扎命令
    • 能够定义用于编译的Swift版本
    • 指定每个目标的源文件的位置

    这些都是获得SPM所需要的重大步骤。 SPM还有很长的路要走,但是我们可以通过保持积极的建议来帮助形成一个。

    有关最近解决的提案的全面了解,请查看“ Swift 4软件包管理器更新” 。

    从哪里走?

    还在举行视频
    想要更快学习吗?节省时间与我们的视频课程

    Swift语言多年来一直在增长和成熟。 提案过程和社区参与使得跟踪管道中出现的变化非常容易。 它也使东方任何一个人直接影响演变。

    随着Swift 4中的这些变化,我们终于到了一个ABI稳定性就在拐角处的地方。 升级Swift版本的痛苦越来越小。 构建性能和工具大大提高。 在苹果生态系统之外使用Swift变得越来越可行。 并且想一想,我们可能只是从一个直观的实现中完全重写String的一些;]。

    斯威夫特还有更多的东西。 要保持最新的所有更改,请确保查看以下资源:

    你对Swift 4有什么想法? 你最喜欢的变化是什么? 你还想从语言中看到什么? 你有没有找到新的和令人兴奋的东西,这里没有涵盖? 让我们在下面的评论中知道!

    本文由陈云峰翻译,转载请注明。原文地址

    展开全文
  • swift4.0 适配

    2019-06-11 12:10:30
    一、前言在我们的工程中处于swift和OC混编的状态,使用swift已经有一年半的时间了,随着Xcode9的更新,swift3.2swift4.0也随之到来,swift3.2相较于Xcode8的swift3.1变动极小,适配没遇到问题,主要关注swift4.0的...

    一、前言

    在我们的工程中处于swiftOC混编的状态,使用swift已经有一年半的时间了,随着Xcode9的更新,swift3.2swift4.0也随之到来,swift3.2相较于Xcode8swift3.1变动极小,适配没遇到问题,主要关注swift4.0的适配。

    二、查看当前工程的 swift 版本

    image.png

    三、使用 Xcode 将工程转换到 swift4.0

    1、环境

    • Xcode9.1
    • 当前 swift 版本 3.2

    2、转换步骤:

    1. 选中要转换的 target
    2. Edit -> Convert -> To Current Swift Syntax
      image.png
    3. 勾选需要转换的 targetpod 引用不用勾选),Next
      image.png
    4. 选择转换选项,Next
      这两个选项是关于 swift@objc 推断特性的,如果使用了 swift4.0 显式的 @objc 属性,能减少整体代码的大小。此时我们选 Minimize Inference(recommend),
      image.png

      关于两个选项:
    • Minimize Inference(recommend)
      根据静态推断,仅在需要的地方添加@objc属性。使用此选项后,需要按照Completing a Swift 4 minimize inference migration来完成转换。

    • Match Swift 3 Behavior
      在编译器隐式推断的任何地方向代码添加一个@objc属性。这个选项不会改变你的二进制文件的大小,因为被Swift 3隐式推断在所有的地方都添加了显式的@objc属性。

    1. 预览转换代码,没问题,Save。

    3、修改错误

    完成上述5步之后,看一下 swift 版本,已经是4.0了:

    image.png

    至此打完收工,适配结束。然而并没有,当你运行的时候会看到这个:

    image.png

    是否欲哭无泪,居然这么多错误,不用怕,其实要改动的地方并不多,有些都是重复的,可以直接全局替换就行。

    举个栗子:

    • class dynamic func
    // 转换前
    class dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
      let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
      return vc
    }
    
    // 转换后
    class @objc dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
      let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
      return vc
    }
    
    // 问题 @objc 修饰符需要前置
    // 修改成下面即可
    @objc class dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
      let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
      return vc
    }
    
    // 全局替换即可
    class @objc dynamic func  -> @objc class dynamic func复制代码

    image.png


    上面使用 dynamic 修饰符是由于以前使用 JSPatch 来做 hotfix,需要用到原来OC的运行时特性。

    四、@objc

    swift4.0 最大的特性之一就是 @objc 修饰符的变化了,它主要处理 OCswift 混编时一些方法的调用以及属性获取问题,swift4.0 将在 swift3.x 中一些隐式类型推断的特性去除以后,需要我们来手动管理 @objc 修饰符。
    在上文中使用 Xcode 转换 swift4.0 时我们勾选了 Minimize Inference 选项,那么我们就需要手动处理相关的 @objc 修饰符,来保证 OCswift 代码能正常相互调用。

    1、@objc 修饰符手动处理步骤

    使用“最小化”转换代码后,需要处理构建和运行时的问题,在完成初始的 swift4.0 转换后,需要按照下面步骤来处理其它问题。

    1. 运行你的工程
    2. 修复编译器提示需要添加 @objc 的地方
    3. 测试你的代码,并修复编译器提示使用了不推荐的隐式 @objc 引用的警告。直到没有警告发生。

    4. 打开工程的 build settings.

    5. Swift 3 @objc inference 设置为 Default.

    2、@objc 修饰符需要处理的问题

    1. 编译警告
    • swift 中编译的警告
      #selector 参数指定的实例方法必须使用 @objc 修饰,因为swift4中弃用了 @objc属性推断。
    // 下面的代码会有警告
    class MyClass : NSObject {
       func foo() {
       }
    
       func bar() {
          self.perform(#selector(MyClass.foo)
       }
    }
    warning: argument of ‘#selector’ refers to instance method ‘foo’ in ‘MyClass’ that depends on ‘@objc’ attribute inference deprecated in Swift 4复制代码
    • Objective-C 编译时警告
      OC 中调用的 swift 方法,在 swift 中需要追加 @objc 修饰,swift4 废弃了该类型推断。
    // 下面的代码会有警告
    @implementation MyClass (ObjCMethods)
    - (void)other {
          [self foo];
       }
    @end
    warning: Swift method MyClass.foo uses @objc inference deprecated in Swift 4; add @objc to provide an Objective-C entrypoint复制代码
    • 修复编译时警告
    // 通过追加 @objc 来消除警告
    class MyClass : NSObject {
       @objc func foo() {
       }
    
       func bar() {
          self.perform(#selector(MyClass.foo)
       }
    }复制代码
    • 查看所有需要添加 @objc 的编译警告
      image.png

      直接选中定位到相应位置,追加 @objc 修饰即可。
    1. 运行时警告
      运行时警告会打印在控制台:
    ***Swift runtime: 
    ClassName.swift:lineInFile:columnInLine: 
    entrypoint -[ClassName methodName] generated by implicit @objc inference is deprecated and will be removed in Swift 4; 
    add explicit @objc to the declaration to emit the Objective-C entrypoint in Swift 4 and suppress this message复制代码

    Xcode9.1 中,运行时警告在这里也能看到:

    image.png

    想要修复运行时警告,需要添加 @objc 修饰符到对应的方法或者符号。

    • 运行时警告的常见原因:

      • OC 中使用 SEL
      • swift 中使用了 perform methods
      • OC 中使用了 performSelector methods
      • 使用了 @IBOutlet 或者 @IBAction
    // 下面 swift 代码会产生运行时警告
    class MyClass : NSObject {
       func foo() {
       }
    
       func bar() {
          let selectorName = "foo"
          self.perform(Selector(selectorName)
       }
    }
    ***Swift runtime: MyClass.swift:7:7: entrypoint -[MyClass foo] generated by implicit @objc inference is deprecated and will be removed in Swift 4; add explicit @objc to the declaration to emit the Objective-C entrypoint in Swift 4 and suppress this message复制代码

    五、swift4.0 其它部分特性

    1、NSAttributedStringKey

    NSAttributedString 的初始化方法变化:

    // swift3.x
    public init(string str: String, attributes attrs: [AnyHashable : Any]? = nil)
    
    // swift4.0
    public init(string str: String, attributes attrs: [NSAttributedStringKey : Any]? = nil)复制代码

    示例:

    // 转换前
    let attributes = [NSForegroundColorAttributeName: RGB(128, g: 134, b: 146),
                              NSParagraphStyleAttributeName: paragraph,
                              NSFontAttributeName: UIFont.systemFont(ofSize: 14)] as [String : Any]
    var tipAttrText = NSAttributedString.init(string: tipText, attributes: attributes)
    
    // 转换后
    let attributes = [NSAttributedStringKey.foregroundColor.rawValue: RGB(128, g: 134, b: 146),
                              NSAttributedStringKey.paragraphStyle: paragraph,
                              NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)] as! [String : Any]
    var tipAttrText = NSAttributedString(string: tipText, attributes: attributes)
    
    // tipAttrText 初始化报错提示
    Cannot convert value of type '[String : Any]' to expected argument type '[NSAttributedStringKey : Any]?'
    
    // 修改
    NSAttributedStringKey.foregroundColor.rawValue -> NSAttributedStringKey.foregroundColor
    去掉 as! [String : Any]复制代码

    2、String

    • Stringcharacters 属性被废弃了
    let string = "abc"
    var count = string.characters.count
    
    // 第二行报错
    'characters' is deprecated: Please use String or Substring directly
    
    // 对应新方法
    count = string.count复制代码
    • StringaddingPercentEscapes 方法被废弃了
    // swift3.x
    var url = @"http://www.example.com?username=姓名"
    url = url.addingPercentEscapes(using: String.Encoding.utf8)!
    
    // 报错
    'addingPercentEscapes(using:)' is unavailable: Use addingPercentEncoding(withAllowedCharacters:) instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent since each URL component or subcomponent has different rules for what characters are valid.
    
    // 修改
    uri = uri.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!复制代码
    • substring(to:) 被废弃了
    let index = tagText.index(tagText.startIndex, offsetBy: MPMultipleStyleListItemTagMaxLength)
    
    // 警告:'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range upto' operator.
    let b = tagText.substring(to: index)
    
    // 新 API
    // 注意:a 的类型是 Substring,不是 String
    let a = tagText.prefix(upTo: index)复制代码

    3、initialize 废弃

    // swift3.x
    override class func initialize() {
        // some code
    }
    
    // 报错
    Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift复制代码

    Swift3.x 继续 Method Swizzling这篇文章里面介绍了一种解决思路。

    4、swift3 使用 #selector 指定的方法,只有当方法权限为 private 时需要加 @objc 修饰符,swift4.0 都要加 @objc 修饰符

    // 示例代码
    func startMonitor() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.refreshUserLoginStatus), name: NSNotification.Name.XSLUserLogin, object: nil)
    }
    func refreshUserLoginStatus() {
        // some code
    }
    
    // 第二行警告
    Argument of '#selector' refers to instance method 'refreshUserLoginStatus()' in 'MPUnreadMessageCountManager' that depends on '@objc' inference deprecated in Swift 4
    
    // 追加 private
    func startMonitor() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.refreshUserLoginStatus), name: NSNotification.Name.XSLUserLogin, object: nil)
    }
    private func refreshUserLoginStatus() {
        // some code
    }
    
    // 第二行报错
    Argument of '#selector' refers to instance method 'refreshUserLoginStatus()' that is not exposed to Objective-C复制代码
    • swift4.0 不再允许重载 extension 中的方法(包括instancestaticclass方法)
    // 示例代码
    class TestSuperClass: NSObject {
    }
    extension TestSuperClass {
        func test() {
            // some code
        }
    }
    class TestClass: TestSuperClass {
        // 报错:Declarations from extensions cannot be overridden yet
        override func test() {
            // some code
        }
    }复制代码

    六、pod 引用

    添加以下内容到 Podfile

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            if ['WTCarouselFlowLayout', 'XSLRevenue', 'OHHTTPStubs/Swift'].include? target.name
                target.build_configurations.each do |config|
                    config.build_settings['SWIFT_VERSION'] = '3.2'
                end
            end
        end
    end复制代码

    七、踩坑

    UITableViewDelegate 协议方法名变更,没有错误提示:

    // swift3.x
    func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat 
    
    // swift4.0
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat复制代码

    转载于:https://juejin.im/post/5a056079f265da432717b10b

    展开全文
  • xcode升级到9.0 swift改到swift4.0之后扫码一直不走回调 ,研究了好长时间,发现苹果把扫码的代理方法的参数变了之前的方法 func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects ...

    xcode升级到9.0 swift改到swift4.0之后扫码一直不走回调 ,研究了好长时间,发现苹果把扫码的代理方法的参数变了之前的方法

    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)

    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!)

    这是之前swift3.2的代理方法,swift4.0之后不会走这两个代理方法,原因是现在代理方法不一样了

    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection)


    swift4.0的两个代理方法对比之前的3.2方法,可以发现现在方法的参数变了

    将之前的两个方法的参数改好,扫码就可以正常用了

    展开全文
  • 之前的代码Swift版本是3.2(假设),而最新的Widget则使用4.0为什么不把3.2的代码升级到4.0呢?这是可以的,但是涉及到要改很多地方:而且3.24.0也不是完全不兼容,只是在某些地方新旧版本会有冲突,没必要为了这

    你会问同一个Xcode项目中还会用不同版本的Swift?

    对!举个栗子:2年前你写了一个管理密码的App,最近你觉得有必要再写一个Today Widget插件用来便捷显示密码。

    之前的代码Swift版本是3.2(假设),而最新的Widget则使用4.0

    为什么不把3.2的代码升级到4.0呢?这是可以的,但是涉及到要改很多地方:

    升级带来许多问题


    而且3.2和4.0也不是完全不兼容,只是在某些地方新旧版本会有冲突,没必要为了这一点兼容性而老项目全部升级版本。

    比如说对于NSMutableAttributedString的操作,4.0之后对于属性名称的引用方式和之前有不同,无法写出3.2和4.0都能编译通过的代码。

    对于3.2和4.0都会引用到的代码片段,我们必须因”版”而异,用Swift的编译宏命令做代码隔离:

    #if swift(>=4.0)
                attrString.addAttribute(.foregroundColor, value: UIColor.red, range: range)
                attrString.addAttribute(.backgroundColor, value: UIColor.green, range: range)
                attrString.addAttributes([.shadow:keywordShadow,.verticalGlyphForm:0], range: range)
            #else
                attrString.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: range)
                attrString.addAttribute(NSBackgroundColorAttributeName, value: UIColor.green, range: range)
            attrString.addAttributes([NSShadowAttributeName:keywordShadow,NSVerticalGlyphFormAttributeName:0], range: range)
            #endif

    以上代码在项目的公共组里的String+ext.swift文件中,该文件会同时被App和Widget项目包含使用。

    使用如上所示的代码隔离,或称为选择编译使得全部项目顺利编译通过。

    展开全文
  • Xcode 8.3.# both versions supports Swift 3.2. If your existing project has default language Swift 3.2, then it will work in Xcode 9, with minor changes. But if your project has support o...
  • Swift已经发布了4.0版本,在Xcode9中新建项目后,默认是使用4.0语法的。项目中的引用的第三方库,虽然有很多已经发不了4.0版本,但是还是有一些未及时更新的,那在作者未更新之前我们是否有更好的办法来使用这些第三...
  • Swift 4 在 Swift 3 的基础上,提供了更强大的稳健性和稳定性,为 Swift 3 提供源码兼容性,对标准库进行改进,并添加了归档和序列化等功能。 你可以通过观看 WWDC 2017: What’s New in Swift 或完整发行说明...
  • swift中因为swift 3.24.0的不同版本问题,当我们在支持3.2不支持4.0 的第三方库安装的时候会出现一个问题: 出现问题情况:支持3.2不支持4.0,当前swift版本是4.0,安装第三方库版本支持3.2,安装完了之后,就会...
  • 苹果官方的文档一般都很少去看,笔者参考官方文档和各路大神的经验,写下了一份基于Swift 4.0 的编码规范,并会持续更新,欢迎大家补充指正。 编码格式 命名规范 语法规范 1. 编码格式 1.1 使用二元...
  • 升级Swift4.0遇到的坑

    2017-09-12 19:36:19
    所以我们仅能做到将自己的业务代码(主工程代码)部分升级到Swift4.0,然后同时保留各种pod库在Swift3.2版本。 没办法,谁叫Swift4.0也还无法做到API兼容呢(但愿能在Swift5之前实现吧)。 至于我说的同时使用两个...
  • 许多优秀的 Swift 第三方框架还没有来得及迎接 Swift 4.0 的到来,它们还停留在 Swift3.x 的状态。 这个时候新建一个项目,使用cocoapods 引入所需的第三方,即使 pod install 成功后,一编译工程就是满屏红, 且...
  • Swift - KVO初探

    2019-02-28 00:58:51
    swift 4.0 KVO使用 swift4.0 中使用了不同于OC的API,使用起来更加简单。但是也需要注意几点: 是 swift class 需要在声明的时候增加 @objcMembers 关键字 被观察的属性需要用dynamic修饰 不需要在对象被回收时手动...
  • New Build System Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings......对于 Swift 和 Objective-C 混合的项目,Swift 调用 Objective-C 时,需要建立一个 Bridging H
  • 原文地址:https://github.com/easyui/blog/blob/master/Swift/2017-09-26-Swift-Tips%5BV4.0%2B%2CXcode9.0%2B%5D.md   :smile:Xcode 9 中同时集成了 Swift 3.2Swift 4。 Swift 3.2 完全兼容 Swift 3.1,...
  • 警告:Expression of type ‘UIViewController?’ is unused// 赋值给_,否则会报Expression of type 'UIViewController?' is unused _ = self.navigationController?.popViewController(animated: true)...
  • 用Xcode8打开自己的Swift2.3的项目,选择Edit->Convert->To Current Swift Syntax… 让Xcode帮我们把Swift2.3的代码转换为Swift3.0。 手动调出Xcode自动转换Swift2.3 到 Swift3.0弹出语言版本选择界面,选择Covert ...
  • swift2.3的时候,因为项目需求,需要做一个能够根据加速度来得到你行走的计步器,然后做完计步器,每走一步路后,通过跳转,跳到主线程进行画图画点。 然而最近一段时间,电脑更新到了Xcode8.0 ,一下子就能用...
  • Swift 4迁移总结:喜忧参半,新的起点 每日一篇优秀博文 这次Swift 3 到 4 的迁移代码要改动的地方比较少,花了一个下午的时间就完成了迁移。Swift 把原来 4.0 的目标从 ABI 稳定改为了源码兼容...3.24.0 ...
1 2 3 4 5 ... 20
收藏数 461
精华内容 184
热门标签
关键字:

3.2 4.0 swift