app更新提示框 swift4
2017-12-14 03:29:41 weixin_34218579 阅读数 1

相比较Swift 3时的更新,此次Swift 4的变化要小得多,甚至还以新颖和改进的形式重塑了一些旧的功能。更让我们高兴的是,我们不需要把项目立即就更新到Swift 4.0!

集成到Xcode 9中的Swift 4编译器有一个“Swift 3.2”模式。这不是一个独立的编译器,而是一种模式,这种模式允许你继续构建你的Swift 3代码,直到你想更新到Swift 4为止。当然了,你可能会担心CocoaPod是否需要马上update,在这里我可以放心地告诉你,不需要。因为你可以在你的项目中选择你的Swift语言版本,这样你就可以在同一个项目中混合Swift 3和Swift 4的target。话不多说,现在看看到底有哪些更新吧!

One-sided ranges 单边区间

Swift 4引入了RangeExpression协议,这是一种简化如何描述区间的新协议。它被许多其他的协议所采用,以支持创建新的前缀和后缀操作符(prefix and postfix )。因此,您可以忽略一个区间的上界或下界,从而创建一个One-sided range(单边区间)。

当您想要在集合中获取自索引之后的一个值,或到某个索引结束的值时,这个特性将非常有用。

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 无限序列

一个单边区间可以用来创建一个无限序列,一个只指定开始值的序列。例如,您可以构建一个字符与ASCII码表十进制数字相匹配的元组数组。

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 模式匹配

单边区间可以极大地简化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中获得了很多的恩宠,苹果在努力使它们变得更强大,更容易使用。最重要的变化是字符串现在是集合,就像它们在Swift 1中一样。这意味着,一个集合可以做的事情,一个字符串也可以做到!

let text = "Hakuna Matata"
let unicodeText = "✋🐘👌🌹🐩🤝"
text.count
text.isEmpty
"".isEmpty
// 13
// false
// true
// `reversed()` 返回 一个 `ReversedCollection<String>`
// 所以需要显式转换
String(text.reversed()) // "atataM anukaH"
复制代码

在字符串中遍历每个字符是很容易的,当然它也适用于Unicode字符。

for c in unicodeText {
    print(c) // 打印出每个Unicode字符
}
复制代码

字符串下标的类型不是像Int这样的类型,而是一个String.IndexRange<String.Index>这样的类型.。

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

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

//当然你也可以使用单边区间
let lastWord = text[index...]
// lastWord is "Matata"
index = text.index(text.endIndex, offsetBy: -7)
let firstWord = text[..<index]
// firstWord is "Hakuna"
复制代码

Introducing Substring 字符子串

对字符串进行下标操作时会返回部分视图,它源于原始字符串的buffer,这些操作增加了buffer的引用计数。取决于应用程序及其数据的不同,这有可能在内存中保留大量未使用的字符串。虽然从技术上讲,这并不是一种内存泄漏,但它却不怎么让人喜欢。

Swift标准库通过引入一种新的类型:Substring来解决上面描述的问题,它是由字符串下标操作返回的类型。因为大多数API仍将继续使用String类型参数,所以您必须显式地利用Substring类型来创建一个新的String类型字符串并使用这个新的字符串进行其他操作。而这些操作允许之前原始而又长的字符串自然地离开作用域,而被释放。

其实没有你看起来的那么难理解,因为标准库同时也引入了一个新的协议:StringProtocol。大多数StringAPI都被转移到StringProtocol中,StringSubstring都遵从这个新协议。

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

Unicode magic

Swift 3时,如果您想要访问Character中单个的Unicode数值,您首先必须将Character转换为String类型。Swift 4.0中,Character类型引入了一个unicodeScalars属性。

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

复制代码

Swift 3中以下表达式将给出错误的答案。而Swift 4可以正确地处理它们,根据所看到的内容给出其长度,而不需要去encode它。

"✋🐘👌".count
//3
"😢".count
//1
"🐵🐶".count
//2
复制代码

Converting between Range<String.Index> and NSRange (Range<String.Index> 和 NSRange 类型之间的转换)

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

现在Foundation框架在NSRange和Range<String.Index> 中都引进了新的构造方法。这使得两者之间的转换更加容易。

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)])
复制代码

如预期所想,NSRange的长度与UTF-16视图的长度相匹配。显示是这样的:

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)

复制代码

上面代码中创建出每个数字emoji的索引,其中为第一部分的字符串创建出一个Range<String.Index>类型的区间,并将其转换为NSRange类型。然后,改变其字体的大小,显示如下:

range = twoIndex..<threeIndex
nsRange = NSRange(range, in: population)
display.addAttribute(.font, value: UIFont.systemFont(ofSize: 30), range: nsRange)
复制代码

接下来,字符串的中间部分转换为Range<String.Index>区间,转换它到NSRange并改变字体大小。显示如下:

在另一种方法中,用NSRange来创建一个Range<String.Index>区间是很容易的。注意,这个构造方法没有成功完成初始化,因此结果是一个可选类型。

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 多行字符串字面量

现在您可以创建多行字符串字面量,使其更容易生成精确的格式化输出或将输入以一种“漂亮的”(如:JSON或HTML格式)方式粘贴到你的代码中。字符串插值(interpolation)在这里仍然有效,您甚至可以对换行进行转义,这样它们就不会包含在输出的文字中了!

字面量语法由""" """分隔开,每行的缩进取决于每一行开始你删除的空格是多少。您可以在字面量语法中使用引号,而不用对它们进行转义。

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.
"""
复制代码

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!
"""
复制代码

Dictionary enhancements 字典的强化

作为一个Swift程序员,字典对你的日常生活有多么重要想必大家都清楚。Swift 4带来了一些改进,使它变得更强大、更有用、更具可操作性。

Sequence-based initializer 基于序列的构造函数

现在可以从键值对的序列中创建字典。例如,您可以创建一个有编号的项目列表。

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"]
复制代码

或者,你已经有了一个Tuple类型的数据, 可以直接使用它去创建字典:

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

Merging

字典中现在包含一个允许您merge两个字典的构造方法。如果您想将一个字典merge到另一个字典中,那么Swift也提供了一个merge方法。两者都需要您对闭包进行操作,以解决由重复键(Key)引起的在merge过程中值(Value)的冲突。

/// - Parameters:
///- keysAndValues: 用于新字典的键值对序列
///- combine:            一个在出现重复键(key)的时候才会被调用的闭包(用于处理和并key之后value的取值问题是应该取那个,如何取的问题),
                                    该闭包返回最终需要的值(可以在闭包内部决定是返回那个value值,冲突前的还是冲突后的)。
 public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value)

复制代码

通常在使用上述方法的时候,闭包会以尾随闭包的形式出现

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协议,且内部元素是Tuple类型的序列,尾随闭包中的current值是在内部迭代过程中首先发现的冲突Value,ignore是后发现的冲突Value,并返回先发现的Value(即curren的值是1,2)

您可以利用闭包来改变您的输入。下面是一个更强大的示例,它使用了一系列键值对,并将它们转换为数组的字典:

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"]]
复制代码

正如您所看到的,您可以做的不仅仅是创建一个字典。如果您想知道每一个字母在字符串中的出现次数,对于这种问题的解决可以说是它的拿手好戏。

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]
复制代码

