Swift 基础语法
简单值
类型不会自动转换,需要手动转换:
let label = "The width is "
let width = 94
let widthLabel = label + String(width)
字符串插值语法 ()
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
数组与字典
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
//初始化多个值
Array(repeating: GridItem(), count: 3)
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
//断言
let m1: [String:Int] = ["lisi": 13, "wangwu": 12]
let number = m1["lisi"]! + 5
let emptyArray: [String] = []
let emptyDictionary: [String: Float] = [:]
元组
var m: (max: Int, Int, sum:Int) = (1,2,3)
print(m.0)
print(m.max)
print(m.1)
控制流语句
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)
// 遍历字典
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)
// 输出 "25"
if let xxx = XXX
var optionalString: String? = "Hello"
print(optionalString == nil)
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
?? 默认值
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
级联 ?
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
switch case
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.")
}
for ... in range
//不包含上界
var total = 0
for i in 0..<4 {
total += i
}
print(total)
//包含上界
var total = 0
for i in 0...4 {
total += i
}
print(total)
for (index,item) in xxx.enumertated
let names = ["Alice", "Bob", "John"]
names.enumerated().forEach { (index, name) in
print("\(index): \(name)")
}
for (index, name) in names.enumerated() {
print("\(index): \(name)")
}
函数与闭包
函数
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person:"Bob", day: "Tuesday")
参数的 tag
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
函数闭包
函数作为返回值
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
函数作为参数
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)
闭包
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
注:其中关键字 in
前面是函数签名,后面是函数 body
简写 1
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
简写 2
let reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
print(reversedNames)
简写 3
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
简写 4
// 直接传另一个运算符作为参数
let r = names.sorted(by: >)
尾随闭包
// 函数的声明
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 正常使用闭包传参,**需要写参数标签**
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 当需要将一个很长的闭包表达式作为最后一个参数传递给函数,尾随闭包是一个书写在**函数圆括号之后的闭包表达式,不用写出它的参数标签**
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
// 如果闭包表达式是函数或方法的唯一参数,还可以把 () 也省略掉:
someFunctionThatTakesAClosure {
// 闭包主体部分
}
举例:
// 尾随闭包省略参数标签
reversedNames = names.sorted() { $0 > $1 }
// 尾随闭包省略参数标签与圆括号
reversedNames = names.sorted { $0 > $1 }
除了忽略尾闭包参数标签,还可以选择忽略第一个闭包的参数标签,但剩余闭包参数标签得写出来
//函数声明
func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) {
if let picture = download("photo.jpg", from: server) {
completion(picture)
} else {
onFailure()
}
}
//尾闭包调用
loadPicture(from: someServer) { picture in
someView.currentPicture = picture
} onFailure: {
print("Couldn't download the next picture.")
}
示例:(swiftui 中)
//列出所有参数标签
Button(action: {
config.done()
}, label: {
Text("Save")
})
//最后一个尾闭包不写参数标签,写在()后面
Button(action: {
config.done()
}) {
Text("Save")
}
//多个闭包,第一个尾闭包不写参数标签,其余尾闭包参数标签需要写出来
Button {
config.done()
} label: {
Text("Save")
}
自动闭包
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
// 正常闭包,需要写花括号
serve(customer: { customersInLine.remove(at: 0) } )
func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
// 自动闭包,传参时不需要写花括号
serve(customer: customersInLine.remove(at: 0))
逃逸闭包
// 闭包参数不在函数内执行,而在函数外执行
// 存储闭包的数组
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
//将传进行的闭包参数放到外面的数组中,并不执行
completionHandlers.append(completionHandler)
}
对象和类
类与构造函数
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
注:每个属性都要有初始化 —— 无论是通过声明(就像 numberOfSides
)还是通过构造器(就像 name
)。
getter/setter 的使用
class Person {
private var name: String
private var _age: Int = 0
var age: Int {
get {
_age
}
set {
_age = newValue
}
}
init(name: String) {
self.name = name
}
func printName() {
print("\(name)")
}
}
let p = Person(name: "Lisi")
p.printName()
p.age = 15
print(p.age)
willSet/didSet 在设置一个新值之前或者之后运行代码
略
单例
// 日期格式化
class AppDateFormatter {
// 初始化单例
static let shared = AppDateFormatter()
private init() {}
private let mediumDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .none
return df
}()
private let mediumTimeFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .none
df.timeStyle = .medium
return df
}()
private let mediumDateTimeFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .medium
return df
}()
func mediumDateString(from date: Date) -> String {
return mediumDateFormatter.string(from: date)
}
func mediumTimeString(from date: Date) -> String {
return mediumTimeFormatter.string(from: date)
}
func mediumDateTimeString(from date: Date) -> String {
return mediumDateTimeFormatter.string(from: date)
}
}
枚举
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
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
枚举的简写
enum CompassPoint {
case north;
case south;
case east;
case west;
}
// 当被赋值的变量或参数的类型已知时,可使用点语法进行简写
var directionToHead: CompassPoint = .east
原始值(默认值)
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
//当默认值为整数或者字符串类型的枚举时,Swift 将会自动赋值
// 整数
enum CompassPoint: Int {
case north = 1;
case south;
case east;
case west;
}
var directionToHead: CompassPoint = .south
// 打印1
print(directionToHead.rawValue)
// 字符串
enum CompassPoint:String {
case north;
case south;
case east;
case west;
}
var directionToHead: CompassPoint = .south
// 打印 south
print(directionToHead.rawValue)
关联值(枚举类的成员是一个构造函数)
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
递归枚举(枚举类的成员中存在关联值类型是枚举类本身)
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 在枚举类型开头加上 indirect 关键字来表明它的所有成员都是可递归的
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
结构体
使用 struct
来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
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()
结构体和枚举是值类型。当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝。Swift 中所有的基本类型:整数(integer)、浮点数(floating-point number)、布尔值(boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,其底层也是使用结构体实现的。
协议
协议
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
类、枚举和结构体都可以遵循协议
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"
// struct 中 mutating 关键字用来标记一个会修改结构体的方法
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
属性
存储属性
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。
计算属性
除存储属性外,类、结构体和枚举可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
简化 setter 声明
struct AlternativeRect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set {
// 如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue。
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
只读计算属性
struct Cuboid {
var width = 0.0, height = 0.0, depth = 0.0
// 只读计算属性的声明可以去掉 get 关键字和花括号
var volume: Double {
return width * height * depth
}
}
属性观察器
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("将 totalSteps 的值设置为 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("增加了 \(totalSteps - oldValue) 步")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步
属性包装器
定义一个属性包装器,你需要创建一个定义 wrappedValue 属性的结构体、枚举或者类。在下面的代码中,TwelveOrLess 结构体确保它包装的值始终是小于等于 12 的数字。如果要求它存储一个更大的数字,它则会存储 12 这个数字。
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
上面例子以 private 的方式声明 number 变量,这使得 number 仅在 TwelveOrLess 的实现中使用。写在其他地方的代码通过使用 wrappedValue 的 getter 和 setter 来获取这个值,但不能直接使用 number。有关 private 的更多信息
使用该属性包装器
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// 打印 "0"
rectangle.height = 10
print(rectangle.height)
// 打印 "10"
rectangle.height = 24
print(rectangle.height)
// 打印 "12"
等价于(不使用注解的方式)
struct SmallRectangle {
private var _height = TwelveOrLess()
private var _width = TwelveOrLess()
var height: Int {
get { return _height.wrappedValue }
set { _height.wrappedValue = newValue }
}
var width: Int {
get { return _width.wrappedValue }
set { _width.wrappedValue = newValue }
}
}
属性包装器的初始化
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
① 使用 init()
构造函数对属性构造器进行初始化
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
② 使用 init(wrappedValue:)
构造函数
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// 打印 "1 1"
③ 使用 init(wrappedValue: Int, maximum: Int)
构造函数
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// 打印 "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// 打印 "12"
属性包装器中的呈现值
除了被包装值,属性包装器可以通过定义被呈现值暴露出其他功能。
@propertyWrapper
struct SmallNumber {
private var number: Int
// 该变量可以反应当前属性是否发生改变
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
// 当前属性有改变过
projectedValue = true
} else {
number = newValue
// 当前属性未发生改变
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
enum Size {
case small, large
}
struct SizedRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
mutating func resize(to size: Size) -> Bool {
switch size {
case .small:
height = 10
width = 20
case .large:
height = 100
width = 100
}
// 返回语句检查 $height 和 $width 来确认是否属性包装器调整过 height 或 width。
return $height || $width
}
}
下标语法
subscript(index: Int) -> Int {
get {
// 返回一个适当的 Int 类型的值
}
set(newValue) {
// 执行适当的赋值操作
}
}
只读下标(省略 get 关联字和大括号)
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
类型下标(可理解为类的静态方法是下标方法)
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
print(mars)
KeyPath
struct User {
let name: String
}
let users = [
User(name: "John"),
User(name: "Alice"),
User(name: "Bob")
]
// 单纯闭包
let userNames1 = users.map { $0.name }
// keyPath
let userNames2 = users.map(\.name)
// 等价于
let userNames3 = users.map { $0[keyPath: \.name] }
构造方法
结构体的逐一成员构造器
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
无参构造器
// 如果所有属性都有默认值,则自动有一个无参构造器
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
代理构造器
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
// 因为值类型(结构体与枚举类)没有继承,只能代理自己的构造器,而无法继承父类构造器
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
类的必要构造器
class SomeClass {
// 在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器
required init() {
// 构造器的实现代码
}
}
技巧
构造方法设置参数默认值,这样使用的时候可跳过传递该参数
struct User {
// 通过 actions 中的 generate memberwise initializer 快速生成
internal init(firstName: String, middleName: String? = nil, lastName: String) {
self.firstName = firstName
self.middleName = middleName
self.lastName = lastName
}
let firstName: String
let middleName: String?
let lastName: String
}
添加新构造器,同时保留默认的成员初始化构造器
struct User {
let firstName: String
let middleName: String?
let lastName: String
}
extension User {
// 如果在 struct 直接声明构造器,则默认成员初始化构造器将被替代,在 extension 中则不会
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
self.middleName = nil
}
}
// 使用示例
var u = User(firstName: "", middleName: "", lastName: "")
var u2 = User(firstName: "", lastName: "")
通过闭包设置成员属性的默认值
class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
// 注:闭包结尾的花括号后面接了一对空的小括号
}
扩展
使用 extension
来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展让某个在别处声明的类型来遵守某个协议,这同样适用于从外部库或者框架引入的类型
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
为具有特定类型的实现的类型添加功能(或默认实现)
protocol P {
func foo()
}
extension P where Self: UIViewController {
func foo() {
print("You can reference UIViewController property here: \(view)")
}
}
错误处理
① do...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)
}
注:
在 do
代码块中,使用 try
来标记可以抛出错误的代码。在 catch
代码块中,除非你另外命名,否则错误会自动命名为 error
。
② 错误断言,出现异常直接 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)
泛型
// 重新实现 Swift 标准库中的可选类型
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
注:这里 .none 是因为父类型已经给出声明,故省略。
泛型限定
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])
注:
在类型名后面使用 where
来指定对类型的一系列需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
注2:
<T: Equatable>
和 <T> ... where T: Equatable
的写法是等价的。
不透明类型
一个不透明类型只能对应一个具体的类型,即便函数调用者并不能知道是哪一种类型;协议类型可以同时对应多个类型,只要它们都遵循同一协议。总的来说,协议类型更具灵活性,底层类型可以存储更多样的值,而不透明类型对这些底层类型有更强的限定。
不透明类型可以使用类型推断,在关联类型中常用到,而协议类型不行(因为无法确定具体类型)。