Swift一览

Swift5一览

1.运算符

Swift的Int类型不在支持自增减运算符,比如 ++a, -—a,a-—,a++

要支持这种方法,须重载运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
extension Int {
//前缀++
static prefix func ++(num:inout Int) -> Int {
num += 1
return num
}
//后缀++
static postfix func ++(num:inout Int) -> Int {
let temp = num
num += 1
return temp
}
//前缀--
static prefix func --(num:inout Int) -> Int {
num -= 1
return num
}
//后缀--
static postfix func --(num:inout Int) -> Int {
let temp = num
num -= 1
return temp
}
}

var aInt = 0
var b = aInt++
print("aInt:\(aInt),b:\(b)") //输出:aInt:1,b:0

var c = ++aInt
print("aInt:\(aInt),c:\(c)") //输出:aInt:2,c:2

使用自增运算符:

1
2
3
var a = 1
a += 2
// a is now equal to 3

这里a += 2a = a + 2的简写形式。

2.String

  • String.Index

表示字符在字符串中的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a
greeting[greeting.endIndex] // 报错:越界
greeting.index(after: greeting.endIndex) // 报错:越界

for index in greeting.indices {
print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n T a g ! "
  • 增删字符
1
2
3
4
5
6
7
8
9
10
11
12
13
//增
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome now equals "hello!"
welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there!"

//删
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome now equals "hello"
  • 截取

截取字符串时返回的是Substring类型:

1
2
3
4
5
6
7
let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning is "Hello"

// Convert the result to a String for long-term storage.
let newString = String(beginning)

3.集合

1.Array

Array<Element>表示数组的类型,简写作:[Element]

1
2
3
4
5
var someInts: [Int] = []
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."
someInts.append(3)
// someInts now contains 1 value of type Int

数组的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var shoppingList = ["Eggs", "Milk"]
if shoppingList.isEmpty {
print("The shopping list is empty.")
} else {
print("The shopping list isn't empty.")
}
// Prints "The shopping list isn't empty."

// 增
shoppingList.append("Flour")
// shoppingList now contains 3 items, and someone is making pancakes
shoppingList += ["Baking Powder"]
// shoppingList now contains 4 items
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList now contains 7 items

// 读
var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"
// 修改指定索引处的元素
shoppingList[0] = "Six eggs"
// the first item in the list is now equal to "Six eggs" rather than "Eggs"
shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList now contains 6 items

// 插入
shoppingList.insert("Maple Syrup", at: 0)
// shoppingList now contains 7 items
// "Maple Syrup" is now the first item in the list

// 删除
let mapleSyrup = shoppingList.remove(at: 0)
// the item that was at index 0 has just been removed
// shoppingList now contains 6 items, and no Maple Syrup
// the mapleSyrup constant is now equal to the removed "Maple Syrup" string

// 遍历
for item in shoppingList {
print(item)
}
for (index, value) in shoppingList.enumerated() {
print("Item \(index + 1): \(value)")
}

2.Set

Set是无序集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
var letters = Set<Character>()

// 增
letters.insert("a")
// letters now contains 1 value of type Character
// 置空
letters = []
// letters is now an empty set, but is still of type Set<Character>

// 字面量赋值
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// 简写
favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]

Set的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
favoriteGenres.insert("Jazz")
// favoriteGenres now contains 4 items
if let removedGenre = favoriteGenres.remove("Rock") {
print("\(removedGenre)? I'm over it.")
} else {
print("I never much cared for that.")
}
// Prints "Rock? I'm over it."
if favoriteGenres.contains("Funk") {
print("I get up on the good foot.")
} else {
print("It's too funky in here.")
}
// Prints "It's too funky in here."

// 遍历
for genre in favoriteGenres {
print("\(genre)")
}

3.Dictionary

Dictionary<Key, Value>表示字典的类型,简写作:[Key: Value]

1
2
3
4
5
6
7
8
9
// 创建空字典
var namesOfIntegers: [Int: String] = [:]
namesOfIntegers[16] = "sixteen"
namesOfIntegers = [:]

// 创建字典并赋值
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
// 简写形式
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

字典的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 更新
airports["LHR"] = "London Heathrow"
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
print("The old value for DUB was \(oldValue).")
}
// Prints "The old value for DUB was Dublin."

// 取值
if let airportName = airports["DUB"] {
print("The name of the airport is \(airportName).")
} else {
print("That airport isn't in the airports dictionary.")
}
// Prints "The name of the airport is Dublin Airport."

// 删除
if let removedValue = airports.removeValue(forKey: "DUB") {
print("The removed airport's name is \(removedValue).")
} else {
print("The airports dictionary doesn't contain a value for DUB.")
}
// Prints "The removed airport's name is Dublin Airport."

// 遍历字典
for (airportCode, airportName) in airports {
print("\(airportCode): \(airportName)")
}
// 遍历键
for airportCode in airports.keys {
print("Airport code: \(airportCode)")
}
// 遍历值
for airportName in airports.values {
print("Airport name: \(airportName)")
}
// 取所有键
let airportCodes = [String](airports.keys)
// 取所有值
let airportNames = [String](airports.values)

4.枚举

定义和使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
enum CompassPoint {
case north
case south
case east
case west
}

var directionToHead = CompassPoint.west
directionToHead = .east

directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// Prints "Watch out for penguins"
  • 遍历枚举
1
2
3
4
5
6
7
8
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
// 遍历
for beverage in Beverage.allCases {
print(beverage)
}
  • 原始值

原始值可以是字符串、字符、或者任何整型值或浮点型值。

