码迷,mamicode.com
首页 > 移动开发 > 详细

【iOS】A Swift Tour

时间:2021-05-24 05:47:33      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:sid   swift   float   row   ota   width   lis   ams   参数   

一个最简单的完整的 Swift 程序如下所示:

print("Hello, world!")
  • 类似于 Python 等脚本语言,写在全局的代码作为程序的入口,不需要定义 main 函数
  • 使用 print 等输入输出函数时,不需要像 C/C++ 一样包含相关的头文件或库
  • 语句结尾不需要加分号

Simple Values

Swift 使用 let 声明常量,使用 var 声明变量:

let myConstant = 21;
var myVariable = 30;
myVariable = 40;

常量的值不需要在编译时已知,可以在运行时才确定,但需要确保只能赋值一次,不能多次赋值。

Swift 是强类型语言,每个常量和变量在定义时都必须有一个固定的类型,后续也不能再改变。当提供了初始值时,编译器可以根据初始值推断类型,比如上面的 myConstantmyVariable 都是 Int 类型。

也可以在定义时指定数据类型:

let explicitDouble: Double = 70

尽管初始值 70Int 类型,但显式指定了数据类型为 Double

Swift 没有隐式类型转换,必须进行显式转换:

let label = "The width is "
let width = 94
let widthLabel = label + String(width)

由于上面的语法在拼接字符串时很麻烦,Swift 也支持 \() 语法来拼接字符串:

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

类似于 Python 中的 f-string。

