Swift语法笔记

180 阅读9分钟

Swift初见

Hello World 就这么来了

print("Hello World")

这就是一个完整的程序。不需要main函数,甚至不用;号。

简单值

使用let声明常量,使用var声明变量。

  • 通过一个值来声明变量和常量时,编译器会自动推断其类型,不用明确的声明类型
let myConstant = 12
var myVariable = 7
myVariable = 10
  • 如果没有初始值,需要在变量后面声明类型,使用冒号分割
var explicitDouble : Double = 70

将值转换为字符串的方式

  • 直接使用String()显示转换
let label = "The width is"
let width = 12
let widthLabel = label + String(width)
  • 使用\()来转换
print("The width is \(width)")

多行字符串的使用

使用三个双引号"""来包含多行字符串内容。双引号不能与字符串内容在同一行出现,会报错。

let quotation = """
白日依山尽,
黄河入海流。
欲穷千里目,
更上一层楼。
"""
print(quotation)

数组和字典

使用方括号[]来创建数组和字典。

var shoppingList = ["catfish","water","tulips"]

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

shoppingList.append("blue paint")
occupations["Jayne"] = "Public Relations"

创建一个空数组或空字典

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

如果类型信息可以推测出来,可以直接使用[][:]来创建空数组和空字典

shoppingList = []
occupations = [:]

控制流

使用ifswitch来进行条件操作,使用for-inwhilerepeat-while来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须得的。

if和for语句

let individualScores = [75,23,56,56,85]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    }else{
        teamScore += 1
    }
}
print(teamScore)

if语句中,条件必须是一个布尔表达式。 也可以使用iflet一起来处理值缺失的情况。一个可选的值是一个具体的值或者是nil以表示值缺失。在类型后面加一个问号来标记一个变量是可选的。 也可使用??操作符来提供一个默认值,如果值缺失就是用默认值来替代。

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

switch语句

switch支持任意类型的数据以及各种比较操作,不仅仅是整数以及测试相等

let vegetable = "red pepper"
switch vegetable {
case "cellery":
    print("cellery")
case "cucumber":
    print("cucumber")
case let x where x.hasSuffix("pepper"):
    print("pepper")
default:
    print("default")
}

default语句是必须有的。switch语句在匹配到case语句之后,程序会退出switch语句,不会继续执行,所以不需要在每个子句之后写break

while语句

  • 使用 while来重复运行一段代码直到条件改变
var n = 2
while n < 100 {
    n *= 2
}
print(n)
  • 循环条件可以在结尾,保证至少循环一次
var m = 2
repeat{
    m *= 2
}while m < 100
print(m)

下标范围的表示:使用..<不包含上界;使用...包含上界

函数和闭包

使用func来声明一个函数,使用名字和参数来调用函数。使用->来指定函数的返回值类型。

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

greet(person: "Bob", day: "Tuseday")

默认情况下,函数使用参数名称作为参数的标签,也可以在参数的前面自定义参数标签,或者使用_表示不使用参数标签。

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

greet("Bob", on: "Tuseday")

函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,可以用来重构一个太长或太复杂的函数。

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

returnFifteen()

函数作为一等类型,是可以作为另一个函数的返回值或者参数的

  • 作为返回值
func makeIncrementer() -> ((Int) -> Int){
    func addOne(number: Int) -> Int{
        return number + 1
    }
    return addOne
}

var incrementer = makeIncrementer()
incrementer(8)
  • 作为参数
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool{
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}

func lessThanTen(num: Int) -> Bool{
    return num < 10
}
var nums = [20,12,4,56]

hasAnyMatches(list: nums, condition: lessThanTen)

函数实际上是一种特殊的闭包:他是一段能之后被调用的代码。闭包中的代码能访问闭包作用域中的变量和函数,及时闭包是在一个不同的作用域被执行的。 使用一个{}来创建一个匿名的闭包,使用in将参数和返回值类型的声明与闭包函数体分离。

var nums = [20,12,4,56]

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

对象和类

使用class和类名来创建一个类。类中属性的声明和常量、变量一样,唯一的区别就是上下文是类。

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

创建一个类的实例,在类名后面加上括号(),使用点语法来访问实例的属性和方法。

let shape = Shape()
shape.numberOfSides = 3
var shapeDescription = shape.simpleDEscription()
print(shapeDescription)

构造函数,使用init来创建一个构造器,来初始化类实例。使用deinit来创建一个析构函数,来在对象释放之前进行一些清理工作。

class NameShape{
    var numberSides: Int = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape named \(self.name) with \(numberSides) sides"
    }
}

创建一个子类,是在类名后面加上父类的名字,中间用冒号分开。重写父类的方法需要使用override标记。编译器同样也会检测使用override标记的方法是否在父类中。

class Square: NameShape{
    var sideLength: Double
    
    init(sideLength: Double,name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberSides = 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()

还可以使用settergetter设置计算属性。

class EquilateralTriangle: NameShape{
    var sideLength: Double = 0.0
    
    init(sideLength: Double,name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberSides = 3
    }
    
    var perimeter: Double {
        get{
            return 3.0 * sideLength
        }
        set{
            sideLength = newValue / 3.0
        }
    }
    
    override func simpleDescription() -> String {
        return "An equilateral triangel with sides of length \(sideLength)"
    }
}

setter中,新值的名字是newValue。或者在set之后的圆括号中显式的设置一个名字。

var perimeter: Double {
        get{
            return 3.0 * sideLength
        }
        set(myNewValue){
            sideLength = myNewValue / 3.0
        }
    }

如果不需要计算属性,但仍需要在设置一个新值之前或之后运行代码,可以使用willSetdidSet

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 triangelAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangelAndSquare.square.sideLength)
print(triangelAndSquare.triangle.sideLength)
triangelAndSquare.square = Square(sideLength: 50, name: "lager square")
print(triangelAndSquare.triangle.sideLength)

子类初始化的步骤

  • 设置子类声明的属性值
  • 调用父类的构造器
  • 改变父类定义的属性值

枚举

使用enum来创建一个枚举。枚举可以包含方法

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
print(ace)  //"ace\n"

默认情况下,Swift按照从0开始每次加1的方式为原始值就行赋值,也可以通过显示的赋值进行改变,如Ace被显示赋值为1,则剩下的原始值会按照顺序赋值。也可以使用字符串或者浮点数作为枚举的原始值。

可以使用init?(rawValue:)初始化构造器来从原始值创建一个枚举实例。

if let covertedRank = Rank(rawValue: 3) {
    let threeDescription = covertedRank.simpleDescription()
    print(threeDescription)  //"3\n"
}

给枚举成员设置关联值

let failure = ServerResponse.result("5:00 am", "9:00 pm")
let failure = ServerResponse.failure("Out of cheese")

switch failure {
case let .result(sunrise, sunset):
    print("Sunrise is \(sunrise) and sunset is at \(sunset)")
case let .failure(message):
    print("Failure... \(message)")
}

结构体

使用struct来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器,最大的一个区别就是结构体是传值,类是传引用

构造一个结构体

enum Suit{
    case spades,hearts,diamonds,clubs
    func simpleDescription() -> String {
        switch self {
        case .spades:
            return "spades"
        case .hearts:
            return "hearts"
        case .diamonds:
            return "diamonds"
        case .clubs:
            return "clubs"
        }
    }
}

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 threeOfSapdesDescription = threeOfSpades.simpleDescription()
print(threeOfSapdesDescription)//The 3 of spades

var clone = threeOfSpades
clone.rank = Rank.ace
print(clone.simpleDescription())//The ace of spades
print(threeOfSpades.simpleDescription())//The 3 of spades

通过引用一个类和结构体来解释 结构体是传值,类是传引用,重点看注释

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

let shape = Shape()
shape.numberOfSides = 3
print(shape.simpleDescription())//A shape with 3 sides

var clone = shape
clone.numberOfSides = 5
print(clone.simpleDescription())//A shape with 5 sides
print(shape.simpleDescription()) //A shape with 5 sides

协议protocol

使用protocol来声明一个协议。类型、枚举和结构体都可以遵循协议。

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

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

var a = SimpleClass()
a.adjust()
print(a.simpleDescription)

struct SimpleStruct: ExampleProtocol{
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += "  (adjusted)"
    }
}

var b = SimpleStruct()
b.adjust()
print(b.simpleDescription)

注意: 声明SimpleStruct时候mutating关键字来标记一个会修改结构体的方法。SimpleClass不需要标记,因为类中的方法通常是可以修改类属性的。

拓展: 可以像使用其他命名类型一样使用协议名。可以创建一个有不同类型,但是都实现了同一个协议的对象集合。处理的对象是协议的值时,协议外定义的方法不可用。

扩展extension

使用extension 声明一个扩展,用来给现有的类型添加新的功能。可以使用扩展让某个在别处声明的类型来遵守某个协议。

extension Int: ExampleProtocol{
    var simpleDescription: String{
        return "The number is \(self)"
    }
    mutating func adjust() {
        self += 12
    }
}

print(7.simpleDescription)

错误处理

通过实现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-catch。在do代码块中,使用try来标记可以抛出错误的代码。在catch代码块中,错误默认命名为error,也可以自定义名字。
do {
    let printerResponse = try send(job: 12, toPrinter: "Never Has Toner")
    print(printerResponse)
} catch  {
    print(error)
}

也可以使用多个catch来处理不同的错误类型。

do {
    let printerResponse = try send(job: 12, toPrinter: "Never Has Toner")
    print(printerResponse)
} catch PrinterError.outOfPaper {
    print("outOfPaper")
} catch PrinterError.onFire{
    print("onFire")
} catch{
    print(error)
}
  • 使用try?将结果转化为可选的。如果函数抛出错误,该错误会被抛弃并且结果为nil,否则,结果会是一个包含函数返回值的可选值。
let printerSuccess = try? send(job: 11, toPrinter: "Test")

拓展: 使用defer代码块来表示在函数返回前,函数最后执行的代码。无论函数是否会抛出错误,这段代码都会被执行。

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

func fridgeContains(_ food: String) -> Bool{
    fridgeIsOpen = true
    defer{
        fridgeIsOpen = false
    }
    let result = fridgeCountent.contains(food)
    return result
}

fridgeContains("banana")
print(fridgeIsOpen)

泛型

在尖括号里写一个名字来创建一个泛型函数或者类型。

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], [2])