1
2
3
4
5
6
7
enum Rank: Int { // 默认从0开始,往后递增。
case ace = 1 // 自定义case对应的值
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
}
let ace = Rank.ace
let aceRawValue = ace.rawValue

Int类型的枚举默认从0开始,往后递增。手动指定某个case的值后,其后面的枚举值也是递增。

1
2
3
4
5
enum CompassPoint: String {
case north, south, east, west
}
let direction = CompassPoint.north.rawValue // "north"
let south = CompassPoint(rawValue: "south") // "south"

CompassPoint.south隐式的原始值是字符串”south”。

  • 相关值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 .result(let sunrise, let 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."

5.属性

1.存储属性

1
2
3
4
5
6
7
8
9
struct Number
{
var digits: Int
let pi = 3.1415
}

var n = Number(digits: 12345)
n.digits = 67
n.pi = 3.1 // 报错,pi是常量,不能修改

2.计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct TextBool {
// 1.存储属性
var aInt:Int
// 2.计算属性
var text:String {
get {
if aInt == 1 {
return "true"
}else{
return "false"
}
}
set {
if newValue == "true" {
aInt = 1;
}else{
aInt = 0;
}
}
}
// 3.只读属性
var readOnlyText:String {
// 只有一个表达式时,可隐式返回表达式的值
get{
aInt == 1 ? "true" : "false"
}
}
}
var aBool = TextBool(aInt: 0)
aBool.text = "true"
print(aBool.aInt) // 打印:"1"
print(aBool.readOnlyText) // 打印:“true”

只有get没有set的计算属性就是只读属性。

get函数中只有一个表达式时,会隐式返回它的值(即省略return关键字)。

3.延迟存储属性

第一次被调用时才会计算其初始值的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct TextBool {
init(){
print("Init TextBool")
aInt = 0;
}
// 存储属性
var aInt:Int
}

struct Number
{
var digits: Int
let pi = 3.1415
// 1.延迟属性
lazy var aBool = TextBool()
// 2.首次赋值使用默认语句
var onceInt:Int = {
print("init Int at first call")
return 3
}()
// 3.延迟+默认赋值语句
lazy var onceDouble:Double = {
print("init lazy Double at first call")
return Double(pi+1)
}()
}

var n = Number(digits: 1)
n.digits = 2
print("\(n.digits)")
print("This is \(n.aBool)")
print(n.onceInt)
print(n.onceDouble)

打印日志:

1
2
3
4
5
6
7
init Int at first call
2
Init TextBool
This is TextBool(aInt: 0)
3
init lazy Double at first call
4.141500000000001

创建aBool实例实会先执行其init()函数并打印日志,而日志中2先于Init TextBool打印,说明创建n实例后并未立即创建aBool实例。只有调用print(n.aBool)时第一次访问了aBool实例,此时aBool才真正被初始化。

={}()用来在第一次访问属性时使用括号内的默认语句给其赋值。

4.观察器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct TextBool {
// 存储属性
var aInt:Int {
// 属性观察器
willSet {
print("willSet:\(newValue)");
}
didSet{
print("didSet:\(oldValue)");
}
}
}

var aBool = TextBool(aInt: 0)
aBool.aInt = 1

打印日志:

1
2
willSet:1
didSet:0

初始化方法中设置属性,以及在 willSet 和 didSet 中再次设置属性,都不会再次触发属性观察器。

5.包装器

作用:将属性的定义和管理代码封装在一起。它可以让我们抽象出属性的某些行为,比如线程安全性、延迟初始化等。当需要给多个属性定义同样的行为时,使用包装器可以省去大量重复代码,让我们的代码更加简洁和易于理解。

示例1:属性值不能 > 12

  • 定义包装器
1
2
3
4
5
6
7
8
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) } // 属性值不能>12
}
}

属性封装器中必须声明一个wrappedvalue属性,用来表示真正待封装属性。待封装属性可能是height,也可能是width,但在封装器中均用wrappedvalue来表示。

  • 使用包装器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12" // 24大于12,所以取二者中的最小值12

使用时以@+封装器的名字修饰变量;编译器会按照上面的定义,自动合成此属性的setter 和 getter,因此我们省去了这部分任务,这正是属性封装器的精髓,尤其是类似的属性很多时,这个语法糖的优势很明显。

示例2:属性值发生变化时页面同步更新

1
2
3
4
5
6
7
8
9
10
11
12
class User: ObservableObject {
@Published var name: String
// ...
}

struct ContentView: View {
@ObservedObject var user: User

var body: some View {
Text("Hello, \(user.name)!")
}
}

这是SwiftUI中的@Published@ObservedObject,它们也都是属性包装器。

user.name的值发生变化时,ContentView 会自动更新,显示新的名字。

6.修饰符

1.强弱

Swift 里属性默认是强类型的,OC 中strong属性在 Swift 中会转换为存储属性

Swift 中weak对应 OC 中的weak,仅能修饰引用类型,不能修饰StringInt等值类型属性;

weak修饰的属性必须是 optional 对象类型,否则会报错“’weak’ variable should have optional type ‘xxClass?’”。

1
weak var ani:Animal?

weak 一般用来解决对象间或闭包属性与其所属对象之间的循环引用问题。

2.读写

Swift 没有 OC 中的readwritereadonly关键字。

对于存储属性,使用let表明只读;使用var表明可读/可写。

对于计算属性,提供一个 getter 使其可读,提供 setter 使其可写;只有 getter 没有 setter,则属性只读;计算属性不能只提供 setter 不提供 getter,否则会报错。

