Swift 4之更新

相比較Swift 3時的更新,此次Swift 4的變化要小得多,甚至還以新穎和改進(jìn)的形式重塑了一些舊的功能。更讓我們高興的是,我們不需要把項目立即就更新到Swift 4.0!

集成到Xcode 9中的Swift 4編譯器有一個“Swift 3.2”模式。這不是一個獨(dú)立的編譯器,而是一種模式,這種模式允許你繼續(xù)構(gòu)建你的Swift 3代碼,直到你想更新到Swift 4為止。當(dāng)然了,你可能會擔(dān)心CocoaPod是否需要馬上update,在這里我可以放心地告訴你,不需要。因為你可以在你的項目中選擇你的Swift語言版本,這樣你就可以在同一個項目中混合Swift 3和Swift 4的target。話不多說,現(xiàn)在看看到底有哪些更新吧!

One-sided ranges 單邊區(qū)間

Swift 4引入了RangeExpression協(xié)議,這是一種簡化如何描述區(qū)間的新協(xié)議。它被許多其他的協(xié)議所采用,以支持創(chuàng)建新的前綴和后綴操作符(prefix and postfix )。因此,您可以忽略一個區(qū)間的上界或下界,從而創(chuàng)建一個One-sided range(單邊區(qū)間)。

當(dāng)您想要在集合中獲取自索引之后的一個值,或到某個索引結(jié)束的值時,這個特性將非常有用。

let esports = ["Hearthstone", "CS:GO", "League of Legends",
               "Super Smash Bros", "Overwatch", "Gigantic"]
esports[3...]
// returns ["Super Smash Bros", "Overwatch", "Gigantic"]
// In Swift 3, you had to write
esports[3..<esports.endIndex]
esports[...2]
// returns ["Hearthstone", "CS:GO", "League of Legends"]
esports[..<2]
// returns ["Hearthstone", "CS:GO"]

infinite sequence 無限序列

一個單邊區(qū)間可以用來創(chuàng)建一個無限序列,一個只指定開始值的序列。例如,您可以構(gòu)建一個字符與ASCII碼表十進(jìn)制數(shù)字相匹配的元組數(shù)組。

let uppercase = ["A", "B", "C", "D"]
let asciiCodes = zip(65..., uppercase)
print(Array(asciiCodes))
// prints [(65, "A"), (66, "B"), (67, "C"), (68, "D")]

Pattern matching 模式匹配

單邊區(qū)間可以極大地簡化switch語句的模式。

func gameRank(_ index: Int) -> String {
  switch index {
  case ...1:
    return "Oldie but goodie"
  case 3...:
    return "Meh"
  default:
    return "Awesome-sauce!"
  }
}
gameRank(2)
// prints "Awesome-sauce!"
func sentiment(_ rating: Double) -> String {
    switch rating {
    case ..<0.33:
        return "??"
    case ..<0.66:
        return "??"
    default:
        return "??"
    }
}
    sentiment(0.5)
// return "??"

Strings 字符串

字符串在Swift 4中獲得了很多的恩寵,蘋果在努力使它們變得更強(qiáng)大,更容易使用。最重要的變化是字符串現(xiàn)在是集合,就像它們在Swift 1中一樣。這意味著,一個集合可以做的事情,一個字符串也可以做到!

let text = "Hakuna Matata"
let unicodeText = "???????????"
text.count
text.isEmpty
"".isEmpty
// 13
// false
// true
// `reversed()` 返回 一個 `ReversedCollection<String>`
// 所以需要顯式轉(zhuǎn)換
String(text.reversed()) // "atataM anukaH"

在字符串中遍歷每個字符是很容易的,當(dāng)然它也適用于Unicode字符。

for c in unicodeText {
    print(c) // 打印出每個Unicode字符
}

字符串下標(biāo)的類型不是像Int這樣的類型,而是一個String.IndexRange<String.Index>這樣的類型.。

var index = text.index(text.startIndex, offsetBy: 7)
text[index]
// "M"