如果您有一组默认值,并希望将它们与用户设置组合在一起的话,对于这种问题的解决将是 merge 方法的完美用途。

 将别的给定字典合并到此字典中,使用闭包来确定任何重复键(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.为任何重复键接受当前和新值的闭包。闭包返回最终字典所需的值。
 public mutating func merge(_ other: [Dictionary.Key : Dictionary.Value], uniquingKeysWith combine: (Dictionary.Value, Dictionary.Value) throws -> Dictionary.Value) rethrows
复制代码

merge方法中的尾随闭包用法和上面构造方法的尾随闭包用法一致。

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 下标的默认值

对字典的进行取值时,经常会出现可选项的情况,这就要求您采取尽可能简单的代码,可以使用可选绑定、强制解包或optional chaining对其解包。在过去,解决这个问题的一种常见方法是使用空合运算符(? ?)来提供默认值,并使结果是非可选的(non-optional)。

Swift 4增加了在下标中指定默认值的能力:

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.

复制代码

这种特性同时提供了另一种方法来实现您先前看到的频率计数问题。

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

复制代码

Filtering and mapping

在Swift 4中,对字典执行filter操作, 其会在结果中保存字典的结构和类型。

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函数必定返回一个数组结果。通常,在使用字典的时候,这样的返回值会让你很难过! Swift 4添加了mapValues的方法,以允许您保留字典的结构和类型。

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

Grouping

Dictionary最强大的新特性之一是,它可以基于任意谓词对数据进行分区,创建组或存储类似数据的buckets。最简单的例子是对名单的第一个字母进行分组展示。

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"]]
复制代码

你可以指定一个任意的谓词,意味着您能更有创造力。

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]]

复制代码

在一个更实际的例子中,您可能希望基于一个结构体中的值来分组。将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. 定义一个学生的结构体。
  2. 使用之前的数组来创建一个Student数组的值。
  3. 使用新的keyPath语法引用Student中lastName字段。
  4. 用last name的第一个字母来对Student进行分组。

Generic subscripts and associated typeconstraints 泛型下标和关联类型约束

在Swift 3中使用混合数据类型的字典是相当痛苦的,因为在使用它之前需要对每个值进行类型转换。Swift 4现在允许下标返回一个泛型类型。

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?对类型进行强制转换

复制代码

这绝对不是实现结构体的最佳方式。但是,如果您使用的是新的可Decodable工具,这个概念可能会简化您的自定义init(from:)

下标本身也可以是泛型。例如,您可以实现一种使用Sequence协议从集合类型中获取一个数组的方法。

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]
复制代码

您还可以使用该特性向标准库类型中添加新功能以供自己使用。

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"]

复制代码

前面两个例子都利用了另一个新特性:关联类型约束。关联类型现在可以用where子句约束。这极大地简化了使用泛型的工作。

例如,在扩展Grade的时候,Swift 3要求您指定where Keys.Iterator.Element == String

允许对关联类型进行约束可以更容易地扩展标准库。

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调用约定之间进行转换。这些thunks只适用于来自Objective-C语言的调用,而不是Swift。为了减轻现有的Objective-C代码之间的相互协作的负担,Swift 3(以及更早的版本)在许多情况下需要这些thunks代码。

不幸的是,经验表明,许多这样的thunks是不需要的,因为这会导致应用程序二进制文件的体积增大。在很多情况下,应用程序大小的6%-8%是由这种“胶水”代码组成的。为了消除这种浪费,Swift 4做出了改善。

例如,从NSObject派生的类不再被Objective-C自动访问。因为您必须显式地使用@objc关键字来让Swift中的方法对Objective-C可见。

在计算机编程中,一个thunk代表一个创建的子程序,通常它是自动创建的,以帮助调用另一个子程序。Thunks主要用于表示子程序需要执行的额外计算,或者调用不支持一般调用机制的程序。Thunks有许多其他的应用程序来生成编译代码和模块编程。

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

苹果建议你将所有需要对Objective-C可见的方法进行分组并放置在一个Extension内。如果您将@objc关键字添加到Extension本身,那么它所包含的一切都将是可访问的。如果您需要在这个Extension内排除一个方法,不让其被Objective-C可见,使用@nonobjc关键字对其单独标注。

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

上面如果没有关键字@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中被访问到,你需要使用关键字@objcMembers对这个类进行标识.

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

使用关键字@nonobjc对你不想要被Objective-C访问的方法进行标识

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

Swift 4 inference

Swift 4中对@objc的的自动推导,苹果对其规则进行了明确的定义。

如果声明是对使用关键字@objc方法声明的重写,它可以推导出这个重写的方法和@objc关键字标识的方法一样。

class Super {
  @objc func foo() {}
}
class Sub: Super {
  // inferred @objc
  override func foo() {}
}
复制代码

如果一个方法是对被@objc标识的协议进行实现,它可以推导出这个实现的方法和@objc关键字标识的方法一样。

@objc protocol MyDelegate {
func bar() }
class MyThirdClass: MyDelegate {
  // inferred @objc
     func bar() {}
}
复制代码

在这种情况下,这种推导是必需的,因为对MyDelegate.bar()的调用,不管是来自Objective-C还是Swift,都是通过Objective-C的消息发送机制,所以对协议遵守也需要其能被Objective-C所访问到。

基于同样的原因,Swift 4也会对任何声明有一个或多个以下属性进行推导:

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

最后,虽然dynamic关键字是使用objective-C消息机制来实现的,但它不再引起@objc推导。您必须显式地添加注释。

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

Swift 3中dynamic关键字,它用于修饰变量或函数,它的意思与OC不同。它告诉编译器使用动态分发而不是静态分发。OC区别于其他语言的一个特点在于它的动态性,任何方法调用实际上都是消息分发,而Swift则尽可能做到静态分发。

因此,Swift 3中标记为dynamic的变量/方法会隐式的加上@objc关键字,它会使用OC的runtime机制。

Other changes 其他的变化

还有许多其他更小的变化值得注意。

Private access in extensions

Swift 3引入了fileprivate,破坏了大量的代码,但这些代码已被正确地划分为extensions。Swift 4改善了这种状况。现在,声明为private的方法和变量可以在同一个源文件中进行扩展。fileprivate仍然有其“单一文件作用域”的含义,但它应该尽可能不被使用。


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(::)

在为未来做准备时,Swift 4引入了一个新概念,即对内存的独占访问,这样只有一个参与者可以一次写入一个对象所占用的内存。这将中断方法swap(_:_:)的使用。现在,您可以使用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中的整数值是否可以用Swift标量类型(scalar type)来表示。例如:


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

复制代码

常量v将会被赋值为91。这显然是错误的和不安全的。在Swift 4中,常量 v将是nil。引用Swift发展建议,“as?”对于NSNumber,应该意味着“我可以将NSNumber中的数值安全地存储在这里吗吗?” 其同样适用于关键字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之前,没有办法指定一个对象必须是一个特定的类型,并且符合一个协议。现在可以了!

protocol MySpecialDelegateProtocol {}
class MySpecialView: UIView {}
class MyController {
  var delegate: (UIView & MySpecialDelegateProtocol)?
}
复制代码