Swift 使用 3 个双引号 """ 来表示多行字符串:

let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""

开头和结尾的空白符会被去掉。Python 也有相同的用法。

Swift 支持基本的数据结构:数组 Arrays 和字典 Dictionaries

var shoppingList = ["catfish", "water", "tulips"]
shoppingList[1] = "bottle of water"

var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

数组和字典都使用方括号 [],并使用方括号语法来访问列表中的元素和字典中的值。

Python 中的数组叫做列表 list,使用方括号 [];字典 dict 使用花括号 {}

向数组中添加元素用 append 方法:

shoppingList.append("blue paint")

声明一个空数组或空字典时,需要指定数据类型,再次印证 Swift 是强类型语言:

let emptyArray = [String]()
let emptyDictionary = [String: Float]()

如果类型已知,比如给已有数组赋新值、给函数传入数组实参,可以使用简化语法表示空数组或空字典:

shoppingList = []
occupations = [:]

这里的类型推断为变量的类型。

Control Flow

控制流主要包括条件和循环。分支结构可以用 ifswitch,循环结构可以用 for-inwhilerepeat-while

与 C/C++ 不同,ifswitch 后面的条件可以不加圆括号 ()。但是分支和循环结构的代码体都必须加花括号 {},即使只有一条语句。

循环结构类似于 Python,但多出一个 repeat-while,类似于 C/C++ 中的 do-while

for-in 循环的使用,必须加花括号:

let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
print(teamScore)

值得注意的是,由于 Swift 没有隐式类型转换,在其他语言中常用的 if score { ... } 语法是会报错的,条件必须是一个 Bool 值。

Swift 用 nil 表示一个空对象,但是一个普通变量不能是非空的,除非在定义时的类型后面加上一个问号 ?,此时变量的类型是 Optional

var optionalString: String? = "Hello"
print(optionalString == nil)

为了尽可能减少空指针引用的错误,Swift 将可能为空的类型统一定义为 Optional,当需要使用或输出一个 Optional 变量时,需要先拆包 (unwrap) 为普通类型。

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

除了通过 if 条件判断并拆包,也可以在使用 Optional 变量时提供一个默认值来妥善处理可选类型。

let nickname: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickname ?? fullName)"

Swift 中的 switch 语句更加强大,支持所有数据类型、匹配多个值、复杂条件匹配等。

let vegetable = "red pepper"
switch vegetable {
case "celery":
    print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
    print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
    print("Is it a spicy \(x)?")
default:
    print("Everything tastes good in soup.")
}

通过使用 let 可以实现部分分支匹配复杂的条件,而不仅仅是值相等。每个 case 的结尾也不需要写 break。

使用 for-in 遍历字典时需要提供两个临时变量名,并用圆括号 () 括起来,不需要的值可以用 _

let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (_, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
print(largest)

Swift 中的 whilerepeat-while 与其他语言类似。

var n = 2
while n < 100 {
    n *= 2
}
print(n)

var m = 2
repeat {
    m *= 2
} while m < 100
print(m)

Swift 提供了类似于 Python 中 range 的写法,但更加简洁:

var total = 0
for i in 0..<4 {
    total += i
}
print(total)

..< 不包括上界,比如 0..<4 表示 0, 1, 2, 3;... 包括上界,比如 0...4 表示 0, 1, 2, 3, 4。

Functions and Closures

函数声明和调用的语法如下:

func greet(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

函数定义时参数列表里的是参数名 (name),函数调用时使用的是参数标签 (label),函数定义时如果没有给出 label,默认使用 name 作为 label。

也可以额外指定参数的 label,函数调用使用 label 能使代码的可读性更好 (调用语句就像一个完整的句子)。下划线 _ 表示没有 label。

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

参数类型也可以是数组或字典,声明语法和定义变量相同。返回值可以有多个,用圆括号 () 括起来作为一个元组 tuple,可以给每个返回值指定一个名字 (也可以不指定),访问时用名称或索引。

func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics)
// Prints "(min: 3, max: 100, sum: 120)"
print(statistics.sum)
// Prints "120"
print(statistics.2)
// Prints "120"

函数的定义可以嵌套,内部函数可以使用外部的变量。

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

Swift 中函数是一等公民,就像 JavaScript 一样,函数可以作为参数、返回值,表达方式与函数定义类似。

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

makeIncrementer 函数返回了一个函数 addOne,这个函数接收一个 Int 参数,返回一个 Int 值。

函数作为参数的一个例子:

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)

Swift 拥有闭包 (closure) 的概念,类似于其他语言中的匿名函数。闭包是一段可执行的代码块,用花括号 {} 包含,用 in 分隔类型声明与代码体。函数是一种特殊的闭包。闭包可以访问创建时所在作用域的变量和函数。

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    return result
})

Swift 作为一门现代语言,尽可能简化了开发者的编码,能省略的地方几乎都可以省略不写。当闭包的参数和返回值的类型都已知时,可以省略类型,只写参数名,使代码更加简洁。

let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
// Prints "[60, 57, 21, 36]"

甚至可以省略参数名,只根据参数的位置来引用参数 (从 0 开始)。

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
// Prints "[20, 19, 12, 7]"

Objects and Classes

Swift 支持面向对象,拥有类 class 的概念。可以在类内定义变量和函数。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

用构造函数实例化一个类,用点语法访问对象的属性和方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

Swift 中类的构造函数有一个统一的名字 init,与 Python 类似。在类内用 self 引用当前对象。

class NamedShape {
    var numberOfSides: Int = 0
    var name: String

    init(name: String) {
        self.name = name
    }

    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

类的每个属性都必须被初始化,可以在定义变量时初始化,也可以在构造方法中初始化。

Swift 也提供了析构方法 deinit

如果需要从另一个类继承,则在类名后写出父类名称,用冒号 : 分隔。Swift 不要求所有的类都有一个最终的祖先,比如 Java 中的 Object,如果一个类定义时没有给出父类,那么这个类就不继承任何一个类。

class Square: NamedShape {
    var sideLength: Double

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }

    func area() -> Double {
        return sideLength * sideLength
    }

    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

在子类中重写父类方法必须加上 override,重写了方法但没有加 override 或加了 override 但没有重写方法都会报错,这也避免了一个常见的编码错误。

可以为属性添加 getset 方法,实现更复杂的逻辑。set 方法的参数名默认是 newValue,也可以自己指定。

class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get {
            return 3.0 * sideLength
        }
        set {
            sideLength = newValue / 3.0
        }
    }

    override func simpleDescription() -> String {
        return "An equilateral triangle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
// Prints "9.3"
triangle.perimeter = 9.9
print(triangle.sideLength)
// Prints "3.3000000000000003"

Swift 还提供了 willSetdidSet 方法,用于在 set 之前和之后执行。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
        willSet {
            square.sideLength = newValue.sideLength
        }
    }
    var square: Square {
        willSet {
            triangle.sideLength = newValue.sideLength
        }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
// Prints "10.0"
print(triangleAndSquare.triangle.sideLength)
// Prints "10.0"
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
// Prints "50.0"

与基本类型相同,对象也有可选类型 Optional,当可选对象为 nil 时,在该对象上的一切操作都会被忽略;如果不是 nil,则拆包并执行相应操作,但最终结果仍是 Optional 类型。

let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
print(sideLength)
// Prints "Optional(2.5)"

Enumerations and Structures

Swift 中使用 enum 创建一个枚举类型,枚举类型中的每个枚举量都对应一个 rawValue,这个 rawValue 可以是整型、浮点型和字符串。如果是整型,默认从 0 开始。除了枚举量,枚举类型还可以包含方法。

enum Rank: Int {
    case ace = 1
    case two, three, four, five, six, seven, eight, nine, ten
    case jack, queen, king

    func simpleDescription() -> String {
        switch self {
        case .ace:
            return "ace"
        case .jack:
            return "jack"
        case .queen:
            return "queen"
        case .king:
            return "king"
        default:
            return String(self.rawValue)
        }
    }
}
let ace = Rank.ace
let aceRawValue = ace.rawValue

switch 语句中的 .ace 其实是 Rank.ace 的简化写法,由于已经知道 self 是 Rank 类型的枚举值,因此可以省略。

上面的代码直接使用枚举量的名称来实例化一个枚举类型,也可以用 rawValue 来实例化一个枚举类型,但是需要注意返回值是 Optional 类型,需要拆包后使用。

if let convertedRank = Rank(rawValue: 3) {
    let threeDescription = convertedRank.simpleDescription()
}

枚举量也可以包含参数化的多个 rawValue,作为枚举量的内部属性。

enum ServerResponse {
    case result(String, String)
    case failure(String)
}

let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")

switch success {
case let .result(sunrise, sunset):
    print("Sunrise is at \(sunrise) and sunset is at \(sunset).")
case let .failure(message):
    print("Failure...  \(message)")
}
// Prints "Sunrise is at 6:00 am and sunset is at 8:09 pm."

Swift 中的结构 struct 与类 class 很相似,都可以包含属性和方法,区别是 struct 在赋值时总是深拷贝,即复制全部数据,而 class 是浅拷贝,只复制引用。

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

Protocols and Extensions

Swift 中的协议 protocol 类似于其他语言中的接口,定义了需要包含的属性和方法,包括各种数据类型。mutating 表示该方法可以修改内部的属性。

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

类或结构都可以采用 adopt 协议,即包含协议要求的属性和方法。

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

除了在定义新的类型时 adopt 协议,也可以通过 extension 让已有的类型支持协议。

extension Int: ExampleProtocol {
    var simpleDescription: String {
        return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
print(7.simpleDescription)
// Prints "The number 7"

协议可以用来实现多态。无法访问协议定义之外的属性和方法。

let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// Prints "A very simple class.  Now 100% adjusted."
// print(protocolValue.anotherProperty)  // Uncomment to see the error

Error Handling

Swift 中的 Error 类似于其他语言中的异常,可以用一个 adopt 了 Error 协议的类型表示,比如枚举类型。

enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

throw 抛出一个错误,用 throws 标记可能抛出错误的函数,由调用函数的作用域负责处理错误。

func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

可以用 do-catchtry 来处理错误,在可能抛出错误的函数调用前加上 try,并将语句放在 do 代码块内,如果抛出了错误,错误对象将使用 error 作为名称,并进入到 catch 代码块内。

do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
// Prints "Job sent"

可以使用多个 catch 代码块,对不同类型的错误进行不同的处理。

do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I‘ll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
// Prints "Job sent"

如果需要简化错误处理,比如出错直接返回 nil,可以用 try?。如果未抛出错误,函数的返回值将被转换为 Optional 类型,否则转换为 nil

let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

函数中可以加入 defer 代码块,无论是否抛出错误,在函数返回前都会执行,用于做一些善后工作。

var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]

func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }

    let result = fridgeContent.contains(food)
    return result
}
fridgeContains("banana")
print(fridgeIsOpen)
// Prints "false"

Generics

在函数名后用尖括号 <> 添加泛型。

func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
    var result = [Item]()
    for _ in 0..<numberOfTimes {
        result.append(item)
    }
    return result
}
makeArray(repeating: "knock", numberOfTimes: 4)

在函数体之前用 where 表示对泛型的约束和要求。

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element
{
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])

上面的函数使用 2 个类型参数 TU,要求两者都采用了 Sequence 协议,并且元素都采用了 Equatable 协议。

【iOS】A Swift Tour

标签:sid   swift   float   row   ota   width   lis   ams   参数   

原文地址:https://www.cnblogs.com/huzheyu/p/ios-a-swift-tour.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!