//你可以使用prefix(upTo:) 和 suffix(from:)方法進(jìn)行字符串截選
let upString = text.prefix(upTo: index)
let fromString = text.suffix(from: index)

//當(dāng)然你也可以使用單邊區(qū)間
let lastWord = text[index...]
// lastWord is "Matata"
index = text.index(text.endIndex, offsetBy: -7)
let firstWord = text[..<index]
// firstWord is "Hakuna"

Introducing Substring 字符子串

對字符串進(jìn)行下標(biāo)操作時會返回部分視圖,它源于原始字符串的buffer,這些操作增加了buffer的引用計數(shù)。取決于應(yīng)用程序及其數(shù)據(jù)的不同,這有可能在內(nèi)存中保留大量未使用的字符串。雖然從技術(shù)上講,這并不是一種內(nèi)存泄漏,但它卻不怎么讓人喜歡。

Swift標(biāo)準(zhǔn)庫通過引入一種新的類型:Substring來解決上面描述的問題,它是由字符串下標(biāo)操作返回的類型。因為大多數(shù)API仍將繼續(xù)使用String類型參數(shù),所以您必須顯式地利用Substring類型來創(chuàng)建一個新的String類型字符串并使用這個新的字符串進(jìn)行其他操作。而這些操作允許之前原始而又長的字符串自然地離開作用域,而被釋放。

其實沒有你看起來的那么難理解,因為標(biāo)準(zhǔn)庫同時也引入了一個新的協(xié)議:StringProtocol。大多數(shù)StringAPI都被轉(zhuǎn)移到StringProtocol中,StringSubstring都遵從這個新協(xié)議。

type(of: lastWord)
// Substring.Type
lastWord.uppercased()
// "MATATA"
let lastWordAsString = String(lastWord)
type(of: lastWordAsString)
// String.Type

Unicode magic

Swift 3時,如果您想要訪問Character中單個的Unicode數(shù)值,您首先必須將Character轉(zhuǎn)換為String類型。Swift 4.0中,Character類型引入了一個unicodeScalars屬性。

let c : Character = "??"
Array(c.unicodeScalars)
//[128149]

Swift 3中以下表達(dá)式將給出錯誤的答案。而Swift 4可以正確地處理它們,根據(jù)所看到的內(nèi)容給出其長度,而不需要去encode它。

"?????".count
//3
"??".count
//1
"????".count
//2

Converting between Range<String.Index> and NSRange (Range<String.Index> 和 NSRange 類型之間的轉(zhuǎn)換)

Swift中的字符串區(qū)間是由Range<String.Index> 所描述。許多Foundation API(如:NSRegularExpression,NSAttributedString,NSLinguisticTagger) 需要NSRange去描述區(qū)間。

現(xiàn)在Foundation框架在NSRange和Range<String.Index> 中都引進(jìn)了新的構(gòu)造方法。這使得兩者之間的轉(zhuǎn)換更加容易。

let population = "1????2????3????"
population.count
// 6
var nsRange = NSRange(population.startIndex...,
                      in: population)
// population.startIndex... is a Range<String.Index>
// (0, 29)
population.utf16.count
// 29
let display = NSMutableAttributedString(string: population,attributes: [.font: UIFont.systemFont(ofSize: 20)])

如預(yù)期所想,NSRange的長度與UTF-16視圖的長度相匹配。顯示是這樣的:

UTF-16.png
let oneIndex = population.index(of: "1??")!
let twoIndex = population.index(of: "2??")!
let threeIndex = population.index(of: "3??")!
var range = oneIndex..<twoIndex
nsRange = NSRange(range, in: population)
display.addAttribute(.font, value: UIFont.systemFont(ofSize: 40), range: nsRange)

上面代碼中創(chuàng)建出每個數(shù)字emoji的索引,其中為第一部分的字符串創(chuàng)建出一個Range<String.Index>類型的區(qū)間,并將其轉(zhuǎn)換為NSRange類型。然后,改變其字體的大小,顯示如下:

utf-16_002.png
range = twoIndex..<threeIndex
nsRange = NSRange(range, in: population)
display.addAttribute(.font, value: UIFont.systemFont(ofSize: 30), range: nsRange)

接下來,字符串的中間部分轉(zhuǎn)換為Range<String.Index>區(qū)間,轉(zhuǎn)換它到NSRange并改變字體大小。顯示如下:

utf-16_003.png

在另一種方法中,用NSRange來創(chuàng)建一個Range<String.Index>區(qū)間是很容易的。注意,這個構(gòu)造方法沒有成功完成初始化,因此結(jié)果是一個可選類型。

let textInput = "You have traveled 483.2 miles."
let pattern = "[0-9]+(\\.([0-9])?)?"
let regex = try! NSRegularExpression(pattern: pattern, options: [])
nsRange = NSRange(textInput.startIndex..., in: textInput)
let mileage = regex.rangeOfFirstMatch(in: textInput, range: nsRange)
range = Range(mileage, in: textInput)!
textInput[range]
//"483.2"

Multi-line string literals 多行字符串字面量

現(xiàn)在您可以創(chuàng)建多行字符串字面量,使其更容易生成精確的格式化輸出或?qū)⑤斎胍砸环N“漂亮的”(如:JSON或HTML格式)方式粘貼到你的代碼中。字符串插值(interpolation)在這里仍然有效,您甚至可以對換行進(jìn)行轉(zhuǎn)義,這樣它們就不會包含在輸出的文字中了!

字面量語法由""" """分隔開,每行的縮進(jìn)取決于每一行開始你刪除的空格是多少。您可以在字面量語法中使用引號,而不用對它們進(jìn)行轉(zhuǎn)義。

let firstVerse = """
Half a league, half a league,
  Half a league onward,
All in the valley of Death
  Rode the six hundred.
"Forward, the Light Brigade!
"Charge for the guns!" he said:
Into the valley of Death
  Rode the six hundred.
"""
indentation.png
let details = """
Note that the indentation of the
  closing delimiter determines
  the amount of whitespace removed.
You can insert \(firstWord) and \
\(lastWord) and escape newlines too!
"""
note.png

Dictionary enhancements 字典的強(qiáng)化

作為一個Swift程序員,字典對你的日常生活有多么重要想必大家都清楚。Swift 4帶來了一些改進(jìn),使它變得更強(qiáng)大、更有用、更具可操作性。

Sequence-based initializer 基于序列的構(gòu)造函數(shù)

現(xiàn)在可以從鍵值對的序列中創(chuàng)建字典。例如,您可以創(chuàng)建一個有編號的項目列表。

let groceries = Dictionary(uniqueKeysWithValues: zip(1..., ["Prosciutto", "Heavy Cream", "Butter", "Parmesan",
     "Small shells"])
)
// [5: "Small shells", 2: "Heavy Cream", 3: "Butter",
//  1: "Prosciutto", 4: "Parmesan"]

或者,你已經(jīng)有了一個Tuple類型的數(shù)據(jù), 可以直接使用它去創(chuàng)建字典:

let housePointTotals = [("Slytherin", 472),
                        ("Ravenclaw", 426),
                        ("Hufflepuff", 352),
                        ("Gryffindor", 312)]
let banquetBegins = Dictionary(uniqueKeysWithValues: housePointTotals)
// ["Ravenclaw": 426, "Hufflepuff": 352, "Gryffindor": 312,
//  "Slytherin": 472]

Merging

字典中現(xiàn)在包含一個允許您merge兩個字典的構(gòu)造方法。如果您想將一個字典merge到另一個字典中,那么Swift也提供了一個merge方法。兩者都需要您對閉包進(jìn)行操作,以解決由重復(fù)鍵(Key)引起的在merge過程中值(Value)的沖突。

/// - Parameters:
///- keysAndValues: 用于新字典的鍵值對序列
///- combine:            一個在出現(xiàn)重復(fù)鍵(key)的時候才會被調(diào)用的閉包(用于處理和并key之后value的取值問題是應(yīng)該取那個,如何取的問題),
                                    該閉包返回最終需要的值(可以在閉包內(nèi)部決定是返回那個value值,沖突前的還是沖突后的)。
 public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value)