这中方式也在Swift标准库中以有趣的方式使用

public typealias Codable = Decodable & Encodable

Migrating to Swift 4

将项目迁移到Swift 4,是一个简单的过程。在Xcode 9中,选择Edit\Convert\To Current Swift Syntax... 。有一个例外, migrator将询问您想要什么类型的@objc推导。推荐选择:Minimize Inference

如果你按照建议,选了Minimize Inference, 会出现以下警告:

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 “” target.

一旦您对您的源代码中所有必要的注释感到满意,就打开Target 的 build settings,并将Swift 3的@objc Inference更改为Default,就像在警告消息中指出的那样。

翻译自:Ray Wenderlich 新书 iOS 11 by Tutorials 中的 What's New in Swift 4

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

2017-10-22 20:57:00 weixin_34224941 阅读数 21

相比较Swift 3时的更新,此次Swift 4的变化要小得多,甚至还以新颖和改进的形式重塑了一些旧的功能。更让我们高兴的是,我们不需要把项目立即就更新到Swift 4.0!

集成到Xcode 9中的Swift 4编译器有一个“Swift 3.2”模式。这不是一个独立的编译器,而是一种模式,这种模式允许你继续构建你的Swift 3代码,直到你想更新到Swift 4为止。当然了,你可能会担心CocoaPod是否需要马上update,在这里我可以放心地告诉你,不需要。因为你可以在你的项目中选择你的Swift语言版本,这样你就可以在同一个项目中混合Swift 3和Swift 4的target。话不多说,现在看看到底有哪些更新吧!

One-sided ranges 单边区间

Swift 4引入了RangeExpression协议,这是一种简化如何描述区间的新协议。它被许多其他的协议所采用,以支持创建新的前缀和后缀操作符(prefix and postfix )。因此,您可以忽略一个区间的上界或下界,从而创建一个One-sided range(单边区间)。

当您想要在集合中获取自索引之后的一个值,或到某个索引结束的值时,这个特性将非常有用。

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 无限序列

一个单边区间可以用来创建一个无限序列,一个只指定开始值的序列。例如,您可以构建一个字符与ASCII码表十进制数字相匹配的元组数组。

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 模式匹配

单边区间可以极大地简化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中获得了很多的恩宠,苹果在努力使它们变得更强大,更容易使用。最重要的变化是字符串现在是集合,就像它们在Swift 1中一样。这意味着,一个集合可以做的事情,一个字符串也可以做到!

let text = "Hakuna Matata"
let unicodeText = "✋🐘👌🌹🐩🤝"
text.count
text.isEmpty
"".isEmpty
// 13
// false
// true
// `reversed()` 返回 一个 `ReversedCollection<String>`
// 所以需要显式转换
String(text.reversed()) // "atataM anukaH"

在字符串中遍历每个字符是很容易的,当然它也适用于Unicode字符。

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

字符串下标的类型不是像Int这样的类型,而是一个String.IndexRange<String.Index>这样的类型.。

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

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

//当然你也可以使用单边区间
let lastWord = text[index...]
// lastWord is "Matata"
index = text.index(text.endIndex, offsetBy: -7)
let firstWord = text[..<index]
// firstWord is "Hakuna"

Introducing Substring 字符子串

对字符串进行下标操作时会返回部分视图,它源于原始字符串的buffer,这些操作增加了buffer的引用计数。取决于应用程序及其数据的不同,这有可能在内存中保留大量未使用的字符串。虽然从技术上讲,这并不是一种内存泄漏,但它却不怎么让人喜欢。

Swift标准库通过引入一种新的类型:Substring来解决上面描述的问题,它是由字符串下标操作返回的类型。因为大多数API仍将继续使用String类型参数,所以您必须显式地利用Substring类型来创建一个新的String类型字符串并使用这个新的字符串进行其他操作。而这些操作允许之前原始而又长的字符串自然地离开作用域,而被释放。

其实没有你看起来的那么难理解,因为标准库同时也引入了一个新的协议:StringProtocol。大多数StringAPI都被转移到StringProtocol中,StringSubstring都遵从这个新协议。

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

Unicode magic

Swift 3时,如果您想要访问Character中单个的Unicode数值,您首先必须将Character转换为String类型。Swift 4.0中,Character类型引入了一个unicodeScalars属性。

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

Swift 3中以下表达式将给出错误的答案。而Swift 4可以正确地处理它们,根据所看到的内容给出其长度,而不需要去encode它。

"✋🐘👌".count
//3
"😢".count
//1
"🐵🐶".count
//2

Converting between Range<String.Index> and NSRange (Range<String.Index> 和 NSRange 类型之间的转换)

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

现在Foundation框架在NSRange和Range<String.Index> 中都引进了新的构造方法。这使得两者之间的转换更加容易。

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)])

如预期所想,NSRange的长度与UTF-16视图的长度相匹配。显示是这样的:

2270079-9c5341c03ee031a7.png
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)

上面代码中创建出每个数字emoji的索引,其中为第一部分的字符串创建出一个Range<String.Index>类型的区间,并将其转换为NSRange类型。然后,改变其字体的大小,显示如下:

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

接下来,字符串的中间部分转换为Range<String.Index>区间,转换它到NSRange并改变字体大小。显示如下:

2270079-334802c488e182f9.png
utf-16_003.png

在另一种方法中,用NSRange来创建一个Range<String.Index>区间是很容易的。注意,这个构造方法没有成功完成初始化,因此结果是一个可选类型。

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 多行字符串字面量

现在您可以创建多行字符串字面量,使其更容易生成精确的格式化输出或将输入以一种“漂亮的”(如:JSON或HTML格式)方式粘贴到你的代码中。字符串插值(interpolation)在这里仍然有效,您甚至可以对换行进行转义,这样它们就不会包含在输出的文字中了!

字面量语法由""" """分隔开,每行的缩进取决于每一行开始你删除的空格是多少。您可以在字面量语法中使用引号,而不用对它们进行转义。

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.
"""
2270079-a780c232478e63de.png
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!
"""
2270079-b405dc9b354c9611.png
note.png

Dictionary enhancements 字典的强化

作为一个Swift程序员,字典对你的日常生活有多么重要想必大家都清楚。Swift 4带来了一些改进,使它变得更强大、更有用、更具可操作性。

Sequence-based initializer 基于序列的构造函数

现在可以从键值对的序列中创建字典。例如,您可以创建一个有编号的项目列表。

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"]

或者,你已经有了一个Tuple类型的数据, 可以直接使用它去创建字典:

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

Merging

字典中现在包含一个允许您merge两个字典的构造方法。如果您想将一个字典merge到另一个字典中,那么Swift也提供了一个merge方法。两者都需要您对闭包进行操作,以解决由重复键(Key)引起的在merge过程中值(Value)的冲突。

