oh-my-docs

Summary for developers


Project maintained by italkso Hosted on GitHub Pages — Theme by mattgraham

Swift 编程

1.Swift 简介

Swift 的现代编程特性

Swift 的版本更迭

Swift的特点

2. 语法要点

此处总结了使用 Swift 编写代码的一些要点,如果需要更详细的说明,后文相关章节会有展开。

使用 Xcode、 Playground、swiftc 或 REPL 都可以编译和运行 Swift 代码。

使用 Xcode 的 Playground 文件练习 Swift 语法时,请记得在Xcode 的 Playground文件中使用import Foundation导入 Swift 语言及核心功能框架。长按并选择底部的「Automatically Run」,Playground 就会在在用户每次新增代码后,自动运行并给出代码执行结果。

注释和打印

单行注释用//,多行注释用/* */print()函数默认添加了换行\n,如果不想要换行,使用 print()给 terminator 赋值一个期望的分隔符。

public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

代码规范

包括大小写、空行、命名、标点符号等。

常量和变量

let 和 var

命名规则(见名知义)

命名禁忌

运算符

一元(unary)运算符只需要一个操作数,前缀(-a!b)、后缀(可选类型的)均可。 二元(binary)运算符需要两个数,中缀( 3 + 4 )。条件运算符(?: )是唯一的三元(ternary)运算符。

控制流

类型

因为存储空间总是有限的,计算机所作的计算也只是近似计算,所有需要类型这个概念。

按照存储性质类型划分,可以分为值类型与引用类型。基本类型是指编程语言直接提供的类型,而enum、class、struct等是自定义类型。

Swift 是强类型语言 (strongly typed language),要么直接完成初始化,Swift 会根据内容推断常量或变量的类型,即类型推断(Type Inference);要么明确明确标出常量或变量的类型,即类型注解(Type Annotations)。按下 Option 键并点击任意常/变量,Xcode 就会显示其类型。

使用String(42)Int 42 转换为 String呢,就叫做类型转换(Type Casting)。

你还可以适度使用typealias关键字给基础类型或者更复杂的类型起一个类型别名,比如typealias name = String

常用的类型

值类型和引用类型

硬盘容量大,可以永久存储( Permanent Storage)信息。而内存空间有限,仅被用来临时存放(Temporary Storage)正在运行的应用。

Int、Double、Bool、Enum、Struct、Array、Dictionary等都是值类型(Value Type),它们直接将信息存储在内存的存储单元中,这些类型的常量或变量所指代的就是内存中的信息本身。

Class是属于引用类型(Reference Type),引用类型的变量存储的是内存中值所对应的内存地址,即指针(Pointer)。

集合类型

元组

Tuple

可选类型

结构、枚举、类、扩展和协议

权限控制 Access Control

权限控制( Access Control)分为 5 个级别,即openfileprivateinternalprivatepublic

异常处理

Swift 使用 Error 协议处理应用中的异常 ,你可以使用do-try-catchtry?try!处理可能的异常。

3. 函数和闭包

函数和闭包都是引用类型,当你把一个函数或闭包赋给一个常量或变量时,实际上是在引用函数和闭包。

函数(Function)

函数是 first-class 类型,可以将函数类型作为参数传递和返回值返回。

// Define a function
func functionName(argumentLabel parameter: ParameterType) -> ReturnType {
    //    Implementation
    return ReturnType()
}

// Call a function
functionName(argumentLabel: actualValue)    

示例:

func greeting() {
    print("Hello")
}

func greeting(to name:String) {
    print("Hello,\(name)")
}

greeting()
greeting(to: "Jobs")

闭包(Closure)

//    Closure Expression
{(parameters) -> ReturnType in
    statements
}

let chars = ["a", "e", "i", "o", "u"]

var reversedChars = chars.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

reversedChars = chars.sorted(by:{s1, s2 in return s1 > s2})

reversedChars = chars.sorted(by:{s1, s2 in s1 > s2})

reversedChars = chars.sorted(by:{ $0 > $1})

reversedChars = chars.sorted(by:>)
let chars = ["a", "e", "i", "o", "u"]

var reversedChars = chars.sorted { (s1: String, s2: String) -> Bool in
    return s1 > s2
}

reversedChars = chars.sorted {s1, s2 in return s1 > s2}

reversedChars = chars.sorted {s1, s2 in s1 > s2}