1
2
3
4
5
6
7
8
9
class A {
var aInt: Int {
get {
return 1
}
set {
}
}
}
3.原子性

OC 中的atomicnonatomic在 Swift 中没有对应的修饰符,Swift 中属性默认是nonatomic的,可以通过 OC 中类似的锁机制来保证属性对象的线程安全。

4.拷贝

在Swift中,OC的copy被转换为@NSCopying属性。这一类属性须遵守 NSCopying协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Book: NSCopying {
var name: String?
// 实现copy协议方法
func copy(with zone: NSZone? = nil) -> Any {
let copy = Book()
copy.name = name
return copy
}
}

class BooksShelf {
@NSCopying var book: Book?
}

//调用及日志
let book1 = Book()
book1.name = "swift"
let bookShelf = BooksShelf()
bookShelf.book = book1
print(bookShelf.book === book1)//false

6.控制流

1.if else

  • 1.一般用法:
1
2
3
4
5
6
7
8
9
10
11
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
// Prints "11"
  • 2.变量赋值:

可以在=号之后使用ifswitch语句根据条件取变量的值:

1
2
3
4
5
6
7
let scoreDecoration = if teamScore > 10 {
"🎉" //省略了return
} else {
""
}
print("Score:", teamScore, scoreDecoration)
// Prints "Score: 11 🎉"
  • 3.可选绑定
1
2
3
4
5
6
7
8
9
var optionalString: String? = "Hello"
print(optionalString == nil)
// Prints "false"

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName { //不为nil时 值绑定到常量name上
greeting = "Hello, \(name)"
}

可选绑定的简写用法:

1
2
3
4
5
let nickname: String? = nil
if let nickname { // 省略了赋值:if let x = nickName
print("Hey, \(nickname)") // 使用同名变量代替拆包后的值
}
// Doesn't print anything, because nickname is nil.

2.switch

case中支持任何数据类型。

1
2
3
4
5
6
7
8
9
10
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("The first letter of the Latin alphabet")
case "z":
print("The last letter of the Latin alphabet")
default:
print("Some other character")
}
// Prints "The last letter of the Latin alphabet"
  • fallthrough

switch匹配成功后默认不需要手动书写break,会自动退出当前控制流。

fallthrough可以让case之后的语句按顺序继续运行,且不论条件是否满足都会执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var index = 10

switch index {
case 100 :
print( "index 的值为 100")
fallthrough
case 10,15 :
print( "index 的值为 10 或 15")
fallthrough
case 5 :
print( "index 的值为 5")
case 0 :
print( "index 的值为 0")
default :
print( "默认 case")
}
// 输出日志:
// index 的值为 10 或 15
// index 的值为 5
  • 合并case的写法
1
2
3
4
5
6
7
8
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A": // 写在同一行,逗号隔开
print("The letter A")
default:
print("Not the letter A")
}
// Prints "The letter A"
  • 变量赋值
1
2
3
4
5
6
7
8
9
10
11
let aCharacter: Character = "a"
let message = switch aCharacter {
case "a":
"The first letter of the Latin alphabet"
case "z":
"The last letter of the Latin alphabet"
default:
"Some other character"
}
print(message)
// Prints "The first letter of the Latin alphabet"
  • 间隔匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
case 1..<5:
naturalCount = "a few"
case 5..<12:
naturalCount = "several"
case 12..<100:
naturalCount = "dozens of"
case 100..<1000:
naturalCount = "hundreds of"
default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// Prints "There are dozens of moons orbiting Saturn."
  • 支持where语句:
1
2
3
4
5
6
7
8
9
10
11
12
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.")
}
// Prints "Is it a spicy red pepper?"

3.for

for-in遍历集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
// Prints "25"

支持..<右不包含、...左右都包含的区间遍历:

1
2
3
4
5
6
var total = 0
for i in 0..<4 {
total += i
}
print(total)
// Prints "6"

4.while

  • 1.一般用法:
1
2
3
4
5
6
var n = 2
while n < 100 {
n *= 2
}
print(n)
// Prints "128"
  • 2.repeat用法:
1
2
3
4
5
6
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
// Prints "128"

保证循环至少执行一次。

7.函数

  • 1.局部参数名
1
2
3
4
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

personday都是局部参数,在函数体内使用。

  • 2.外部参数名
1
2
3
4
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")

_on即为外部参数名,其中_表示外部参数名可省略,调用时无需书写。

  • 3.inout参数

在函数中定义的参数默认都是常量参数,函数体内不可以修改它的值;

如果想声明一个变量参数,可以在参数定义前加上inout关键字:

1
2
3
4
5
6
7
8
9
10
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
  • 4.参数默认值
1
2
3
4
5
6
func someFunction(a: Int, b: Int = 12) {
// If you omit the second argument when calling this function, then
// the value of b is 12 inside the function body.
}
someFunction(a: 3, b: 6) // b is 6
someFunction(a: 4) // b is 12
  • 5.函数重载

Swift允许函数重载,需保证:函数名相同、参数标签不同、参数类型不同、参数个数不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1.原函数
func samefunc(a:Int){
print("++a:\(a)")
}
// 2.参数标签不同
func samefunc(b:Int){
print("++Int b:\(b)")
}
// 3.参数类型不同
func samefunc(b:Double){
print("++Double b:\(b)")
}
// 4.参数个数不同
func samefunc(a:Int ,b:Double){
print("++a:\(a), b:\(b)")
}
samefunc(a: 0)
samefunc(b: 0)
samefunc(b: 0.0)
samefunc(a: 0, b: 1)

注意:返回值类型与函数重载无关。函数名、参数个数&类型&标签都相同时,无论返回值类型是否相同,调用时都会报错。