/// - Parameters:
///- keysAndValues: 用于新字典的键值对序列
///- combine:            一个在出现重复键(key)的时候才会被调用的闭包(用于处理和并key之后value的取值问题是应该取那个,如何取的问题),
                                    该闭包返回最终需要的值(可以在闭包内部决定是返回那个value值,冲突前的还是冲突后的)。
 public init<S>(_ keysAndValues: S, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows where S : Sequence, S.Element == (Key, Value)

通常在使用上述方法的时候,闭包会以尾随闭包的形式出现

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协议,且内部元素是Tuple类型的序列,尾随闭包中的current值是在内部迭代过程中首先发现的冲突Value,ignore是后发现的冲突Value,并返回先发现的Value(即curren的值是1,2)

您可以利用闭包来改变您的输入。下面是一个更强大的示例,它使用了一系列键值对,并将它们转换为数组的字典:

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"]]

正如您所看到的,您可以做的不仅仅是创建一个字典。如果您想知道每一个字母在字符串中的出现次数,对于这种问题的解决可以说是它的拿手好戏。

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]

如果您有一组默认值,并希望将它们与用户设置组合在一起的话,对于这种问题的解决将是 merge 方法的完美用途。

 将别的给定字典合并到此字典中,使用闭包来确定任何重复键(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.为任何重复键接受当前和新值的闭包。闭包返回最终字典所需的值。
 public mutating func merge(_ other: [Dictionary.Key : Dictionary.Value], uniquingKeysWith combine: (Dictionary.Value, Dictionary.Value) throws -> Dictionary.Value) rethrows

merge方法中的尾随闭包用法和上面构造方法的尾随闭包用法一致。

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 下标的默认值

对字典的进行取值时,经常会出现可选项的情况,这就要求您采取尽可能简单的代码,可以使用可选绑定、强制解包或optional chaining对其解包。在过去,解决这个问题的一种常见方法是使用空合运算符(? ?)来提供默认值,并使结果是非可选的(non-optional)。

Swift 4增加了在下标中指定默认值的能力:

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.

这种特性同时提供了另一种方法来实现您先前看到的频率计数问题。

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

Filtering and mapping

在Swift 4中,对字典执行filter操作, 其会在结果中保存字典的结构和类型。

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函数必定返回一个数组结果。通常,在使用字典的时候,这样的返回值会让你很难过! Swift 4添加了mapValues的方法,以允许您保留字典的结构和类型。

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

Grouping

Dictionary最强大的新特性之一是,它可以基于任意谓词对数据进行分区,创建组或存储类似数据的buckets。最简单的例子是对名单的第一个字母进行分组展示。

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"]]

你可以指定一个任意的谓词,意味着您能更有创造力。

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]]

在一个更实际的例子中,您可能希望基于一个结构体中的值来分组。将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. 定义一个学生的结构体。
  2. 使用之前的数组来创建一个Student数组的值。
  3. 使用新的keyPath语法引用Student中lastName字段。
  4. 用last name的第一个字母来对Student进行分组。

Generic subscripts and associated typeconstraints 泛型下标和关联类型约束

在Swift 3中使用混合数据类型的字典是相当痛苦的,因为在使用它之前需要对每个值进行类型转换。Swift 4现在允许下标返回一个泛型类型。

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?对类型进行强制转换

这绝对不是实现结构体的最佳方式。但是,如果您使用的是新的可Decodable工具,这个概念可能会简化您的自定义init(from:)

下标本身也可以是泛型。例如,您可以实现一种使用Sequence协议从集合类型中获取一个数组的方法。

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]

您还可以使用该特性向标准库类型中添加新功能以供自己使用。

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"]

前面两个例子都利用了另一个新特性:关联类型约束。关联类型现在可以用where子句约束。这极大地简化了使用泛型的工作。

例如,在扩展Grade的时候,Swift 3要求您指定where Keys.Iterator.Element == String

允许对关联类型进行约束可以更容易地扩展标准库。

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调用约定之间进行转换。这些thunks只适用于来自Objective-C语言的调用,而不是Swift。为了减轻现有的Objective-C代码之间的相互协作的负担,Swift 3(以及更早的版本)在许多情况下需要这些thunks代码。

不幸的是,经验表明,许多这样的thunks是不需要的,因为这会导致应用程序二进制文件的体积增大。在很多情况下,应用程序大小的6%-8%是由这种“胶水”代码组成的。为了消除这种浪费,Swift 4做出了改善。

例如,从NSObject派生的类不再被Objective-C自动访问。因为您必须显式地使用@objc关键字来让Swift中的方法对Objective-C可见。

在计算机编程中,一个thunk代表一个创建的子程序,通常它是自动创建的,以帮助调用另一个子程序。Thunks主要用于表示子程序需要执行的额外计算,或者调用不支持一般调用机制的程序。Thunks有许多其他的应用程序来生成编译代码和模块编程。

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

苹果建议你将所有需要对Objective-C可见的方法进行分组并放置在一个Extension内。如果您将@objc关键字添加到Extension本身,那么它所包含的一切都将是可访问的。如果您需要在这个Extension内排除一个方法,不让其被Objective-C可见,使用@nonobjc关键字对其单独标注。

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

上面如果没有关键字@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中被访问到,你需要使用关键字@objcMembers对这个类进行标识.

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

使用关键字@nonobjc对你不想要被Objective-C访问的方法进行标识

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

Swift 4 inference

Swift 4中对@objc的的自动推导,苹果对其规则进行了明确的定义。

如果声明是对使用关键字@objc方法声明的重写,它可以推导出这个重写的方法和@objc关键字标识的方法一样。

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

如果一个方法是对被@objc标识的协议进行实现,它可以推导出这个实现的方法和@objc关键字标识的方法一样。

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

在这种情况下,这种推导是必需的,因为对MyDelegate.bar()的调用,不管是来自Objective-C还是Swift,都是通过Objective-C的消息发送机制,所以对协议遵守也需要其能被Objective-C所访问到。

基于同样的原因,Swift 4也会对任何声明有一个或多个以下属性进行推导:

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

最后,虽然dynamic关键字是使用objective-C消息机制来实现的,但它不再引起@objc推导。您必须显式地添加注释。

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

Swift 3中dynamic关键字,它用于修饰变量或函数,它的意思与OC不同。它告诉编译器使用动态分发而不是静态分发。OC区别于其他语言的一个特点在于它的动态性,任何方法调用实际上都是消息分发,而Swift则尽可能做到静态分发。

因此,Swift 3中标记为dynamic的变量/方法会隐式的加上@objc关键字,它会使用OC的runtime机制。

Other changes 其他的变化

还有许多其他更小的变化值得注意。

Private access in extensions

Swift 3引入了fileprivate,破坏了大量的代码,但这些代码已被正确地划分为extensions。Swift 4改善了这种状况。现在,声明为private的方法和变量可以在同一个源文件中进行扩展。fileprivate仍然有其“单一文件作用域”的含义,但它应该尽可能不被使用。


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(::)

在为未来做准备时,Swift 4引入了一个新概念,即对内存的独占访问,这样只有一个参与者可以一次写入一个对象所占用的内存。这将中断方法swap(_:_:)的使用。现在,您可以使用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中的整数值是否可以用Swift标量类型(scalar type)来表示。例如:


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

常量v将会被赋值为91。这显然是错误的和不安全的。在Swift 4中,常量 v将是nil。引用Swift发展建议,“as?”对于NSNumber,应该意味着“我可以将NSNumber中的数值安全地存储在这里吗吗?” 其同样适用于关键字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之前,没有办法指定一个对象必须是一个特定的类型,并且符合一个协议。现在可以了!

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