通常在使用上述方法的時候,閉包會以尾隨閉包的形式出現(xiàn)

let duplicates = [("a", 1), ("b", 2), ("a", 3), ("b", 4)]
let oldest = Dictionary(duplicates) { (current, ignore) in
//    print(current,ignore)
   return current
}
// ["b": 2, "a": 1]

上面代碼的意思: duplicates是遵從Sequence協(xié)議,且內(nèi)部元素是Tuple類型的序列,尾隨閉包中的current值是在內(nèi)部迭代過程中首先發(fā)現(xiàn)的沖突Value,ignore是后發(fā)現(xiàn)的沖突Value,并返回先發(fā)現(xiàn)的Value(即curren的值是1,2)

您可以利用閉包來改變您的輸入。下面是一個更強(qiáng)大的示例,它使用了一系列鍵值對,并將它們轉(zhuǎn)換為數(shù)組的字典:

let sortingHat = [
    ("Gryffindor", "Harry Potter"), ("Slytherin", "Draco Malfoy"),
    ("Gryffindor", "Ron Weasley"),
    ("Slytherin", "Pansy Parkinson"),
    ("Gryffindor", "Hermione Granger"),
    ("Hufflepuff", "Hannah Abbott"),
    ("Ravenclaw", "Terry Boot"), ("Hufflepuff", "Susan Bones"),
    ("Ravenclaw", "Lisa Turpin"),
    ("Gryffindor", "Neville Longbottom")
]
let houses = Dictionary( sortingHat.map { ($0.0, [$0.1]) },
                         uniquingKeysWith: { (current, new) in
                            print(current, new)
                            return current + new
})
// ["Ravenclaw": ["Terry Boot", "Lisa Turpin"],
// "Hufflepuff": ["Hannah Abbott", "Susan Bones"],
// "Slytherin": ["Draco Malfoy", "Pansy Parkinson"],
// "Gryffindor": ["Harry Potter", "Ron Weasley",
//                "Hermione Granger", "Neville Longbottom"]]

正如您所看到的,您可以做的不僅僅是創(chuàng)建一個字典。如果您想知道每一個字母在字符串中的出現(xiàn)次數(shù),對于這種問題的解決可以說是它的拿手好戲。

let spell = "I solemnly swear I am up to no good"
var frequencies: [Character: Int] = [:]
let baseCounts = zip(
    spell.filter { $0 != " " }.map { $0 },
    repeatElement(1, count: Int.max))
frequencies = Dictionary(baseCounts, uniquingKeysWith: +)
// ["w": 1, "p": 1, "n": 2, "o": 5, "I": 2, "u": 1, "t": 1,
//  "d": 1, "a": 2, "r": 1, "m": 2, "s": 2, "e": 2, "l": 2,
//  "g": 1, "y": 1]

如果您有一組默認(rèn)值,并希望將它們與用戶設(shè)置組合在一起的話,對于這種問題的解決將是 merge 方法的完美用途。

 將別的給定字典合并到此字典中,使用閉包來確定任何重復(fù)鍵(key)的值。
 /// - Parameters:
 ///   - other:  A dictionary to merge.
 ///   - combine: A closure that takes the current and new values for any
 ///     duplicate keys. The closure returns the desired value for the final
 ///     dictionary.為任何重復(fù)鍵接受當(dāng)前和新值的閉包。閉包返回最終字典所需的值。
 public mutating func merge(_ other: [Dictionary.Key : Dictionary.Value], uniquingKeysWith combine: (Dictionary.Value, Dictionary.Value) throws -> Dictionary.Value) rethrows

merge方法中的尾隨閉包用法和上面構(gòu)造方法的尾隨閉包用法一致。

let defaultStyling: [String: UIColor] = [
    "body": .black, "title": .blue, "byline": .green
]
var userStyling: [String: UIColor] = [
    "body": .purple, "title": .blue
]
userStyling.merge(defaultStyling) { (user, _) -> UIColor in
     user }