1
2
3
4
5
6
// 5.返回值类型无关重载
func samefunc(a:Int) -> Int{
print("++a from (Int)->Int:\(a)")
return a
}
samefunc(a: 0) //报错:Ambiguous use of 'samefunc(a:)'

虽然可以同时定义(a:Int)->void(a:Int)->Int,但调用时会报错。

8.结构体与类

相同点:

  • 定义属性和方法;
  • 使用下标语法subscript syntax
  • 定义初始化器设置初始状态;
  • 添加扩展、实现协议;

class 独有的特性:

  • 继承
  • 类型转换Type casting
  • 析构
  • class 的实例可被多个对象引用

最大的不同:struct 是值类型,class 是引用类型。

值类型在赋值给变量、常量,或者作为函数参数传递时,是值拷贝,对拷贝的修改不影响原对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 枚举
enum Direction {
case north, south, east, west
mutating func goNorth() {
self = .north
}
}
var d1 = Direction.south
var d2 = d1 // 值拷贝
d2.goNorth()
print(d1) // 输出 south,拷贝的修改不影响原对象
print(d2) // 输出 north

// 结构体
struct Calculator {
var num:Int = 0
mutating func add() {
num += 10
}
}
var aCal = Calculator()
var cal2 = aCal // 值拷贝
cal2.num = 1
print(aCal.num) // 输出0,拷贝的修改不影响原对象
print(cal2.num) // 输出1

// 结构体作为函数的参数
func meth(calculator: inout Calculator) {
calculator.num += 10 //传入的是指针,修改会影响外部
//或者调用 calculator.add()
}

meth(calculator: &aCal) //传递指针
print(aCal.num) // 输出10

结构体单纯的作为参数传入函数体时,是值拷贝;

函数体内修改结构体时,需要使用inout并传入结构体的指针。

注意:函数体内修改结构体时,会影响外部对应的结构体对象。

9.单例

  • 方式1: 静态常量
1
2
3
4
5
6
7
8
9
10
11
12
13
class SingletonClass {
// 声明类变量
static let shared = SingletonClass()
// 声明成私有 防止外界通过SingletonClass()创建实例
private init() {
}
func instanceFunc(){
print("+++call static func~")
}
}

// 调用单例及其方法
SingletonClass.shared.instanceFunc()
  • 方式2: 静态变量+闭包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SingletonClass2 {
// 声明类变量
static var shared: SingletonClass2 = {
let instance = SingletonClass2()
// 进行额外的配置和初始化
return instance
}() // 这里的闭包会在第一次访问 shared 属性时执行,从而保证只有一个实例被创建。

private init() {
// 私有化初始化方法,防止其他地方创建实例
}

func instanceFunc(){
print("+++call static func2~")
}
}

// 调用单例及其方法
SingletonClass2.shared.instanceFunc()
  • 方式3:内部结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SingletonClass3 {
static var shared: SingletonClass3 {
//结构体
struct SingletonStruct {
static let shared = SingletonClass3()
}
return SingletonStruct.shared
}

private init() {
}
func instanceFunc(){
print("+++call static func3~")
}
}

// 调用单例及其方法
SingletonClass3.shared.instanceFunc()
  • 方法4:全局常量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//全局常量,只对当前.swift文件可见,别的文件中创建此单例时看不到此常量
fileprivate let globalVar = SingletonClass4()

class SingletonClass4 {
//静态变量
static var shared: SingletonClass4 {
return globalVar
}

fileprivate init() { }
func instanceFunc(){
print("+++call static func4~")
}
}

// 调用单例及其方法
SingletonClass4.shared.instanceFunc()

四种方式从本质上来说,都是利用了类的静态类属性。

10.构造器

Swift 中类的初始化顺序如下:

  1. 初始化自己的存储属性,必须;
  2. 调用父类初始化方法,如无需第3步,则这一步也可省略;
  3. 修改父类成员变量,可选。

构造器结束前,需要完成本类中所有存储属性的初始化;

子类构造器中,存储属性必须全部初始化,才能调用super构造器,否则会报错;

子类构造器中,需要先完成父类的初始化,才能给从父类中继承的属性赋值,否则会报错;

便利构造器中,不能直接调用super构造器,可调用本类中默认构造器,或者其他便利构造器;

便利构造器中,可以将初始化任务代理给其他构造器,但最终需要调用本类的默认构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Graph {
var corners:Int
var description: String {
return "\(corners) 边"
}
init(corners:Int) {
self.corners = corners // 构造函数结束前 需要完成所有存储属性的初始化
}
}
let rectangle = Graph(corners: 4)
print("矩形: \(rectangle.description)")

class SubGraph: Graph {
var aInt:Int
// 子类初始化函数
init(aInt:Int ,corners:Int) {
self.aInt = aInt // 注意:子类存储属性须全部初始化完成,才能调用super构造器,否则会报错:Property 'self.xxx' not initialized at super.init call
super.init(corners: corners)
self.corners = 5 // 注意:需要先完成父类的初始化,才能在子类中给父类中继承的属性赋值,否则会报错:self' used in property access 'corners' before 'super.init' call
}
// 便利构造函数
convenience init(){
self.init(aInt:1, corners: 5) // 调用本类中默认构造器
}
}
let subGraph = SubGraph() //调用便利函数
print("五角形: \(subGraph.description)")

11.Extension

Swift 的扩展能向一个已有的类、结构体或枚举类型添加新功能:

  • 添加计算型属性和计算型静态属性;
  • 定义实例方法和类型方法;
  • 提供新的构造器;
  • 定义下标;
  • 定义和使用新的嵌套类型;
  • 使一个已有类型符合某个协议;