这中方式也在Swift标准库中以有趣的方式使用

public typealias Codable = Decodable & Encodable

Migrating to Swift 4

将项目迁移到Swift 4,是一个简单的过程。在Xcode 9中,选择Edit\Convert\To Current Swift Syntax... 。有一个例外, migrator将询问您想要什么类型的@objc推导。推荐选择:Minimize Inference

2270079-3b3374d7712dacf7.png
Migrating_to_Swift_4_001.png

如果你按照建议,选了Minimize Inference, 会出现以下警告:

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,就像在警告消息中指出的那样。

2270079-aa246b796be44f63.png
Migrating_to_Swift_4_002.png

翻译自:Ray Wenderlich 新书 iOS 11 by Tutorials 中的 What's New in Swift 4

2018-05-14 16:23:25 FlyingFireFish 阅读数 355

swift 4 实际使用:

在Appledelegate中的didfinishLaunchingWithOptions 方法中是是实现:

    

        let images = ["launch_a", "launch_b", "launch_c", "launch_d"]

        LaunchIntroductionView.shared(withImages: images, buttonImage: "launch_d", buttonFrame: UIScreen.main.bounds)//CGRect.init(x: screenWidth/2 - 551/4, y: screenHeight - 150, width: 551/2, height: 45)

        

        



基于oc的引导页具体实现解析:

.h文件源码

#import <UIKit/UIKit.h>


#define kScreen_height  [[UIScreen mainScreen] bounds].size.height

#define kScreen_width   [[UIScreen mainScreen] bounds].size.width


@interface LaunchIntroductionView : UIView

/**

 *  选中page的指示器颜色,默认白色

 */

@property (nonatomic, strong) UIColor *currentColor;//

/**

 *  其他状态下的指示器的颜色,默认

 */

@property (nonatomic, strong) UIColor *nomalColor;

/**

 *  不带按钮的引导页,滑动到最后一页,再向右滑直接隐藏引导页

 *

 *  @param imageNames 背景图片数组

 *

 *  @return   LaunchIntroductionView对象

 */

+(instancetype)sharedWithImages:(NSArray *) imageNames;

/**

 *  带按钮的引导页

 *

 *  @param imageNames      背景图片数组

 *  @param buttonImageName 按钮的图片

 *  @param frame           按钮的frame

 *

 *  @return LaunchIntroductionView对象

 */

+(instancetype)sharedWithImages:(NSArray *) imageNames buttonImage:(NSString *) buttonImageName buttonFrame:(CGRect ) frame;


/**

 用storyboard创建的project调用此方法


 @param storyboardName storyboardName

 @param imageNames 图片名字数组

 @return LaunchIntroductionView对象

 */

+ (instancetype)sharedWithStoryboardName:(NSString *)storyboardName images:(NSArray *)imageNames;


/**

 用storyboard创建的project调用此方法


 @param storyboardName storyboardName

 @param imageNames 图片名字数组

 @param buttonImageName 按钮图片名字

 @param frame 按钮的frame

 @return LaunchIntroductionView对象

 */

+(instancetype)sharedWithStoryboard:(NSString *)storyboardName images:(NSArray *) imageNames buttonImage:(NSString *) buttonImageName buttonFrame:(CGRect ) frame;



@end


.m文件源码:

#import "LaunchIntroductionView.h"


static NSString *const kAppVersion = @"appVersion";


@interface LaunchIntroductionView ()<UIScrollViewDelegate>

{

    UIScrollView  *launchScrollView;

    UIPageControl *page;

}


@end


@implementation LaunchIntroductionView

NSArray *images;

BOOL isScrollOut;//在最后一页再次滑动是否隐藏引导页

CGRect enterBtnFrame;

NSString *enterBtnImage;

static LaunchIntroductionView *launch = nil;

NSString *storyboard;


#pragma mark - 创建对象-->>不带button

+(instancetype)sharedWithImages:(NSArray *)imageNames{

    images = imageNames;

    isScrollOut = YES;

    launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];

    launch.backgroundColor = [UIColor whiteColor];

    return launch;

}


#pragma mark - 创建对象-->>带button

+(instancetype)sharedWithImages:(NSArray *)imageNames buttonImage:(NSString *)buttonImageName buttonFrame:(CGRect)frame{

    images = imageNames;

    isScrollOut = NO;

    enterBtnFrame = frame;

    enterBtnImage = buttonImageName;

    launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];

    launch.backgroundColor = [UIColor whiteColor];

    return launch;

}

#pragma mark - 用storyboard创建的项目时调用,不带button

+ (instancetype)sharedWithStoryboardName:(NSString *)storyboardName images:(NSArray *)imageNames {

    images = imageNames;

    storyboard = storyboardName;

    isScrollOut = YES;

    launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];

    launch.backgroundColor = [UIColor whiteColor];

    return launch;

}

#pragma mark - 用storyboard创建的项目时调用,带button

+ (instancetype)sharedWithStoryboard:(NSString *)storyboardName images:(NSArray *)imageNames buttonImage:(NSString *)buttonImageName buttonFrame:(CGRect)frame{

    images = imageNames;

    isScrollOut = NO;

    enterBtnFrame = frame;

    storyboard = storyboardName;

    enterBtnImage = buttonImageName;

    launch = [[LaunchIntroductionView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];

    launch.backgroundColor = [UIColor whiteColor];

    return launch;

}

#pragma mark - 初始化

- (instancetype)initWithFrame:(CGRect)frame

{

    self = [super initWithFrame:frame];

    if (self) {

        [self addObserver:self forKeyPath:@"currentColor" options:NSKeyValueObservingOptionNew context:nil];

        [self addObserver:self forKeyPath:@"nomalColor" options:NSKeyValueObservingOptionNew context:nil];

        if ([self isFirstLauch]) {

            UIStoryboard *story;

            if (storyboard) {

                story = [UIStoryboard storyboardWithName:storyboard bundle:nil];

            }

            UIWindow *window = [UIApplication sharedApplication].windows.lastObject;

            if (story) {

                UIViewController * vc = story.instantiateInitialViewController;

                window.rootViewController = vc;

                [vc.view addSubview:self];

            }else {

                [window addSubview:self];

            }

            [self addImages];

        }else{

            [self removeFromSuperview];

        }

    }

    return self;

}

#pragma mark - 判断是不是首次登录或者版本更新

-(BOOL )isFirstLauch{

    //获取当前版本号

    NSDictionary *infoDic = [[NSBundle mainBundle] infoDictionary];

    NSString *currentAppVersion = infoDic[@"CFBundleShortVersionString"];

    //获取上次启动应用保存的appVersion

    NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:kAppVersion];

    //版本升级或首次登录

    if (version == nil || ![version isEqualToString:currentAppVersion]) {

        [[NSUserDefaults standardUserDefaults] setObject:currentAppVersion forKey:kAppVersion];

        [[NSUserDefaults standardUserDefaults] synchronize];

        return YES;

    }else{

        return NO;

    }

}

#pragma mark - 添加引导页图片

-(void)addImages{

    [self createScrollView];

}