// ["body": .purple, "title": .blue, "byline": .green]

Default value for subscript 下標(biāo)的默認(rèn)值

對字典的進(jìn)行取值時,經(jīng)常會出現(xiàn)可選項的情況,這就要求您采取盡可能簡單的代碼,可以使用可選綁定、強(qiáng)制解包或optional chaining對其解包。在過去,解決這個問題的一種常見方法是使用空合運(yùn)算符(? ?)來提供默認(rèn)值,并使結(jié)果是非可選的(non-optional)。

Swift 4增加了在下標(biāo)中指定默認(rèn)值的能力:

let swift3 = banquetBegins["House elves"] ?? 0
let swift4 = banquetBegins["House elves", default: 0]
// both are 0; both are Int and not Int?
let housePoints = banquetBegins["Hufflepuff", default: 0]
// value is 352 with or without the default.
// Without, type is Int?; with, Int.

這種特性同時提供了另一種方法來實現(xiàn)您先前看到的頻率計數(shù)問題。

frequencies.removeAll()
print(frequencies)
let dict = spell.filter { $0 != " " }
spell.filter { $0 != " " }.map {
    frequencies[$0, default: 0] += 1
}

Filtering and mapping

在Swift 4中,對字典執(zhí)行filter操作, 其會在結(jié)果中保存字典的結(jié)構(gòu)和類型。

let oddGroceries = groceries.filter { $0.key % 2 == 1 }
// [5: "Small shells", 3: "Butter", 1: "Prosciutto"]

filter方法同時也可以用于Set類型:

let set: Set = ["a", "b", "c", "d", "e"]
let filteredSet = set.filter { $0.hashValue % 2 == 0 }
// ["b", "d"]

map函數(shù)必定返回一個數(shù)組結(jié)果。通常,在使用字典的時候,這樣的返回值會讓你很難過! Swift 4添加了mapValues的方法,以允許您保留字典的結(jié)構(gòu)和類型。

let mirroredGroceries = oddGroceries.mapValues {
    String($0.reversed())
}
// [5: "sllehs llamS", 3: "rettuB", 1: "ottuicsorP"]

Grouping

Dictionary最強(qiáng)大的新特性之一是,它可以基于任意謂詞對數(shù)據(jù)進(jìn)行分區(qū),創(chuàng)建組或存儲類似數(shù)據(jù)的buckets。最簡單的例子是對名單的第一個字母進(jìn)行分組展示。

let names = ["Harry", "ron", "Hermione", "Hannah",
             "neville", "pansy", "Padma"].map { $0.capitalized }
let nameList = Dictionary(grouping: names) { $0.prefix(1) }
// ["H": ["Harry", "Hermione", "Hannah"], "R": ["Ron"],
// "N": ["Neville"], "P": ["Pansy", "Padma"]]

你可以指定一個任意的謂詞,意味著您能更有創(chuàng)造力。

enum Multiples {
    case threes, notThrees
}
let numbers = 1...18
let predicate: (Int) -> Multiples = { $0 % 3 == 0 ? .threes : .notThrees }
let multiplesOfThree = Dictionary(grouping: numbers,by: predicate)
// [.threes: [3, 6, 9, 12, 15, 18],
// [.notThrees: [1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17]]
type(of: multiplesOfThree)
// [Multiples: [Int]]

在一個更實際的例子中,您可能希望基于一個結(jié)構(gòu)體中的值來分組。將grouping與Swift 4的新keyPath特性組合起來,將使這成為一種輕而易舉的事情。

// 1
struct Student {
    let firstName: String
    let lastName: String
}
// 2
let classRoll = sortingHat.map { $0.1.split(separator: " ") }
    .map { Student(firstName: String($0[0]),
                   // 3
        lastName: String($0[1])) }
let lastNameKeypath = \Student.lastName
// 4
let contactList = Dictionary(grouping: classRoll) {
    $0[keyPath: lastNameKeypath].prefix(1)
}