1.基本语法

1
2
3
extension SomeType {
// 这里添加新功能
}

2.增加计算属性

1
2
3
4
5
6
7
8
extension Int {
var add: Int { return self + 1 }
var sub: Int { return self - 1 }
var mul: Int { return self * 1 }
var div: Int { return self / 1 }
}
let addition = 1.add
print("加法运算:\(addition)")

3.增加新的方法

1
2
3
4
5
6
7
8
9
10
11
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
//调用
3.repetitions {
print("Hello!")
}

注意:扩展可以添加新功能,但不能重写已有的功能:

  • 不能增加父类;
  • 不能覆盖原有的方法;
  • 不能增加存储属性;
  • 不能为已有属性添加观察器;

Swift 的Extension与 OC 的Category都不能改变原类型的内存结构,而增加存储属性会改变内存结构。OC 中可使用关联对象达到给分类增加属性实现存取的效果,Swift 也可以参考这种做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct AssociatedKey {
static var textKey: String = "textKey"
}

extension Int {
public var text: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKey.textKey) as? String
}
set {
objc_setAssociatedObject(self, &AssociatedKey.textKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
var a = 1
a.text = "1" //存值
print(a.text!) //取值

4.现有类适配协议

扩展已有类型,使其能够适配一个或多个协议:

1
2
3
4
// 注意,冒号`:`后面只能使用`协议`,不能是某个`类`。
extension SomeType: SomeProtocol, AnotherProtocol {
// 协议的实现写到这里
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol Calculator {
var add: Int { get }
var sub: Int { get }
var mul: Int { get }
var div: Int { get }

}
extension Int: Calculator {
var add: Int { return self + 100 }
var sub: Int { return self - 10 }
var mul: Int { return self * 10 }
var div: Int { return self / 5 }
}
let addition = 3.add
print("加法运算后的值:\(addition)")

5.协议方法默认实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol IOS {
func run()
}
extension IOS {
func run(){
print("run on \(self)")
}
}
class iPhone: IOS{
// 给协议方法提供默认实现后,这里就可以不重写协议方法了
// func run() {
// print("run on \(self)")
// }
}
let iphoneX = iPhone()
iphoneX.run()

默认情况下,iPhone类中需要重写run()协议方法并提供自己的实现,否则编译时会报错。通过extensionIOS协议中的run()提供默认实现后,iPhone类中可以不再重写此协议方法,调用时会使用协议扩展中的默认实现。

12.Protocols

协议规定了实现特定功能所必需的方法和属性。类、结构体、枚举均可遵循协议并提供具体实现。

1.语法

1
2
3
4
5
6
7
8
9
// 定义协议
protocol SomeProtocol {
// protocol definition goes here
}

// 实现超类与协议
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// 这里提供协议的具体实现
}

2.属性

协议中可指定实例属性或类属性,不用指定是存储型属性还是计算型属性。

协议中必须指明属性是只读的还是可读可写的:

协议中指定为可读可写的属性,实现中必须也是可读可写;

协议中指定为只读的属性,实现中可以保持只读也可以可读可写,只要满足协议中的要求即可。

1
2
3
4
5
protocol SomeProtocol {
var mustBeSettable: Int { get set } // 可读可写
var doesNotNeedToBeSettable: Int { get } // 只读
static var someTypeProperty: Int { get set } // 类型属性
}

3.方法

协议中可以定义实例方法或类型方法:

1
2
3
4
protocol SomeProtocol {
func someInstanceMethod()
static func someTypeMethod()
}

4.构造器

协议可以要求它的遵循者实现指定的构造器。

在遵循该协议的类中实现构造器时,可以指定其为类的指定构造器或者便利构造器。两种情况下,都必须给构造器的实现标上required修饰符:

示例1:实现中声明为指定构造器

1
2
3
4
5
6
7
8
protocol SomeProtocol {
init(someParameter: Int)
}
class SomeClass: SomeProtocol {
required init(someParameter: Int) { // 指定构造器
// 这里是构造器的具体实现
}
}

示例2:实现中声明为便利构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol SomeProtocol {
init(someParameter: Int)
}
class SomeClass: SomeProtocol {
init() {
print("designed init")
}
required convenience init(someParameter: Int) { // 便利构造器
print("convenience init")
self.init()
}
}
var inst = SomeClass(someParameter: 0)

如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示requiredoverride修饰符:

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// initializer implementation goes here
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
required override init() { // 这里 同时提供两个标注
// initializer implementation goes here
}
}

5.用作代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SomeController: UIViewController, UITableViewDelegate {

var tableview: UITableView

required init?(coder: NSCoder) {
tableview = UITableView.init()
super.init(coder: coder)
}

override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self // 设置代理
}

// 实现代理方法
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}

6.类的扩展

扩展现有类,使其遵循某个协议,即使你无法访问该类的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol Calculator {
var add: Int { get }
var sub: Int { get }
var mul: Int { get }
var div: Int { get }

}
extension Int: Calculator {
var add: Int { return self + 100 }
var sub: Int { return self - 10 }
var mul: Int { return self * 10 }
var div: Int { return self / 5 }
}
let addition = 3.add
print("加法运算后的值:\(addition)")

7.where与协议