reversedChars = chars.sorted { $0 > $1}

4. 结构、类和 OOP

面向对象编程OOP

面向对象编程(Object-oriented programming,OOP)就是「整理归类」,即将待用物品归类,并根据类别赋予不同的功能和属性。OOP 的实质是间接(indirection)技术。

抽象(Abstraction)封装 (Encapsulation)继承 (Inheritance)多态 (Polymorphism)是OOP 的四个重要属性。

Swift 中,类 class 与结构 struct 都用来创建自定义类型,都具有「属性、初始化器、方法」。关键字不同只是两者在语法上的区别。有兴趣可以参考 CS193P 课程中关于结构和类的比较。

struct 和 class 的核心差异是:

对于 Apple 官方框架来说,适用于反复使用的框架一般定义为 class,以减少重复占用过多内存。而不需要继承、不适合反复使用的实体常被定义为 struct。Apple 官方文档建议,当创建新的自定义类别时,首先定义为 struct。只有你需要用到 class 继承的特性,或者是作为引用类型的特性时,再将关键字更换为 class。

结构

struct 用来定义新的类型,是 Swift 语言的核心组成部分。一个常见的 struct 可能包含属性初始化器方法。用属性来处理数值、用方法来书写功能。Swift 中的IntDoubleArrayDictionary 等数据类型实际上都是struct 。

struct StructName {

    // Instance or Type Property, Stored or Computed Property, Lazy Property
    var property: Int

    //    Default Initializer, Custome Initializer
    init(property: Int) {
        self.property = property
    }

    //    Instance or Type Method, Mutating Method
    func someMethod() {
        //
    }
}

参考示例:

lazy 表示把赋值操作 currentHealth = maxHealth 延迟currentHealth 真正需要被使用时。

//  MARK: - Structure
struct Player {
    //  MARK: - Property
    static var allPlayers: [Player] = []    // Type Property
    var name: String
    var livesRemaining = 5 {//Property Observer
        willSet {
            print("Warning: \(livesRemaining) Lives")
        }
        didSet {
            if livesRemaining != 0 {
                print("I'm Back!")
            } else {
                print("Game Over.")
            }
        }
    }// Property Observer
    let maxHealth = 100
    lazy var currentHealth = maxHealth
    var isPlayerOutOfLives: Bool {
        get {
            livesRemaining == 0 ? true : false
        }
        set {
            if newValue {
                livesRemaining = 0
            }
        }
    }// Computed Property

    //  MARK: - Initializer
    init(name: String) {
        self.name = name
    }// Default initializer with a default value

    init(name: String, livesRemaining: Int, currentHealth: Int) {
        self.name = name
        self.livesRemaining = livesRemaining
        self.currentHealth = currentHealth
    }// Default initializer without a default value

    init(name:String, livesRemaining: Int) {
        self.name = "VIP" + name
        self.livesRemaining = livesRemaining
        currentHealth = 10000
    }// Customer initializer

    //  MARK: - Method
    func welcomePlayer() {
        print("Current Player: \(name)")
    }

    mutating func damaged(by health: Int) {
        currentHealth -= health

        if currentHealth <= 0 && livesRemaining > 0 {
            livesRemaining -= 1
            currentHealth = maxHealth
        }

        if livesRemaining == 0 {
            print("Game Over")
        }
    }

    mutating func stateReport() {
        print("Current Health\(currentHealth),\(livesRemaining)Lives Left")
    }

    static func recentAddedPlayer() -> Player {
        allPlayers[allPlayers.count - 1]
    }// Type Method
}

var playerX = Player(name: "X")
var playerY = Player(name: "Y", livesRemaining: 10, currentHealth: 100)
Player.allPlayers.append(contentsOf: [playerX, playerY])
print("Player Added Recently: \(Player.recentAddedPlayer().name)")
playerX.stateReport()
playerX.isPlayerOutOfLives = true

playerX.damaged(by: 50)
playerX.stateReport()

playerX.damaged(by: 30)
playerX.stateReport()
//    Definition of Structure
struct Resolution {
    var width = 0
    var height = 0

    func printInfo(){
        print("Width:\(width),Height:\(height)")
    }
}

//  Create an instance and initialize members of structure Resolution
let someResolution = Resolution(width: 640, height: 480)

print("\(someResolution.height)")
someResolution.printInfo()

属性

方法

初始化器