上面的代碼做了以下幾個方面的事情:

  1. 定義一個學(xué)生的結(jié)構(gòu)體。
  2. 使用之前的數(shù)組來創(chuàng)建一個Student數(shù)組的值。
  3. 使用新的keyPath語法引用Student中l(wèi)astName字段。
  4. 用last name的第一個字母來對Student進(jìn)行分組。

Generic subscripts and associated typeconstraints 泛型下標(biāo)和關(guān)聯(lián)類型約束

在Swift 3中使用混合數(shù)據(jù)類型的字典是相當(dāng)痛苦的,因為在使用它之前需要對每個值進(jìn)行類型轉(zhuǎn)換。Swift 4現(xiàn)在允許下標(biāo)返回一個泛型類型。

struct Grade {
    private var data: [String: Any]
    init(data: [String: Any]) {
        self.data = data
    }
    subscript<T>(key: String) -> T? {
        return data[key] as? T
    }
}
let gradebook = Grade(data: ["name": "Neil Armstrong",
                             "exam": "LEM Landing",
                             "grade": 97])
let who: String? = gradebook["name"]
let grade: Int?  = gradebook["grade"]
// No need to coerce the type with "as?"
//不需要使用as?對類型進(jìn)行強(qiáng)制轉(zhuǎn)換

這絕對不是實現(xiàn)結(jié)構(gòu)體的最佳方式。但是,如果您使用的是新的可Decodable工具,這個概念可能會簡化您的自定義init(from:)。

下標(biāo)本身也可以是泛型。例如,您可以實現(xiàn)一種使用Sequence協(xié)議從集合類型中獲取一個數(shù)組的方法。

extension Grade {
    subscript<Keys: Sequence>(keys: Keys) -> [Any]
        where Keys.Element == String {
            var values: [Any] = []
            for key in keys {
                if let value = data[key] {
                    values.append(value)
                }
            }
            return values
    }
}
gradebook[["name", "grade"]]
gradebook[Set(["name", "grade"])]
// 上面都返回 ["Neil Armstrong", 97]

您還可以使用該特性向標(biāo)準(zhǔn)庫類型中添加新功能以供自己使用。