示例:数组中元素须遵循TextRepresentable协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protocol Textable {
var inText: String { get }
}
struct Dice {
var sides: Int
}
extension Dice: Textable {
var inText: String {
return "A \(sides)-sided dice"
}
}
extension Array: Textable where Element: Textable {
var inText: String {
let itemsAsText = self.map { $0.inText }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
let d6 = Dice(sides: 6)
let d12 = Dice(sides: 12)
let myDice = [d6, d12]
print(myDice.inText)

8.协议的继承

协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的要求。

1
2
3
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 协议定义
}

9.类专属协议

定义一个只有可以实现的协议,需要在协议的继承列表中加入AnyObject协议。

1
2
3
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// class-only protocol definition goes here
}

这样枚举、结构体就不能实现此协议了。

10.合成协议

一个类型可以同时遵循多个协议,通过协议1 & 协议2 & 协议3的形式将多个协议合成一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) { // 合成协议
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"

11.可选协议

Swift可以像OC一样,在协议中定义一些不需要实现类必须满足的属性或方法,这些属性或方法须标记为optional,同时这些可选属性或方法以及协议本身,都需要标记为@objc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@objc protocol SomeProtocol {
func someFunction() // 必选方法
@objc optional func someOptionalFunction() // 可选方法
@objc optional func someOptionalFunctionWithParm(p:Int) // 可选带参方法
@objc optional var someTypeProperty: Int { get set } // 可选属性
}
class SomeClass : SomeProtocol {
var someTypeProperty: Int = 1 // 实现可选属性
func someFunction() { // 实现必选方法
print("someFunction()")
}
}
var delegate: SomeProtocol? = SomeClass()
delegate?.someFunction()
delegate?.someOptionalFunction?()
delegate?.someOptionalFunctionWithParm?(p: 1)
print(delegate?.someTypeProperty ?? 100)

需要注意的是:

与OC不同的是,Swift中每个非必须实现的协议属性或方法前,必须分别标注上optional

因为@objc只能用于Class类型,所以只有Class才能实现这种协议,结构体和枚举都不行;

在协议中使用optional属性或方法时,它们的类型会自动变成可选类型。例如(Int) -> String函数类型会变成((Int) -> String)?可选类型。

12.扩展协议

通过extension可以给协议增加方法、计算属性、下标、构造器,并且可以给方法、计算属性添加默认实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protocol SomeProtocol {
func someFunction()
var someTypeProperty: Int { get set }
}
extension SomeProtocol {
var someTypeProperty: Int { get { 1 } } // 扩展已有属性,默认返回1
var someTypeProperty2: Int { 2 } // 新增计算属性,默认返回2
func someFunction(){
print("default implementation") // 给协议方法添加默认实现
}
}
class SomeClass : SomeProtocol {
var someTypeProperty: Int = 0 // 实现协议属性并赋值
func someFunction() { // 实现协议方法并重写具体实现
print("this is new imp for someFunction()")
}
}
var delegate: SomeProtocol? = SomeClass()
delegate?.someFunction()
print(delegate?.someTypeProperty ?? 3)
print(delegate?.someTypeProperty2 ?? 4)

13.相等

Note that identical to (represented by three equals signs, or ===) doesn’t mean the same thing as equal to (represented by two equals signs, or ==). Identical to means that two constants or variables of class type refer to exactly the same class instance. Equal to means that two instances are considered equal or equivalent in value, for some appropriate meaning of equal, as defined by the type’s designer.

When you define your own custom structures and classes, it’s your responsibility to decide what qualifies as two instances being equal.

  • ===表示两个实例引用了相同的对象,强调“引用”相同;
  • == 表示两个实例中的值“相等”,至于哪些值相等,需要自己在类中定义;

自定义类时,实现Equatable协议并重写全局操作符==来决定两个操作数是否相等。

#示例:

1
2
3
4
5
6
7
8
9
10
11
12
class Cat:Equatable {

var name: String?

static func ==(lhs:Cat, rhs:Cat) -> Bool {
if lhs.name == rhs.name{
return true
}else{
return false
}
}
}

调用并打印结果:

1
2
3
4
5
6
let cat1 = Cat()
let cat2 = Cat()
cat1.name = "Miao"
cat2.name = "Miao"

print(cat1 == cat2) //true

注意,===的左右两个操作数必须是类的实例,对枚举或结构体及其变种(String、Array、Dictionary)等使用时会报错。

14.final、static、class

1.final

修饰类及其元素,强调不能被继承和重写。

  • final 只能修饰类和类中的元素,不能修饰值类型的结构体和枚举,它们本身就不能被继承;
  • final 修饰的方法、属性、下标不能被重写;
  • final 修饰整个类时此类不能被继承,其中的元素也将被标记为 final,因此不能重写;

#示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final class A {
var aInt: Int{
get{
return 1
}
}
func callFunction() {
print("A")
}
}
class B:A { // 报错“Inheritance from a final class 'A'”
override var aInt: Int { // 报错“Property overrides a 'final' property”
get {
return 2
}
}
override func callFunction() { //报错“Instance method overrides a 'final' instance method”
print("B")
}
}

final修饰类的属性、方法时只是将其标记为不能重写,并不会将其变成类属性或类方法。

2.static

修饰类中元素,强调属于类、不可重写。

  • static 可以修饰类中的元素,但不能修饰类本身;
  • static 可以修饰结构体中的元素,但不能修饰结构体本身;
  • static 不能修饰枚举及其元素;
  • static 可以修饰存储属性;
  • static 修饰的属性为静态属性,修饰的方法为静态方法,不能重写,需通过类型名来调用;