初始化器(Initializers)是特殊的方法,用于给 struct 创建 instance ,使用初始化器是为了确保 Instance 中的所有属性都被赋值。Swift 允许开发者自定义初始化器。

通常使初始化器的参数与结构的属性同名,可以使用 self 关键词区分,关键词 self 等同于当前的instance

一般Swift 会自动提供两个初始化器的版本,一种将默认值考虑进去,只需开发者提供剩余参数即可,如 init(name: String);另一种不考虑默认值,要求提供所有参数,如 init(name: String, livesRemaining: Int, currentHealth: Int)

使用类(Class)可以实现上述 OOP 的四个属性。

继承

A class ( subclass ) can inherit methods, properties, and other characteristics from another class ( superclass ).

class SomeBaseClass {
    // Definition of base class 
}

class SomeSubclass: SomeSuperclass {
    // Definition of subclass
}

多态

5. 可选类型和枚举

//     Enumerations
enum CompassPoint {
    case north, south, east, west
    mutating func turnNorth() {
        self = .north
    }
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()

print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")

数组( Array) 适合存储大量不确定的数据,枚举(Emuration) 适合存储有限类别的事务,使用枚举可以创建一个新类型。枚举的关键词是 enum...case。使用枚举的方式是「枚举名称.枚举中的种类」。

可选类型(Optional)实际上就是一个包含两个状态的枚举,Optional.some存储数值,Optional.none存储空值。你可以给 policyNote 赋值 nil,或直接根据其枚举的属性给它赋值为 Optional.none,其结果完全一致,如下图所示。。

除了单独使用外,你也可以将枚举与 switch 语句搭配使用,用于对枚举中不同类型的事务做区别处理。

6. 协议

协议

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements.

protocol MyProtocol {
    var name: String { get }

    init(name:String)
}

struct FirstStruct: MyProtocol {
    var name: String
    init(name:String) {
        self.name = name
    }// You can omit these codes in structure because of its default initializer
}

struct SecondStruct {
    var name: String
}

extension SecondStruct: MyProtocol {
    //    Some Code
}

class ClassName: SuperClass, MyProtocol {
    // Defination of Class
    // Implementation of ProtocolName
}

协议是规则,遵守协议的结构、类或枚举需要实现协议规定的功能。遵守协议可以写在 结构、类或枚举中,也可以用扩展来写。

面向协议编程(Protocol Oriented Programming,POP)是为了代码复用,即整理项目中的 struct、class、enum等代码,提取逻辑层面的内容后将这些能够复用的部分写出协议。因此,protocol 中的属性、方法及初始化器都有其特殊之处。

常见的协议类型

Equatable 协议和 Comparable 协议用于比较、Hashable 协议和 Identifiable 协议用于识别、Codable 协议用于存取。

7. 扩展

Extensions add new functionality to an existing class, structure, enumeration, or protocol type.

//    Extension Syntax
extension SomeType {
    // New functionality
}

//    Extend an existing type to make it adopt one or more protocols
extension SomeType: SomeProtocol, AnotherProtocol {
    // Implementation of protocol requirements
}

//    Computed Properties
extension Double {
    var km: Double { return self * 1_000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}

使用扩展(Extension)可以扩展现有类型(struct,class ,enum,protocol)的内容,扩展支持计算属性、初始化器及方法。

8. 异常处理

调试时强行终止代码运行

应用中的异常处理

Swift 使用 Error 协议处理应用中的异常 。Swift 中的异常处理常被称作do-try-catch ,这是因为与异常处理相关是如下的一些关键词:

//  Error
enum PasswordError: Error {
    case notLongEnough
    case tooLong
}

//    Validate the length of a password
func validatePassword(_ password: String) throws -> Bool {

    if password.count < 6 {
        throw PasswordError.notLongEnough
    } else if password.count > 20 {
        throw PasswordError.tooLong
    } else {
        return true
    }
}

var password = "1234678910"

//    do-try-catch
do {
    try validatePassword(password)
    print("Validate Password")
} catch  PasswordError.notLongEnough {
    print("The password must be at least 6 digits.")
} catch  PasswordError.tooLong {
    print("The password must be no more than 20 digits.")
}

// try?
if let validateResult = try? validatePassword(password) {
    print("Validate Result: \(validateResult), Validate Password.")
} else {
    print("The password must be between 6-20 digits.")
}

//  try!
try! validatePassword(password)