#pragma mark - 创建滚动视图

-(void)createScrollView{

    launchScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, kScreen_width, kScreen_height)];

    launchScrollView.showsHorizontalScrollIndicator = NO;

    launchScrollView.bounces = NO;

    launchScrollView.pagingEnabled = YES;

    launchScrollView.delegate = self;

    launchScrollView.contentSize = CGSizeMake(kScreen_width * images.count, kScreen_height);

    [self addSubview:launchScrollView];

    for (int i = 0; i < images.count; i ++) {

        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(i * kScreen_width, 0, kScreen_width, kScreen_height)];

        imageView.image = [UIImage imageNamed:images[i]];

        imageView.contentMode = UIViewContentModeScaleAspectFit;

        [launchScrollView addSubview:imageView];

        if (i == images.count - 1) {

            //判断要不要添加button

            if (!isScrollOut) {

                UIButton *enterButton = [[UIButton alloc] initWithFrame:CGRectMake(enterBtnFrame.origin.x, enterBtnFrame.origin.y, enterBtnFrame.size.width, enterBtnFrame.size.height)];

                enterButton.imageView.contentMode = UIViewContentModeScaleAspectFit;

                [enterButton setImage:[UIImage imageNamed:enterBtnImage] forState:UIControlStateNormal];

                [enterButton addTarget:self action:@selector(enterBtnClick) forControlEvents:UIControlEventTouchUpInside];

                [imageView addSubview:enterButton];

                imageView.userInteractionEnabled = YES;

            }

        }

    }

    

    page = [[UIPageControl alloc] initWithFrame:CGRectMake(0, kScreen_height*0.9, kScreen_width, 30)];

    page.numberOfPages = images.count;

    page.backgroundColor = [UIColor clearColor];//7.13原来颜色

    page.currentPageIndicatorTintColor = [UIColor redColor];//7.13tianjia

    page.pageIndicatorTintColor = [UIColor grayColor];//7.13tianjia

    page.currentPage = 0;

    page.defersCurrentPageDisplay = YES;

    [self addSubview:page];

}

#pragma mark - 进入按钮

-(void)enterBtnClick{

    [self hideGuidView];

}

#pragma mark - 隐藏引导页

-(void)hideGuidView{

    [UIView animateWithDuration:0.5 animations:^{

        self.alpha = 0;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            [self removeFromSuperview];

        });

        

    }];

}

#pragma mark - scrollView Delegate

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{

    int cuttentIndex = (int)(scrollView.contentOffset.x + kScreen_width/2)/kScreen_width;

    if (cuttentIndex == images.count - 1) {

        if ([self isScrolltoLeft:scrollView]) {

            if (!isScrollOut) {

                return ;

            }

            [self hideGuidView];

        }

    }

}

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{

    if (scrollView == launchScrollView) {

        int cuttentIndex = (int)(scrollView.contentOffset.x + kScreen_width/2)/kScreen_width;

        page.currentPage = cuttentIndex;

        

       

    }

}

#pragma mark - 判断滚动方向

-(BOOL )isScrolltoLeft:(UIScrollView *) scrollView{

    //返回YES为向左反动,NO为右滚动

    if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x < 0) {

        return YES;

    }else{

        return NO;

    }

}

#pragma mark - KVO监测值的变化

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{

    if ([keyPath isEqualToString:@"currentColor"]) {

        page.currentPageIndicatorTintColor = self.currentColor;

    }

    if ([keyPath isEqualToString:@"nomalColor"]) {

        page.pageIndicatorTintColor = self.nomalColor;

    }

}



@end




2015-09-01 15:51:03 xiaojuliet 阅读数 314

经过前面章节的学习,现在可以制作你的第一个iPhone App了!

我们将开发一个名为“Tap Me ”的游戏。主要功能就是看你在30s内能按多少次按钮。

Getting Started

打开 Xcode 选择 Create a new Xcode project.


选择iOS > Application > Single View Application ,点击 Next.



Storyboards

工程创建后,你会发现比之前的命令行工程多出了一些文件。首先我们看下 Main.storyboard.


点击这个文件,会打开一个GUI的界面,模拟运行app的iphone屏幕。



Note: 你可能觉得奇怪为啥这个屏幕像个方形,跟iPhone屏幕的大小有区别。因为,我们在ios开发中,需要设计app以适配不同大小的屏幕尺寸---这个功能叫布局适配。

基本上,你采用“ 放在远离顶端的位置”或是“放在中间”的方式来布局你的用户接口元素,那这样你的app就能适应不同的屏幕尺寸,从iPhone5到iPhone6 plus等等都可以。

现在的app显示的是白板,为了做一个修改,需要确认Utilities Menu 打开了,如下:


选择  Attributes Inspector. 可以查看编辑 View Controller视图控制器的属性. 


尝试修改背景颜色为绿色:

.

在最上面的菜单中选择 iPhone 6 Simulator ,然后运行你的app看下效果..


Adding Labels to the Screen

现在屏幕上什么都没有,我们需要添加Utilities Menu菜单的对象库Object Library中的一些对象。首先我们添加一个标签到屏幕上。标签类似于文本。在对象库Object Library中找到label对象,将其拖到屏幕中,放到顶端的中间。




在 Attributes Inspector 中可以修改label的标题为 “Time: 30”.


这时标签的标题名称没有完全显示出来.点击该标签并选择  Editor\Size to Fit(Command =).再次运行app,就能看下下面的效果。


但是标签并没有显示在屏幕的中间,因为不是所有的ios设备都有相同的分辨率,所以我们需要给这个标签加一些限制,让它能显示在屏幕的中间。点击标签后选择屏幕最下面的对齐按钮(Align button),勾上Horizontal Center in Container ,然后点击Add 1 Constraint.


上面确定了标签的水平位置,接下来要确定其垂直位置。点击屏幕最下方的Pin button设置,然后点击 Add 1 Constraint:


按上面的方法再创建一个记录分数的标签。其字体的大小设置为40,行数为2,高度为130.并添加相应的限制,让其保持在屏幕下方的中间位置。


设置完成后重新运行app,效果如下:


Add a Button

Object Library 中找到button 对象,添加一个按钮到屏幕的正中间。


添加相应的限制:


在Attributes inspector 中修改标题为 “Tap Me!” 并设置背景色为白色.


在 Pin 中修改Width 和 Height.


最后,点击屏幕下方的第三个按钮,选择Update Frames重新适配对按钮所设置的限制


重新运行app


Linking Views to Code 链接views与代码

目前为止,已经完成了views的布局,现在需要将其与代码进行链接。

ios app的每个屏幕都是有一个叫 View Controller.的类控制的。xcode已经创建了一个模板 – ViewController.swift.

在文件ViewController.swift:的类ViewController 中添加两个属性:

@IBOutlet var scoreLabel: UILabel!
@IBOutlet var timerLabel: UILabel!

加上 @IBOutlet ,storyboard 编辑器才能识别这两个UILable类型的属性变量。

同样的,在ViewController 类中定义一个方法,当按下按钮时应该执行的动作:

@IBAction func buttonPressed()  {
  NSLog("Button Pressed")
}