extension Collection {
    subscript<Indices: Sequence>(indices: Indices) -> [Element]
        where Indices.Element == Index {
            var result: [Element] = []
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}
let words = "It was the best of times it was the worst of times"
    .split(separator: " ")
words[[3, 9, 11]]
// ["best", "worst", "times"]

前面兩個例子都利用了另一個新特性:關(guān)聯(lián)類型約束。關(guān)聯(lián)類型現(xiàn)在可以用where子句約束。這極大地簡化了使用泛型的工作。

例如,在擴(kuò)展Grade的時候,Swift 3要求您指定where Keys.Iterator.Element == String。

允許對關(guān)聯(lián)類型進(jìn)行約束可以更容易地擴(kuò)展標(biāo)準(zhǔn)庫。

extension Sequence where Element: Numeric {
    var product: Element {
        return self.reduce(1, *)
    }
}
[2,4,6,8].product
// 384
[1.5, 3.0, -2.0].product
// -9 (a Double)

Limiting @objc inference 限制@objc引用

為了使Objective-C代碼編寫的功能能夠在Swift語言中使用,Swift編譯器必須生成一種稱為“thunk”的方法,以便在Objective-C和Swift調(diào)用約定之間進(jìn)行轉(zhuǎn)換。這些thunks只適用于來自O(shè)bjective-C語言的調(diào)用,而不是Swift。為了減輕現(xiàn)有的Objective-C代碼之間的相互協(xié)作的負(fù)擔(dān),Swift 3(以及更早的版本)在許多情況下需要這些thunks代碼。

不幸的是,經(jīng)驗表明,許多這樣的thunks是不需要的,因為這會導(dǎo)致應(yīng)用程序二進(jìn)制文件的體積增大。在很多情況下,應(yīng)用程序大小的6%-8%是由這種“膠水”代碼組成的。為了消除這種浪費(fèi),Swift 4做出了改善。

例如,從NSObject派生的類不再被Objective-C自動訪問。因為您必須顯式地使用@objc關(guān)鍵字來讓Swift中的方法對Objective-C可見。

在計算機(jī)編程中,一個thunk代表一個創(chuàng)建的子程序,通常它是自動創(chuàng)建的,以幫助調(diào)用另一個子程序。Thunks主要用于表示子程序需要執(zhí)行的額外計算,或者調(diào)用不支持一般調(diào)用機(jī)制的程序。Thunks有許多其他的應(yīng)用程序來生成編譯代碼和模塊編程。

class MyClass: NSObject {
    func print() { Swift.print("hello") } // not visible to Obj-C
    @objc func show() { print() } // visible to Obj-C
}

蘋果建議你將所有需要對Objective-C可見的方法進(jìn)行分組并放置在一個Extension內(nèi)。如果您將@objc關(guān)鍵字添加到Extension本身,那么它所包含的一切都將是可訪問的。如果您需要在這個Extension內(nèi)排除一個方法,不讓其被Objective-C可見,使用@nonobjc關(guān)鍵字對其單獨(dú)標(biāo)注。

@objc extension MyClass {
  func f(_ foo: String?) {}
  @nonobjc func g(_ goo: Int?) {}
}

上面如果沒有關(guān)鍵字@nonobjc,編譯器會對方法g(_:)報出一個錯誤:

error: method cannot be in an @objc extension of a class (without @nonobjc) because the type of the parameter cannot be represented in Objective-C

如果你需要一個類連同它的所有extensions,及其子類和子類的extensions可以在Objective-C中被訪問到,你需要使用關(guān)鍵字@objcMembers對這個類進(jìn)行標(biāo)識.

@objcMembers
class MySecondClass: NSObject {
  func f() {}
  // can't be called from ObjC but no error
  func g() -> (Int, Int) {
    return (1, 1)
  }
}

使用關(guān)鍵字@nonobjc對你不想要被Objective-C訪問的方法進(jìn)行標(biāo)識

@nonobjc extension MySecondClass {
  func h() -> Int? { return nil }
  func j(_ value: Double?) {}
}

Swift 4 inference

Swift 4中對@objc的的自動推導(dǎo),蘋果對其規(guī)則進(jìn)行了明確的定義。

如果聲明是對使用關(guān)鍵字@objc方法聲明的重寫,它可以推導(dǎo)出這個重寫的方法和@objc關(guān)鍵字標(biāo)識的方法一樣。

class Super {
  @objc func foo() {}
}
class Sub: Super {
  // inferred @objc
  override func foo() {}
}

如果一個方法是對被@objc標(biāo)識的協(xié)議進(jìn)行實現(xiàn),它可以推導(dǎo)出這個實現(xiàn)的方法和@objc關(guān)鍵字標(biāo)識的方法一樣。

@objc protocol MyDelegate {
func bar() }
class MyThirdClass: MyDelegate {
  // inferred @objc
     func bar() {}
}

在這種情況下,這種推導(dǎo)是必需的,因為對MyDelegate.bar()的調(diào)用,不管是來自O(shè)bjective-C還是Swift,都是通過Objective-C的消息發(fā)送機(jī)制,所以對協(xié)議遵守也需要其能被Objective-C所訪問到。

基于同樣的原因,Swift 4也會對任何聲明有一個或多個以下屬性進(jìn)行推導(dǎo):

  • @IBAction
  • @IBOutlet
  • @IBInspectable
  • @GKInspectable
  • @NSManaged

最后,雖然dynamic關(guān)鍵字是使用objective-C消息機(jī)制來實現(xiàn)的,但它不再引起@objc推導(dǎo)。您必須顯式地添加注釋。

class Piggy {
    @objc dynamic var dynamicallyDispatchedString: String = ""
    