#示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
static var aInt: Int = 1
static func callFunction() {
print("A.aInt:\(aInt)")
}
}
class B:A {
var aInt: Int = 2 // 属性前不能加override 否则会报错“Property does not override any property from its superclass”

func callFunction() { // 方法前不能加override 否则会报错“Method does not override any method from its superclass”
print("B.aInt:\(aInt)")
}
}
A.aInt // 1
B.aInt // 1
A.callFunction() // A.aInt:1
let b = B()
b.aInt // 2
b.callFunction() // 打印 B.aInt:2

3.class

修饰类中元素,强调属于类、可重写。

  • class 只能用在类中,不能修饰结构体或枚举及它们的元素;
  • class 修饰的属性和方法,可以被重写;
  • class 不能修饰存储属性;
  • class 修饰的属性是类属性,修饰的方法是类方法,可以通过“类名.xx”直接调用;
  • class 可以修饰计算属性,不能修饰存储属性,因为存储属性属于类的实例;

#示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A {
class var aInt: Int {
get {
return 1
}
set {
}
}
class func callFunction() {
print("A")
}
}
class B:A {
override class var aInt: Int {
get {
return 2
}
set {
}
}
override class func callFunction() {
print("B")
}
}
A.aInt // 打印:1
B.aInt // 打印:2
let b = B()
B.callFunction() // 打印:B

15.访问控制符

五种访问修饰符,按从高到低排序是:open > public > interal > fileprivate > private。

  • open:可以被任何模块的代码访问,可以被继承和重写。
  • public: 可以被任何模块的代码访问,模块外不可继承和重写。
  • internal:默认访问级别,源代码所在的整个模块都可以访问。
  • fileprivate:只能在当前文件中访问,当前类的扩展中也可以。
  • private:只能在当前类与其扩展中访问,子类中无法访问。

16.API可用性

#available+if

1
2
3
4
5
if #available(iOS 10, macOS 10.12, *) {
// Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
// Fall back to earlier iOS and macOS APIs
}

#available+guard

1
2
3
4
5
6
7
8
9
10
11
12
@available(macOS 10.12, *)
struct ColorPreference {
var bestColor = "blue"
}

func chooseBestColor() -> String {
guard #available(macOS 10.12, *) else {
return "gray"
}
let colors = ColorPreference()
return colors.bestColor
}

unavailable反向检查:

1
2
3
4
5
6
7
8
if #available(iOS 10, *) {
} else {
// Fallback code
}

if #unavailable(iOS 10) {
// Fallback code
}

第一个 else 语句与第二个的unavailable是等效的。

17.guard

guard是一种早退出机制,其布尔值须为true才能继续执行后续的代码。

guard搭配else语句,如果guard条件为 false,则执行else内的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}

print("Hello \(name)!")

guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}

print("I hope the weather is nice in \(location).")
}

greet(person: ["name": "John"])
// Prints "Hello John!"
// Prints "I hope the weather is nice near you."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino."

18.defer

defer用于在离开当前代码块执行一系列语句,如关闭数据库、清理对象等。

当前代码块存在多个defer语句时,defer语句会按照定义时的顺序从后往前执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func aDefer() {
print("++第一行")
defer {
print("++defer 1")
}
defer {
print("++defer 2")
}
// print("++测试defer是否中断")
// if true {
// return
// }
defer {
print("++defer 3")
}
print("++要结束咯")
}
//调用
aDefer()

/*
打印结果:
++第一行
++要结束咯
++defer 3
++defer 2
++defer 1
*/

defer语句可以被return,error等打断,即执行到defer语句前如果代码已经返回,那么defer将不会被执行。可将上面例子中的注释语句打开并重新运行看看结果:

1
2
3
4
5
打印日志:
++第一行
++测试defer是否中断
++defer 2
++defer 1

即第三个defer前代码块被return,则后续defer及”print(“++要结束咯”)“均未执行。

19.typealias

作用:给已有类型重新定义名称,方便代码阅读。

1
2
3
4
5
6
//eg:1.已有类型的重新命名
typealias Address = CGPoint

let point: CGPoint = CGPoint(x: 0,y: 0)
//等价于
let point: Address = CGPoint(x: 0,y: 0)

常见应用场景:定义闭包,类似oc的 block 定义。

1
2
3
typealias successBlock = (_ code: Int, _ message: String) -> Void
var callBack: successBlock?
self.callBack!(code: 200, message: "ok")

20.泛型

泛型是一种类型参数,是指定并命名一个参数的类型的占位符。在定义类、结构体中的元素或函数中的参数、返回值时,对于类型暂时不确定的,只在调用时才能确定具体类型的,可以引入泛型。

1.语法

<T>形式声明泛型,尖括号中可以有多个泛型命名,以逗号隔开。

  • 用在函数参数中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3


var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
  • 用在自定义的类型中:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
// 使用
var aStack = Stack<String>() // 指定具体类型
aStack.push("uno")
let fromTheTop = aStack.pop()

数组、字典也都是泛型集合,是泛型的典型应用:

  • Array<Element>
  • Dictionary<Key, Value>

这里的ElementKeyValue都用来表示某种数据类型。

2.扩展泛型

可以通过extension给现有泛型增加新的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}

extension Stack {
var topItem: Element? { // 新增的计算属性
return items.isEmpty ? nil : items[items.count - 1]
}
}

var aStack = Stack<String>()
aStack.push("google")
aStack.push("runoob")

if let topItem = aStack.topItem {
print("栈顶元素:\(topItem).")
}

print(aStack.items)

3.类型约束

给泛型设置约束,限制其可使用的类型,如必须继承自某个类或遵循某个协议。

1
2
3
4
5
6
7
8
9
10
11
12
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2

4.associatedtype

  • 语法