接下来,将上面定义的属性和方法同相应的views链接起来。打开Main.storyboard, 展开文件目录。按住ctrl键的同时将 View Controller 拖拽到time标签上。


这时会出现一个小对话框,选择 timerLabel. 这个就是之前在 ViewController.swift 文件中创建的 UILabel 对象。


同样的方法,将 View Controller 拖拽到score 标签上,选择 scoreLabel .

 button按钮和方法 buttonPressed(),的链接方式正好相反。按住Ctrl的同时,点击button并将其拖拽到View Controller 上。



重新运行app,点击button按钮几次,在屏幕的下面的终端上会看到一些信息,每点击一个button就会显示 NSLog 语句中的内容。


Manipulating Labels控制labels

button的目的并非只是在终端输出一段字符,而是每按一次button,core label的内容就加1.那么,你首先必须学会怎么操作label上的文本内容。

用下面的语句代替方法 buttonPressed() 中的内容.

scoreLabel.text = "Pressed"

此时,你运行app后按一下button,屏幕下方的文本内容就会变为“Pressed”.

Keep Track of the Score

首先,在类里创建一个用来记录分数的变量创建一个变量count 。将下面的语句加到以@IBOutlet  开头的两句声明语句的下面。

var count = 0

在方法 buttonPressed()函数中,对变量进行++操作:

count++

或者用下面的代替也可以:

count = count + 1

修改scoreLabel 的文本内容:

scoreLabel.text = "Score \n\(count)"

运行app,再测试下,你会发现按一下按钮button,score label的内容就会加1.

Working with the Timer

接下来,需要给app添加一个计时器。首先需要创建两个变量,一个是记录时间的int类型的

seconds变量,一个是递减时间的NSTimer 类的变量。
将下面两条语句加到声明count变量的下面:

var seconds = 0
var timer = NSTimer()

为了使用这两个变量,还需要创建两个方法。一个是setupGame() 。将下面的代码加到类体内的最下面:

func setupGame()  {
  seconds = 30
  count = 0
 
  timerLabel.text = "Time: \(seconds)"
  scoreLabel.text = "Score: \(count)"
}

上面那段代码是在游戏开始的时候将变量的内容都恢复为初始值。如果不加这段代码,那当seconds递减到0,下一次游戏开始score还是会在原来的基础上继续加1,始终不会重置。

setupGame()中加入下面的代码,开启计时器:

timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("subtractTime"), userInfo: nil, repeats: true)

viewDidLoad() 方法中调用setupGame() 。将下面的代码加到viewDidLoad() 函数体中:

setupGame()

另一个方法叫 subtractTime(). 作用是递减seconds变量,更新timeLabel的文本内容 ,当计时器减到0时给出一个告警。

func subtractTime() {
  seconds--
  timerLabel.text = "Time: \(seconds)"
 
  if(seconds == 0)  {
 
  }
}

在上面的if语句中加入下面的代码后,当计时器递减到0时,计时器就会停止工作。

timer.invalidate()

运行app后,你会发现时间time的值在递减:



Red Alert!

添加一些告警信息,如下:


首先,创建一个告警控制器UIAlertController,在前面if语句中的

timer.invalidate()后面加入下面的代码:

let alert = UIAlertController(title: "Time is up!",
 message: "You scored \(count) points",
 preferredStyle: UIAlertControllerStyle.Alert)

然后,调整button的动作以及显示的内容。在 alert.声明语句的下面添加下面的代码:

alert.addAction(UIAlertAction(title: "Play Again", style: UIAlertActionStyle.Default, handler: {
  action in self.setupGame()
}))

从上面的代码中,你会发现title 决定了button的内容, handler 决定了button被按下后会去调用 setupGame() 方法,然后告警就消失了.

在 alert.addAction(...).下面加上下面的代码,执行告警控制器UIAlertController

presentViewController(alert, animated: true, completion:nil)

运行app,并测试:




2017-09-30 18:26:00 weixin_33716154 阅读数 17

1. One-sided ranges 单边range

可以省略范围range的上限或下限

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 sequences 无限序列

一个单边range可以用来创建一个无限序列,只定义序列的开始即可

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 条件匹配

一个单边range可以大大简化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!"

switch表达式不必为Int类型

/// Produce an emoji based on a numeric value
/// - parameter rating: a value between 0 and 1
func sentiment(_ rating: Double) -> String {
  switch rating {
  case ..<0.33:
     return "😞"
  case ..<0.66:
     return "😐"
  default:
     return "😁"
sentiment(0.5)
// returns "😐"

2. Strings 字符串

String再次成为了集合类型

let text = "Hakuna Matata"
let unicodeText = "👇🏿👏🏻🤝🇦🇹🇧🇿🇧🇹🇫🇯🇧🇷"

text.count      // 13
text.isEmpty    // false
"".isEmpty      // true

// `reversed()` returns a `ReversedCollection<String>` so
// it must be converted back to `String`
String(text.reversed()) 
// "atataM anukaH"

很容易遍历字符串中的每个字符,同样适用于Unicode字符

for c in unicodeText {
  print(c) // prints each of 8 characters, one to a line
}

字符串下标不能为原始类型如Int,相反,它们必须是一个String.Index或Range<String.Index>

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

// You can use prefix(upTo:) and suffix(from:) 
// but why not use one-sided ranges instead

let lastWord = text[index...]
// lastWord is "Matata"

index = text.index(text.endIndex, offsetBy: -7)
let firstWord = text[..<index]
// firstWord is "Hakuna"
  • Introducing Substring 引入Substring类型

Swift试图高效率管理与String相关的buffer,因此,String对象的下标操作返回的是原始字符串buffer的一部分,增加的是buffer的引用计数。这会导致String未被使用到的那一大部分还驻留在内存中。虽然在技术上这不是内存泄漏,但看起来就像是内存泄漏。
Swift标准库通过String的下标操作,返回了一个新类型: Substring,从而解决了这个问题。大部分API还将继续使用String类型作为参数,你得把Substring类型显式转换成String类型使用才行。这让那个较大的原始字符串很自然的出了作用域,从而得到释放。
标准库还引入了一个新的协议:StringProtocol。大部分String的API都迁移到了StringProtocol。String和Substring都遵从了这个新协议。

type(of: lastWord)
// Substring.Type

lastWord.uppercased()
// "MATATA"

let lastWordAsString = String(lastWord)
type(of: lastWordAsString)
// String.Type

注意:在Xcode中选择点击一个变量名查看它的类型,将会看到返回的是String.Subsequence。这只是Substring类型的简单关联。type(of: lastWord) 就是如此,实际上返回的是Substring.Type。

  • Unicode魔法

在Swift3中,如果你想访问Character中单独的Unicode值,首先你得把它转成String。而现在,Character有了个unicodeScalars属性。

let c: Character = "🇨🇭"
Array(c.unicodeScalars)
// [127464, 127469]

Swift3不喜欢下列任何一个表达式,在每种情况下都给出了错误的答案。而Swift4可以正确的处理它们,给出你所看到的预期的长度,而不是编码所需的长度。

"🇧🇿🇫🇯🇧🇷".count    // 3
"👇🏿".count        // 1
"👏🏻".count        // 1
"🇦🇹".count        // 1

如前所述,遍历字符串会正确地显示每个Unicode字符。

  • Range<String.Index> 和 NSRange 转换

Swift字符串的范围用Range<String.Index>来表示。而许多Foundation的API (例如 NSRegularExpression, NSAttributedString, NSLinguisticTagger) 则需要用到NSRange
Foundation现在包含了新的构造方法,使NSRangeRange<String.Index>可以互相转换。再也不需要用UTF-16的offset和view把事情弄的一团糟了。

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)])