    @objc dynamic func dynamicallyDispatchedFunction() {
        
    }
}

Swift 3中dynamic關(guān)鍵字,它用于修飾變量或函數(shù),它的意思與OC不同。它告訴編譯器使用動態(tài)分發(fā)而不是靜態(tài)分發(fā)。OC區(qū)別于其他語言的一個特點(diǎn)在于它的動態(tài)性,任何方法調(diào)用實際上都是消息分發(fā),而Swift則盡可能做到靜態(tài)分發(fā)。

因此,Swift 3中標(biāo)記為dynamic的變量/方法會隱式的加上@objc關(guān)鍵字,它會使用OC的runtime機(jī)制。

Other changes 其他的變化

還有許多其他更小的變化值得注意。

Private access in extensions

Swift 3引入了fileprivate,破壞了大量的代碼,但這些代碼已被正確地劃分為extensions。Swift 4改善了這種狀況?,F(xiàn)在,聲明為private的方法和變量可以在同一個源文件中進(jìn)行擴(kuò)展。fileprivate仍然有其“單一文件作用域”的含義,但它應(yīng)該盡可能不被使用。


struct Person {
    private let firstName: String
    private let lastName: String
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    var name: String {
        return "\(firstName) \(lastName)"
    }
}
extension Person {
    func greeting(with message: String) -> String {
        return "\(message), \(firstName)!"
    }
}
let dumbledore = Person(firstName: "Albus",
                        lastName: "Dumbledore")
dumbledore.greeting(with: "Good mornafterevening")

swapAt(::)

在為未來做準(zhǔn)備時,Swift 4引入了一個新概念,即對內(nèi)存的獨(dú)占訪問,這樣只有一個參與者可以一次寫入一個對象所占用的內(nèi)存。這將中斷方法swap(_:_:)的使用?,F(xiàn)在,您可以使用swapAt(_:_:)來代替它的作用。

var numbers = Array(1...5)
//swap(&numbers[1], &numbers[3]) // Illegal in Swift 4
numbers.swapAt(1, 3)
// [1, 4, 3, 2, 5]

NSNumber bridging

Swift 3沒有檢查存儲在NSNumber中的整數(shù)值是否可以用Swift標(biāo)量類型(scalar type)來表示。例如:


let n = NSNumber(value: 603)
let v = n as? Int8

常量v將會被賦值為91。這顯然是錯誤的和不安全的。在Swift 4中,常量 v將是nil。引用Swift發(fā)展建議,“as?”對于NSNumber,應(yīng)該意味著“我可以將NSNumber中的數(shù)值安全地存儲在這里嗎嗎?” 其同樣適用于關(guān)鍵字is

if n is Int8 {
  print("n must be pretty small")
} else {
  print("nope, doesn't fit. Get a bigger bucket!")
}

Composing classes and protocols

在Swift 4之前,沒有辦法指定一個對象必須是一個特定的類型,并且符合一個協(xié)議?,F(xiàn)在可以了!

protocol MySpecialDelegateProtocol {}
class MySpecialView: UIView {}
class MyController {
  var delegate: (UIView & MySpecialDelegateProtocol)?
}

這中方式也在Swift標(biāo)準(zhǔn)庫中以有趣的方式使用

public typealias Codable = Decodable & Encodable

Migrating to Swift 4

將項目遷移到Swift 4,是一個簡單的過程。在Xcode 9中,選擇Edit\Convert\To Current Swift Syntax... 。有一個例外, migrator將詢問您想要什么類型的@objc推導(dǎo)。推薦選擇:Minimize Inference

Migrating_to_Swift_4_001.png

如果你按照建議,選了Minimize Inference, 會出現(xiàn)以下警告:

The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please
address deprecated @objc inference warnings, test your code with "Use of
deprecated Swift 3 @objc inference" logging enabled, and then disable
inference by changing the “Swift 3 @objc Inference” build setting to
“Default” for the “<your project>” target.

一旦您對您的源代碼中所有必要的注釋感到滿意,就打開Target 的 build settings,并將Swift 3的@objc Inference更改為Default,就像在警告消息中指出的那樣。

Migrating_to_Swift_4_002.png

翻譯自:Ray Wenderlich 新書 iOS 11 by Tutorials 中的 What's New in Swift 4 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容