协议中不支持<T>这种方式定义泛型,使用associatedtype关键字定义某种泛型,在协议的实现类中才指明此泛型具体是何类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protocol Container {
associatedtype Item //这个Item就是泛型,可现在协议中用着
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
// IntStack 类的自有部分
var items: [Int] = []
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// 实现 Container 协议的部分
typealias Item = Int //这里指明Item是Int类型
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
  • 约束关联类型
1
2
3
4
5
6
protocol Container {
associatedtype Item: Equatable // 这里约束泛型须遵循Equatable协议
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}

5.where语句

  • 泛型类型定义中的where
1
struct Stack<Element> where Element:Equatable {}
  • extension中的where
1
2
3
4
5
6
7
8
9
10
extension Stack where Element:Equatable {
func isItemInStack(item:Element)->Bool {
for stackItem in items {
if stackItem == item { //比较大小须遵守Equatable协议
return true
}
}
return false
}
}
  • 参数列表中的where
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
protocol Container {
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}

struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}

extension Array: Container {}

func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
if someContainer.count != anotherContainer.count {
return false
}
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
return true
}
var tos = Stack<String>()
tos.push("google")
tos.push("baidu")
tos.push("microsoft")

var aos = ["google", "baidu", "microsoft"]

if allItemsMatch(tos, aos) {
print("匹配所有元素")
} else {
print("元素不匹配")
}

21.some

1.作用

在标注属性、下标、函数返回值的类型时,对内保存返回值的类型信息,对外隐藏类型信息。

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol IOS {
func run()
}
class iPhone: IOS {
func run() {
print("run on iPhone")
}
}
class iPad: IOS {
func run() {
print("run on iPad")
}
}
func whoCalls() -> some IOS { // 这里 some 关键字标注的就是不透明类型
return iPad()
}

不透明类型,以some+协议配合使用,通常用作函数的返回值类型:当函数返回不透明类型时,其返回值类型不使用具体的类型,而是用它支持的协议来描述。

上面的示例中,whoCalls()返回的就是不透明类型some IOS。调用者并不关心函数返回值的真实类型是啥,也不关心函数内部的具体实现,只需知道返回值是个遵循IOS协议的类型。

2.对比泛型

作为函数返回值类型时:

泛型:让函数的调用者来选择一种返回值的具体类型;

不透明类型:让函数体来选择一种返回值的具体类型;

示例2:

1
func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }

这个泛型场景中,max()函数的返回值类型是根据调用者传进来的类型而定的。调用者传入xy的值,它们的类型决定了T的具体类型,函数内部仅仅使用了Comparable协议定义的通用功能。

1
2
3
func whoCalls() -> some IOS {
return iPad()
}

示例1中,函数体内通过return iPad()指定了返回值的具体类型是iPad,而且通过some IOS不透明类型,对外隐藏了返回值的具体类型,调用者只知道该返回值遵循了IOS协议。

所以,作为函数返回值时,不透明类型与泛型的作用正好相反~

3.对比协议

作为函数返回值类型时:

协议:可以是所有遵循了此协议的对象;

不透明类型:只能是某一种遵循了此协议的对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
protocol IOS {
func run()
}
class iPhone: IOS {
func run() {
print("run on iPhone")
}
}
class iPad: IOS {
func run() {
print("run on iPad")
}
}
enum Device {
case iPhone
case iPad
}
//报错:Function declares an opaque return type, but the return statements in its body do
func opaqueCalls(type device: Device) -> some IOS {
if device == .iPhone { // if else 分支返回的类型不同 会导致报错
return iPhone()
} else {
return iPad()
}
}
// 使用协议
func protoCalls(type device: Device) -> IOS {
if device == .iPhone {
return iPhone()
} else {
return iPad()
}
}
// 使用泛型
func geneticCalls<T: IOS>(type device: T) -> T {
return device
}

// 协议类型返回值
let iphone1 = protoCalls(type: .iPhone)
let iphone2 = protoCalls(type: .iPhone)
iphone1 == iphone2 // 报错
// 泛型返回值
let ipad1 = geneticCalls(type: iPad())
let ipad2 = geneticCalls(type: iPad())
ipad1 == ipad2 // 报错

some返回值的函数opaqueCalls中,使用类似if这样的分支语句返回多种不同类型时会报错。

协议类型返回值的函数protoCalls中,可以返回不同类型的对象,只要遵循IOS协议即可。

所以,不透明类型实际是对内保存了明确类型信息的,比协议的要求更为严格~

另外,对于泛型或协议类型的返回值,不能使用==比较类型。一是因为IOS协议中并未定义==操作符;二是==左右两边的返回值的类型不确定!

4.any

boxed protocol type,用于定义一个类型,该类型须遵循指定的协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
protocol Shape {
func draw() -> String
}
struct VerticalShapes: Shape {
var shapes: [any Shape] // 看这里
func draw() -> String {
return shapes.map { $0.draw() }.joined(separator: "\n\n")
}
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result: [String] = []
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
}
struct Square: Shape {
var size: Int
func draw() -> String {
let line = String(repeating: "*", count: size)
let result = Array<String>(repeating: line, count: size)
return result.joined(separator: "\n")
}
}
let largeTriangle = Triangle(size: 5)
let largeSquare = Square(size: 5)
let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
print(vertical.draw())

数组shapes中的元素可以是不同类型,但都必须遵循Shape协议。

someany的区别在于是否保存明确的类型信息。

  • some必须指定某一个具体类型;
  • any可以是任何类型,只要遵循某个协议即可;

相关参考:

#©Swift翻译组


Swift一览
https://davidlii.cn/2018/08/12/swift-basic.html
作者
Davidli
发布于
2018年8月12日
许可协议