正如预期那样,NSRange的长度匹配UTF-16视图的长度,display会这样显示:

4224061-f6d5d75af18cca7f.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)

上面代码查找每个数字图标的索引,为字符串的第一部分创建range(Range<String.Index>),再转成NSRange,然后采用了新的字体属性,现在display看起来是这样的:

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

接下来,代码生成了字符串中间那部分的range,转换成NSRange并采用了另一种新的字体属性,display现在看起来是这个样子:

4224061-050d3b7423324080.png

把NSRange转成Range<String.Index>也很容易。
注意,这是个可以失败的构造方法,所以返回结果是可选的。

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"

注意,这些新的构造方法也使得像UITextFieldDelegate的textField(_:shouldChangeCharactersIn:replacementString:)这样的东西容易写了,没有转成NSString那么丑了,这在iOS11代码中是如此常见。

  • 多行的字符串字面量

现在你可以创建多行的字符串文本了,很容易生成精确格式化的输出,或者直接在你的代码里粘贴漂亮的输入(例如JSON,HTML)。字符串插值依然可以,甚至你可以摆脱换行符\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.
  """

print(firstVerse)的结果是这样的:

4224061-39441af9f6c126af.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!
  """

print(details)会是这样:

4224061-9cbda5cd1f1628b0.png

3. Dictionary 增强

[SE-0165]
作为一个Swift开发者,你知道Dictionary在你的日常生活中有多重要。Swift 4带来了一系列的改善,使它更强大,更有用,更可用。

  • Sequence-based initializer 基于序列的构造器

现在你可以通过键值对的顺序排列来创建字典。例如,你可以创建个项目的编号列表:

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"]

或者,你已经有了一波元组:

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

现在Dictionary有了个可以让你合并两个字典为一个字典的构造器。如果你想合并一个字典到另一个里面,Swift也提供了一个合并的方法。它们都允许你指定一个闭包来解决因重复的Key导致的合并冲突。

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

你可以用闭包来转换输入。这儿有个更屌的example,它拿到一个键值对数组,将它转换成一个包含数组的字典:

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
    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"]]

如你所见,你可以做的不仅是创建个字典。假设你想知道每一个字母出现在一个字符串中的出现频率是多少?那么字典合并能够帮你!

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]

如果您有一组默认值,并希望将它们与用户的设置合并,对于merge方法来说是个绝佳的使用。

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 下标默认值

字典的值是作为可选类型返回的。虽然这是必要的,但它要求你采取尽可能简单的代码,并使用可选绑定、强制解包或可选链来解包。在过去,解决这个问题的一种常见方法是使用nil合并运算符(??)来提供默认值,使结果是非可选的。
这是个非常常见的做法,而Swift 4增加了在下标中指定默认值的能力。

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.

默认下标提供了另一种方法来实现您先前看到的频率计数器。

frequencies.removeAll()
spell.filter { $0 != " " }.map { 
  frequencies[$0, default: 0] += 1
}
// produces the same results as before
  • Filtering and mapping 过滤和映射

在Swift 4中,过滤字典的返回结果会保持它的结构和类型。

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

这也适用于集合:

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

map函数总是返回一个数组。通常在使用字典的时候你不希望这样! Swift 4添加了mapValue,以允许你保留字典的结构和类型。

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

Dictionary最强大的新特性之一是,它可以基于任意谓词对数据进行分区,创建存储类似数据的组。最简单的例子是分组名单的第一个字母。

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"]]

你可以指定一个任意的谓词,这样你就能更有创造力。

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]]

在一个更实际的例子中,您可能希望根据一个结构中隐藏的值来组成您的组。将分组与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]), 
                 lastName: String($0[1])) }
// 3
let lastNameKeypath = \Student.lastName
// 4
let contactList = Dictionary(grouping: classRoll) {
  $0[keyPath: lastNameKeypath].prefix(1)
}

上面的代码做了如下事情:

  1. 定义一个Student结构体
  2. 使用之前的student列表创建Student值的数组
  3. 使用新的keyPath语法,引用StudentlastName字段
  4. lastName的第一个字母,给Student分组
  • Generic subscripts and associated type constraints

[SE-0142, SE-0148]
在Swift 3中使用混合数据类型字典是相当痛苦的,因为在使用它之前需要对每个值进行类型转换。Swift 4允许下标返回泛型类型。

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?"

这绝不是实现结构体的最佳方法。然而,如果你正在使用新的Decodable,将会简化你自定义的init(from:)方法。

APP启动广告页 (Swift 4)

阅读数 369

APP每次启动都会展示广告启动页:LSLaunchAD.show(with:self.window,countTime:5,showCountTimeOfButton:true,showSkipButton:true,isFullScreenAD:true,localAdImgName:nil,imageURL:"http://yun.kchuangqi.com/u...

博文 来自: FlyingFireFish

判断用户当天是否首次进入APP (Swift 4)

阅读数 498

记得曾碰到个特殊需求,用户当天首次进入APP时,才会去展示某些内容,对此特做封装:     //MARK:判断是不是当天首次进入APP  funcisFirstIntoAppToday()-&gt;Bool{    letdateFormatter=DateFormatter()    dateFormatter.dateFormat="YYYY-MM...

博文 来自: FlyingFireFish

判断是不是首次进入APP或者版本更新后首次进入APP (Swift 4)

阅读数 332

APP引导页常需要判断出现的时间,一般是首次进入APP才会出现,对此特写了个方法,判断是否是首次进入APP     //MARK:判断是不是首次进入APP或者版本更新后首次进入APP  funcisFirstOrUpdateFirstLaunch()-&gt;Bool{    //获取版本号    letapp_version=Bundle.main.in...

博文 来自: FlyingFireFish

Swift 4 泛型:如何在你的代码或App里应用泛型

阅读数 766

原文链接问题1:我能否写一个Swift函数用于查找在任意数组中存储的任意类型的任何实例对象的位置\索引。问题2:我能否写一个Swift函数用于确定在任意数组中存储的任意类型的任何实例对象的类型。我所说的“任何类型”,包括自定义类型,比如我们自己定义的Class类型。提示:我知道我能够用SwiftArray类型的内置方法,如index和contains,但今...

博文 来自: Philm_iOS

Swift 4 泛型:如何在你的代码或App里应用泛型

阅读数 4

原文链接:swift.gg/2018/08/28/…作者:AndrewJaffee译者:BigLuo校对:numbbbbb,muhlenXi定稿:CMB问题1:我能否写一个Swift函数用于查找在任意数组中存储的任意类型的任何实例对象的位置\索引。问题2:我能否写一个Swift函数用于确定在任意数组中存储的任意类型的任何实例对象的类型。我所说的"任何类型",包括...

博文 来自: weixin_34240520
没有更多推荐了,返回首页