• 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 的迁移上已经大大改善了。

    参考资料:

    展开全文
  • swift3.0转4.0遇到的坑

    2018-05-15 10:38:27
    我们以斗鱼APP为例,总结swift3.0以及swift4.0转换过程中遇到的问题。 一、方法重写问题 1、swift 3.0在子类中的方法重写 我们RecommendVC类继承自BaseAnchorVC,BaseAnchorVC中包含setupUI方法,我们重写...

    我们以斗鱼APP为例,总结swift3.0以及swift4.0转换过程中遇到的问题。

    一、方法重写问题

    1、swift 3.0在子类中的方法重写

    我们RecommendVC类继承自BaseAnchorVC,BaseAnchorVC中包含setupUI方法,我们重写setupUI方法,然后再子类RecommendVC中重写setupUI方法。

    extension RecommendVC {
        override func setupUI(){
            //调用父类方法
            super.setupUI()
            collectionView.addSubview(cycleView)
            collectionView.addSubview(gameView)
            //拓宽collectionView的高度
            collectionView.contentInset = UIEdgeInsets(top: kCycleViewH+kGameViewH, left: 0, bottom: 0, right: 0)
        }
    }
    

    2、swift4.0方法的重写

    在swift4.0方法中不可使已使用这种方法重写,否则会报错。如下图所示:
    swift4.0在子类中重写方法错误
    由于无法重写方法,所以我们目前就不重写父类方法,而是重新给子类定义一个方法使用。

    二、KVC自动赋值问题

    1、在swift3.0中,我们从服务器获取数据之后,进行字典转模型,然后使用KVC自动赋值。

    class CycleModel: NSObject {
        var title : String = ""
        var pic_url : String = ""
        var anchor : AnchorModel?
        //didSet自动监控值的变化,使用guard进行检验
        var room :[String :Any]?{
            didSet{
                guard let room = room  else {
                    return
                }
                anchor = AnchorModel(dict: room)
            }
        }    
        init(dict : [String : Any]) {
            super.init()
            setValuesForKeys(dict)
        }
        //防止在KVC赋值时有找不到key导致的崩溃
        override func setValue(_ value: Any?, forUndefinedKey key: String) {}
    }

    2、在swift4.0中,直接这样使用无效。

    我们必须在属性前添加@objc,只有这样才能满足OC的KVC赋值。

    class CycleModel: NSObject {
        @objc var title : String = ""
        @objc var pic_url : String = ""
        @objc var anchor : AnchorModel?
        @objc var room :[String :Any]?{
            didSet{
                guard let room = room  else {
                    return
                }
                anchor = AnchorModel(dict: room)
            }
        }
    
        init(dict : [String : Any]) {
            super.init()
            setValuesForKeys(dict)
        }
    
        override func setValue(_ value: Any?, forUndefinedKey key: String) {
    
        }
    }
    展开全文
  • 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-02-20 00:10:44
    一、前言在我们的工程中处于swift和OC混编的状态,使用swift已经有一年半的时间了,随着Xcode9的更新,swift3.2和swift4.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 
    
    展开全文
  • //// ViewController.swift// 010-字符串//// Created by 庄壮勇 on 2018/1/3.// Copyright © 2018年 Personal. All rights reserved.//import UIKitclass ViewController: UIViewController { override func ...

    //
    //  ViewController.swift
    //  010-字符串
    //
    //  Created by 庄壮勇 on 2018/1/3.
    //  Copyright © 2018年 Personal. All rights reserved.
    //

    import UIKit

    class ViewController: UIViewController {

        override func viewDidLoad() {
            super.viewDidLoad()
            demo4()
        }

        // MARK: - 字符串的子串
        func demo4() {
        
            // 建议: 一般使用NSString 作为中转, 因为Swift取子串的方法一直在优化
            // 更容易理解
            let str = "我们一起去飞"
           
            // 1.NSString
            let ocStr = str as NSString
            let s1 = ocStr.substring(with: NSMakeRange(2, 3))
            print(s1)
           
            // 2. String 的3.0 方法
            // 偶尔使用很方便,但是复杂的截取,不好理解,而且语法经常变化!知道就好
            //let r = 0..<5
            //str.substring(with: <#T##Range<String.Index>#>)
            //Index
            // startIndex position == 0
            print(str.startIndex)
            // endIndex position == 6
            print(str.endIndex)
           
            let s2 = str.substring(from: "我们".endIndex)
            print(s2)
           
            // 取子字符串的范围
            guard let range = str.range(of: "一起") else {
                print("没有找到字符串")
                return
            }
           
            // 一定找到的范围
            print("-------")
            print(range)
            print(str.substring(with: range))
        }
       
       
        // MARK: - 格式化
        func demo3() -> () {
            let h = 8
            let m = 9
            let s = 6
           
            let dateStr = "\(h):\(m):\(s)"
            print(dateStr)
            // 使用格式字符串格式化
            let dateStr1 = String(format: "%02d:%02d:%02d",h,m,s)
            print(dateStr1)
           
        }
       
        // MARK: - 字符串拼接
        func demo2() {
            let name = "老王"
            let age = 18
            let title: String? = "BOSS"
            let point = CGPoint(x: 100, y: 200)
           
            // \(变量/常量)
            // 拼接字符串需要注意 可选项 Optional
            // NSStringFromCGPoint(point)
            let str = "\(name) \(age) \(title ?? "") \(point)"
            print(str)
        }
       
        // MARK: - 字符串的长度
        func demo1() {
            let str = "hello world你好"
            // 1>返回指定编码的对应的字节数量
            // UTF8的编码 (0~4个),每个汉字是3个字节
            print(str.lengthOfBytes(using: .utf8))
           
            // 2>字符串长度 - 返回字符的个数 (推荐使用)
            print(str.characters.count)
           
            // 3> 使用NSString 中转
            /**
                 str as NSString
            
             swift 中可以使用'值 as 类型' 类型转换
             */
           
            let ocStr = str as NSString
            print(ocStr.length)
           
        }
       
        //MARK: - 字符串的遍历
        func demo() {
           
            // NSString 不支持以下方式遍历
            let str = "我要飞得更高"
           
            for c in str.characters {
                print(c)
            }
        }
    }


    展开全文
  • Swift 4.1 Released!

    2018-03-31 11:50:15
    Swift 4.1现已正式发布!它包含核心语言的更新,包括对泛型,新构建选项的更多支持,以及对Swift Package Manager和Foundation的小改进。在稳定ABI方面也取得了重大进展。 Doug Gregor和Ben Cohen最近在Swift ...
  • 很久没看swift。 println已经变成了print了。 定义属性不用现在也会有警告了。 字符串的长度从以前的count(str) 变成现在的str7.characters.count了。 字符串从大写变为小写的方法从lastPathComponent变成了现在的...
  • 优化Swift的构建时间

    2019-07-05 10:07:16
    原文: Regarding Swift build time optimizations 作者: Robert Gummesson 译者: 孙薇 审校: 唐小引(@唐门教主),欢迎技术投稿、约稿,给文章纠错,请发送邮件tangxy@csdn.net 上周我拜读了Nickoneill...
  • Swift3.0语法变化写在前面首先和大家分享一下学习新语法的技巧: 用Xcode8打开自己的Swift2.3的项目,选择Edit->Convert->To Current Swift Syntax… 让Xcode帮我们把Swift2.3的代码转换为Swift3.0。 手动调出Xcode...
  • Swift 是苹果遵循 Apache 开源授权协议开源的一门编程语言 Swift 3 源代码不兼容旧版本,主要是因为 SE-0005 和 SE-0006 的改进,这些改进不仅影响 Standard Library APIs 命名,还会完全改变 Objective-C APIs ...
  • Swift3.0带来的变化汇总系列一——字符串与基本运算符中的变化
  • Swift4.0新特性之String、Array和Dictionary 推荐: Swift 编程语言 原文链接: What’s New in Swift 4? Guards翻译组: 中文地址 Swift 4是苹果计划在2017年秋季推出的最新版本,值得关注的是其提供了与Swift...
  • New Build System Xcode 9 引入了 New Build System,可在 Xcode 9 的 File -> Project Settings......对于 Swift 和 Objective-C 混合的项目,Swift 调用 Objective-C 时,需要建立一个 Bridging H
  • KVO 在iOS应用场景还是挺多的,...Swift Version:4.0 Xcode:10.1 (10B61) iphoneOS:12.1   问题 先看下代码,我要观察currentOrderNum这个属性的新值变化 Model: class OrderModel: NSObject { ...
  • swift3.0蓝牙开发(2)

    2017-05-08 22:10:19
    承接上篇swift3.0蓝牙开发(1)三.代码展示1.设置代理 CBCentralManagerDelegate 中心者的代理 CBPeripheralDelegate 外设的代理class ViewController: UIViewController,CBCentralManagerDelegate,...
  • Swift版本语法的变化

    2018-07-19 10:41:03
    swift4.0 新特性 简单复制整理 Swift3.0语法变化 1. swift代码自动更新 Edit-&gt;Convert-&gt;To Current Swift Syntax… 建议只更新自身模块,三方的不要更新 2. UIColor Swift ...
  • 素材:Language Guide初次接触 Swift,建议先看下 A Swift Tour ,否则思维转换会很费力,容易卡死或钻牛角尖。同样是每一章只总结3个自己认为最重要的点。这样挺好!强迫你去思考去取舍。以后再看,也方便快速重建...
  • Swift 4 发布已经有一段时间了,不知道大家有没有切换到 4.0 版本。 这次 4.0 更新给我最大的感受就是没有了前几次升级的跳跃式变化。 不用为了更新语言版本,完全推翻已有的项目,这也是 Swift 慢慢趋向于稳定的...
  • key-path 通常是用在键值编码(KVC)与键值观察(KVO)上的,KVC、KVO 相关内容可以参考我之前写的这篇文章:Swift - 反射(Reflection)的介绍与使用样例(附KVC介绍) 1.Swift 3 之前使用的是 String 类型的 key-...
1 2 3 4 5 ... 20
收藏数 1,426
精华内容 570