Swift 基础

XCode 打开项目后,左上角 File 菜单栏,New、PlayGround

在 PlayGround 里学习 Demo

import UIKit

var greeting = "Hello, playground"
print(greeting)

import

可以使用 import 语句来引入任何的 Objective-C 框架(或 C 库)到 Swift 程序中。

注释

Swift支持单行注释和多行注释,注释用于解释代码的功能和逻辑。Swift 的注释与 C 语言极其相似,单行注释以两个反斜线开头:

import UIKit

var greeting = "Hello, playground"
print(greeting)

// 单行注释
/* 多行注释
 滴水穿石*/

分号

与C、C++不同的是,Swift 不要求在每行语句的结尾使用分号 ;,但当你在同一行书写多条语句时,必须用分号隔开:

// 一般情况下不需要分号
let x = 10
let y = 20

// 在同一行写多个语句时需要分号
let a = 1; let b = 2; let c = 3

// 在某些复杂表达式中可以提高可读性
let result = calculate(); process(result)

let 与 var

常量 let

// 声明常量
let maximumNumberOfLoginAttempts = 10
let welcomeMessage = "欢迎使用Swift"

// 常量一旦赋值就不能更改
// maximumNumberOfLoginAttempts = 5  // 错误!不能修改常量

// 延迟初始化常量
let username: String
if userIsLoggedIn {
    username = "用户A"
} else {
    username = "访客"
}
// username 只能赋值一次

变量

// 声明变量
var currentLoginAttempt = 0
var friendlyWelcome = "Hello!"

// 变量可以修改
currentLoginAttempt = 1
friendlyWelcome = "Bonjour!"

// 变量可以多次赋值
var temperature = 25.0
temperature = 30.0
temperature = 22.5

空格

Swift语言并不是像C/C++,Java那样完全忽视空格,Swift对空格的使用有一定的要求,但是又不像Python对缩进的要求那么严格。

在Swift中,运算符不能直接跟在变量或常量的后面。例如下面的代码会报错:

let a= 1 + 2
// error: prefix/postfix '=' is reserved
let a = 1 + 2;  // 编码规范推荐使用这种写法
let b = 3+4 // 这样也是OK的

字面量

所谓字面量,就是指像特定的数字,字符串或者是布尔值这样,能够直接了当地指出自己的类型并为变量进行赋值的值。比如在下面:

42                 // 整型字面量
3.14159            // 浮点型字面量
"Hello, world!"    // 字符串型字面量
true               // 布尔型字面量

Swift 使用 print 函数打印输出:

print("Runoob") // 输出 Runoob

print函数是一个全局函数,完整的函数签名为

public func print(items: Any..., separator: String = default, terminator: String = default)

如果我们想让其不换行输出,只需要将最后一个参数赋值为空字符串即可:

for x in 0...10{
    print("\(x) ", terminator: "")
}
print()

如果你需要接收用户的输入可以使用 readLine():

let theInput = readLine()

数据类型

Int

一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型Int,长度与当前平台的原生字长相同:

除非你需要特定长度的整数,一般来说使用Int就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,Int可以存储的整数范围也可以达到-2,147,483,648~2,147,483,647,大多数时候这已经足够大了。

Int8, Int16, Int32, Int64 分别表示 8 位, 16 位, 32 位, 和 64 位的有符号整数形式。

UInt

Swift 也提供了一个特殊的无符号类型UInt,长度与当前平台的原生字长相同:

UInt8, UInt16, UInt32, UInt64 分别表示 8 位, 16 位, 32 位 和 64 位的无符号整数形式。

浮点数

浮点数是有小数部分的数字,比如 3.14159,0.1 和 -273.15。

浮点类型比整数类型表示的范围更大,可以存储比 Int 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:

布尔值

Swift 有一个基本的布尔(Boolean)类型,叫做 Bool。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,true 和 false。

字符串

字符串是字符的序列集合,例如:

let str: String = "Hello, World!"

字符

字符指的是单个字母,例如:

let ch: Character = "H"

可选类型 Optional

使用可选类型来处理值可能缺失的情况。可选类型表示有值或没有值。底层是枚举实现。

类型别名

类型别名对当前的类型定义了另一个名字,类型别名通过使用 typealias 关键字来定义。语法格式如下:

typealias newname = type
// 以下定义了 Int 的类型别名为 Feet:
typealias Feet = Int
var distance: Feet = 100
print(distance)

类型推断

let meaningOfLife = 42
// meaningOfLife 会被推测为 Int 类型
let pi = 3.14159
// pi 会被推测为 Double 类型
let anotherPi = 3 + 0.14159
// anotherPi 会被推测为 Double 类型

变量

变量声明

import UIKit

var varA = 42
print(varA)

var varB: Float

varB = 3.14159
print(varB)

变量输出

变量和常量可以使用 print(swift 2 将 print 替换了 println) 函数来输出。

在字符串中可以使用括号与反斜线来插入变量,如下实例:

var name = "gaowanlu"
var site = "https://mfavant.duckdns.org"

print("\(name)的官网地址为:\(site)")

可选变量

Swift 的可选(Optional)类型,用于处理值缺失的情况。可选表示”那儿有一个值,并且它等于 x “或者”那儿没有值”。

Swfit语言定义后缀?作为命名类型Optional的简写,换句话说,以下两种声明是相等的:

var optionalInteger: Int?
var optionalInteger: Optional<Int>

声明可选整数数组,应该写成 (Int[])? 写成 Int[]? 会报错。

声明一个可选变量或者可选属性的时候没有提供初始值,它的值会默认为 nil。

如果一个可选类型的实例包含一个值,你可以用后缀操作符 !来访问这个值,如下所示:

optionalInteger = 42
optionalInteger! // 42

import Cocoa

var myString:String? = nil

if myString != nil {
    print(myString)
}else{
    print("字符串为 nil")
}

强制解析

你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(!)来获取值。这个感叹号表示”我知道这个可选有值,请使用它。“这被称为可选值的强制解析(forced unwrapping)。

import Cocoa

var myString:String?

myString = "Hello, Swift!"

if myString != nil {
    print( myString ) // Optional("Hello, Swift!")
    print( myString! ) // Hello, Swift!
}else{
    print("myString 值为 nil")
}

使用!来获取一个不存在的可选值会导致运行时错误。使用!来强制解析值之前,一定要确定可选包含一个非nil的值。

自动解析

你可以在声明可选变量时使用感叹号(!)替换问号(?)。这样可选变量在使用时就不需要再加一个感叹号(!)来获取值,它会自动解析。

import Cocoa

var myString:String!

myString = "Hello, Swift!"

if myString != nil {
   print(myString)
}else{
   print("myString 值为 nil")
}

// Hello, Swift!

可选绑定 if let

使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在if和while语句中来对可选类型的值进行判断并把值赋给一个常量或者变量。

像下面这样在if语句中写一个可选绑定:

if let constantName = someOptional {
    statements
}

import Cocoa

var myString:String?

myString = "Hello, Swift!"

if let yourString = myString {
   print("你的字符串值为 - \(yourString)")
}else{
   print("你的字符串没有值")
}

运算符

常见的

算术运算符

+、-、*、/、%

比较运算符

==、!=、>、<、>=、<=

逻辑运算符

&&、||、!

位运算符

~ & | ^

赋值运算符

=、+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=

区间运算符

闭区间运算符 (a...b) 定义一个包含从a到b(包括a和b)的所有值的区间,b必须大于等于a。闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 for-in 循环中:

import Cocoa

print("闭区间运算符:")
for index in 1...5 {
    print("\(index) * 5 = \(index * 5)")
}
闭区间运算符:
1 * 5 = 5
2 * 5 = 10
3 * 5 = 15
4 * 5 = 20
5 * 5 = 25

print("半开区间运算符:")
for index in 1..<5 {
    print("\(index) * 5 = \(index * 5)")
}
半开区间运算符:
1 * 5 = 5
2 * 5 = 10
3 * 5 = 15
4 * 5 = 20

一二三元运算符

import Cocoa

var A = 1
var B = 2
var C = true
var D = false
print("-A 的值为:\(-A)")
print("A + B 的值为:\(A + B)")
print("三元运算:\(C ? A : B )")
print("三元运算:\(D ? A : B )")

条件语句

if 语句

if boolean_expression {
   /* 如果布尔表达式为真将执行的语句 */
}
import Cocoa

var varA:Int = 10;

/* 检测条件 */
if varA < 20 {
    /* 如果条件语句为 true 执行以下程序 */
    print("varA 小于 20");
}
print("varA 变量的值为 \(varA)");

if else 语句

if boolean_expression {
   /* 如果布尔表达式为真将执行的语句 */
} else {
   /* 如果布尔表达式为假将执行的语句 */
}
import Cocoa

var varA:Int = 100;

/* 检测布尔条件 */
if varA < 20 {
   /* 如果条件为 true 执行以下语句 */
   print("varA 小于 20");
} else {
   /* 如果条件为 false 执行以下语句 */
   print("varA 大于 20");
}
print("varA 变量的值为 \(varA)");

if else if else 语句

if boolean_expression_1 {
   /* 如果 boolean_expression_1 表达式为 true 则执行该语句 */
} else if boolean_expression_2 {
   /* 如果 boolean_expression_2 表达式为 true 则执行该语句 */
} else if boolean_expression_3 {
   /* 如果 boolean_expression_3 表达式为 true 则执行该语句 */
} else {
   /* 如果以上所有条件表达式都不为 true 则执行该语句 */
}

switch 语句

switch 语句允许测试一个变量等于多个值时的情况。 Swift 语言中只要匹配到 case 语句,则整个 switch 语句执行完成。

switch expression {
   case expression1  :
      statement(s)
      fallthrough /* 可选 */
   case expression2, expression3  :
      statement(s)
      fallthrough /* 可选 */
  
   default : /* 可选 */
      statement(s);
}

一般在 switch 语句中不使用 fallthrough 语句。 这里我们需要注意 case 语句中如果没有使用 fallthrough 语句,则在执行当前的 case 语句后,switch 会终止,控制流将跳转到 switch 语句后的下一行。 如果使用了fallthrough 语句,则会继续执行之后的 case 或 default 语句,不论条件是否满足都会执行。

在大多数语言中,switch 语句块中,case 要紧跟 break,否则 case 之后的语句会顺序运行,而在 Swift 语言中,默认是不会执行下去的,switch 也会终止。如果你想在 Swift 中让 case 之后的语句会按顺序继续运行,则需要使用 fallthrough 语句。

没有 fallthrough 语句

import Cocoa

var index = 10

switch index {
   case 100  :
      print( "index 的值为 100")
   case 10,15  :
      print( "index 的值为 10 或 15")
   case 5  :
      print( "index 的值为 5")
   default :
      print( "默认 case")
}
// index 的值为 10 或 15

以下实例使用 fallthrough

var index = 10

switch index {
case 100  :
    print( "index 的值为 100")
    fallthrough
case 10,15  :
    print( "index 的值为 10 或 15")
    fallthrough
case 6  :
    print( "index 的值为 6")
case 5  :
    print( "index 的值为 5")
default :
    print( "默认 case")
}
// index 的值为 10 或 15
// index 的值为 6

循环

for in

Swift for-in 循环用于遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。

for index in var {
   循环体
}
for index in 1...5 {
    print("\(index) 乘于 5 为:\(index * 5)")
}

var someInts:[Int] = [10, 20, 30]

for index in someInts {
   print( "index 的值为 \(index)")
}

while

while condition
{
   statement(s)
}
var index = 10

while index < 20 
{
   print( "index 的值为 \(index)")
   index = index + 1
}

repeat while

repeat
{
   statement(s);
} while( condition );
var index = 15

repeat {
    print( "index 的值为 \(index)")
    index = index + 1
} while index < 20

continue

import UIKit

var index = 10

repeat{
    index = index + 1
    
    if( index == 15 ){ // index 等于 15 时跳过
        continue
    }
    print( "index 的值为 \(index)")
} while index < 20

break

var index = 10

repeat {
    index = index + 1
    
    if( index == 15 ){  // index 等于 15 时终止循环
        break
    }
    print( "index 的值为 \(index)")
} while index < 20

字符串

创建字符串

// 使用字符串字面量
var stringA = "Hello, World!"
print( stringA )

// String 实例化
var stringB = String("Hello, World!")
print( stringB )

空字符串

// 使用字符串字面量创建空字符串
var stringA = ""

if stringA.isEmpty {
   print( "stringA 是空的" )
} else {
   print( "stringA 不是空的" )
}

// 实例化 String 类来创建空字符串
let stringB = String()

if stringB.isEmpty {
   print( "stringB 是空的" )
} else {
   print( "stringB 不是空的" )
}

字符串常量

// stringA 可被修改
var stringA = "菜鸟教程:"
stringA += "http://www.runoob.com"
print( stringA )

// stringB 不能修改
let stringB = String("菜鸟教程:")
stringB += "http://www.runoob.com"
print( stringB )

字符串中插入值

var varA   = 20
let constA = 100
var varC: Float = 20.0

var stringA = "\(varA) 乘于 \(constA) 等于 \(varC * 100)"
print( stringA )

字符串连接

let constA = "菜鸟教程:"
let constB = "http://www.runoob.com"

var stringA = constA + constB

print( stringA )

字符串长度

var varA   = "www.runoob.com"

print( "\(varA), 长度为 \(varA.count)" )

字符串比较

var varA   = "Hello, Swift!"
var varB   = "Hello, World!"

if varA == varB {
   print( "\(varA) 与 \(varB) 是相等的" )
} else {
   print( "\(varA) 与 \(varB) 是不相等的" )
}

Unicode 字符串

Unicode 是一个国际标准,用于文本的编码,Swift 的 String 类型是基于 Unicode建立的。你可以循环迭代出字符串中 UTF-8 与 UTF-16 的编码,实例如下:

import UIKit

var unicodeString   = "菜鸟教程"

print("UTF-8 编码: ")
for code in unicodeString.utf8 {
    print("\(code) ")
}

print("\n")

print("UTF-16 编码: ")
for code in unicodeString.utf16 {
    print("\(code) ")
}

字符串函数及运算符

  1. isEmpty 判断字符串是否为空,返回布尔值
  2. hasPrefix(prefix: String) 检查字符串是否拥有特定前缀
  3. hasSuffix(suffix: String) 检查字符串是否拥有特定后缀
  4. Int(String) 转换字符串数字为整型
  5. String.count 计算字符串的长度
  6. utf8 您可以通过遍历 String 的 utf8 属性来访问它的 UTF-8 编码
  7. utf16 您可以通过遍历 String 的 utf8 属性来访问它的 utf16 编码
  8. unicodeScalars 您可以通过遍历 String 值的 unicodeScalars 属性来访问它的 Unicode 标量编码
  9. + 连接两个字符串,并返回一个新的字符串
  10. += 连接操作符两边的字符串并将新字符串赋值给左边的操作符变量
  11. == 判断两个字符串是否相等
  12. < 比较两个字符串,对两个字符串的字母逐一比较
  13. != 比较两个字符串是否不相等

字符

Swift 的字符是一个单一的字符字符串字面量,数据类型为 Character

let char1: Character = "A"
let char2: Character = "B"

print("char1 的值为 \(char1)")
print("char2 的值为 \(char2)")

空字符变量

Swift 中不能创建空的 Character(字符) 类型变量或常量:

// Swift 中以下赋值会报错
let char1: Character = ""
var char2: Character = ""

print("char1 的值为 \(char1)")
print("char2 的值为 \(char2)")

遍历字符串中的字符

Swift 的 String 类型表示特定序列的 Character(字符) 类型值的集合。 每一个字符值代表一个 Unicode 字符。

for ch in "Runoob" {
    print(ch)
}

字符串连接字符

使用 String 的 append() 方法来实现字符串连接字符:

import UIKit

var varA:String = "Hello "
let varB:Character = "G"

varA.append("dcds")

varA.append( varB )

print("varC  =  \(varA)")
// varC  =  Hello dcdsG

数组

创建数组

var someArray1 = Array<Int>()
var someArray2 = Array<Int>(repeating: 1, count: 12)
var someInts1 = [Int](repeating: 0, count: 3)
var someInts2:[Int] = [10, 20, 30]

访问数组

var someVar = someArray[index]
import UIKit

var someInts = [Int](repeating: 10, count: 3)

var someVar = someInts[0]
someVar = 999

print( "第一个元素的值 \(someVar)" )
print( "第一个元素的值 \(someInts[0])" )
print( "第二个元素的值 \(someInts[1])" )
print( "第三个元素的值 \(someInts[2])" )

//第一个元素的值 999
//第一个元素的值 10
//第二个元素的值 10
//第三个元素的值 10

修改数组

可以使用 append() 方法或者赋值运算符 += 在数组末尾添加元素

var someInts = [Int]()

someInts.append(20)
someInts.append(30)
someInts += [40]

var someVar = someInts[0]
someInts[1] = 999

print( "第一个元素的值 \(someVar)" )
print( "第二个元素的值 \(someInts[1])" )
print( "第三个元素的值 \(someInts[2])" )
// 第一个元素的值 20
// 第二个元素的值 999
// 第三个元素的值 40

遍历数组

可以使用 for-in 循环来遍历所有数组中的数据项:

var someStrs = [String]()

someStrs.append("Apple")
someStrs.append("Amazon")
someStrs.append("Runoob")
someStrs += ["Google"]

for item in someStrs {
   print(item)
}

for (index, item) in someStrs.enumerated() {
   print("\(index) \(item)")
}
// Apple
// Amazon
// Runoob
// Google
// 0 Apple
// 1 Amazon
// 2 Runoob
// 3 Google

合并数组

可以使用加法操作符(+)来合并两种已存在的相同类型数组。新数组的数据类型会从两个数组的数据类型中推断出来:

var intsA = [Int](repeating: 2, count:2)
var intsB = [Int](repeating: 1, count:3)

var intsC = intsA + intsB

for item in intsC {
    print(item)
}

count 属性

var intsA = Array(repeating: 2, count: 2)
var intsB = Array(repeating: 1, count: 3)

var intsC = intsA + intsB

print("intsA 元素个数为 \(intsA.count)")
print("intsB 元素个数为 \(intsB.count)")
print("intsC 元素个数为 \(intsC.count)")

isEmpty 属性

可以通过只读属性 isEmpty 来判断数组是否为空,返回布尔值:

var someArray1 = Array<Int>()
var someArray2 = Array<Int>(repeating: 1, count: 12)
var someInts1 = [Int](repeating: 0, count: 3)
var someInts2:[Int] = [10, 20, 30]
print(someArray1.isEmpty) // true

字典

创建字典

var someDict =  [Int: String]()
var someDict1 = Dictionary<Int, String>()
someDict[0] = "hello"
someDict1[1] = "world"
print("\(someDict[0]!) \(someDict1[1]!)")
// hello world
var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

访问字典

var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

var someVar = someDict[1]

print( "key = 1 的值为 \(someVar)" )
print( "key = 2 的值为 \(someDict[2])" )
print( "key = 3 的值为 \(someDict[3])" )
// key = 1 的值为 Optional("One")
// key = 2 的值为 Optional("Two")
// key = 3 的值为 Optional("Three")

修改字典

可以使用 updateValue(forKey:) 增加或更新字典的内容。如果 key 不存在,则添加值,如果存在则修改 key 对应的值。updateValue(_:forKey:) 方法返回Optional值。实例如下:

var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

var oldVal = someDict.updateValue("One 新的值", forKey: 1)

var someVar = someDict[1]

print( "key = 1 旧的值 \(oldVal)" )
print( "key = 1 的值为 \(someVar)" )
print( "key = 2 的值为 \(someDict[2])" )
print( "key = 3 的值为 \(someDict[3])" )

// key = 1 旧的值 Optional("One")
// key = 1 的值为 Optional("One 新的值")
// key = 2 的值为 Optional("Two")
// key = 3 的值为 Optional("Three")

也可以通过指定的 key 来修改字典的值,如下所示:

someDict[1] = "One 新的值"

移除 Key Value 对

可以使用 removeValueForKey() 方法来移除字典 key-value 对。如果 key 存在该方法返回移除的值,如果不存在返回 nil 。实例如下:

var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

var removedValue = someDict.removeValue(forKey: 2)

print( "key = 1 的值为 \(someDict[1])" )
print( "key = 2 的值为 \(someDict[2])" )
print( "key = 3 的值为 \(someDict[3])" )

可以通过指定键的值为 nil 来移除 key-value(键-值)对。实例如下:

var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

someDict[2] = nil

遍历字典

var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

for (key, value) in someDict {
   print("字典 key \(key) -  字典 value \(value)")
}
// 字典 key 2 -  字典 value Two
// 字典 key 3 -  字典 value Three
// 字典 key 1 -  字典 value One

可以使用 enumerate() 方法来进行字典遍历,返回的是字典的索引及 (key, value) 对,实例如下:

var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

for (key, value) in someDict.enumerated() {
    print("字典 key \(key) -  字典 (key, value) 对 \(value)")
}
// 字典 key 0 -  字典 (key, value) 对 (2, "Two")
// 字典 key 1 -  字典 (key, value) 对 (3, "Three")
// 字典 key 2 -  字典 (key, value) 对 (1, "One")

字典转换为数组

var someDict:[Int:String] = [1:"One", 2:"Two", 3:"Three"]

let dictKeys = [Int](someDict.keys)
let dictValues = [String](someDict.values)

print("输出字典的键(key)")

for (key) in dictKeys {
    print("\(key)")
}

print("输出字典的值(value)")

for (value) in dictValues {
    print("\(value)")
}

count 属性

var someDict1:[Int:String] = [1:"One", 2:"Two", 3:"Three"]
var someDict2:[Int:String] = [4:"Four", 5:"Five"]

print("someDict1 含有 \(someDict1.count) 个键值对")
print("someDict2 含有 \(someDict2.count) 个键值对")

isEmpty 属性

var someDict1:[Int:String] = [1:"One", 2:"Two", 3:"Three"]
var someDict2:[Int:String] = [4:"Four", 5:"Five"]
var someDict3:[Int:String] = [Int:String]()

print("someDict1 = \(someDict1.isEmpty)")
print("someDict2 = \(someDict2.isEmpty)")
print("someDict3 = \(someDict3.isEmpty)")

集合

var someIntSet = Set<Int>()

someIntSet.insert(5)
someIntSet.insert(12)
print(someIntSet.insert(12).inserted) // false
// (inserted: false, memberAfterInsert: 12)
someIntSet.insert(1)

for val in someIntSet {
    print(val)
}

for (idx, val) in someIntSet.enumerated() {
    print("\(idx) \(val)")
}

someIntSet.count
someIntSet.isEmpty
let res:Bool = someIntSet.contains(where: { val in
        val == 5
})
print(res) // true

函数

函数定义

func funcname(形参) -> returntype
{
   Statement1
   Statement2
   ……
   Statement N
   return parameters
}
func runoob(site: String) -> String {
    return (site)
}
print(runoob(site: "www.mfavant.duckdns.org"))

元组作为函数返回值

func minMax(array: [Int]) -> (min: Int, max: Int) {
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("最小值为 \(bounds.min) ,最大值为 \(bounds.max)")

结合Optional

func minMax(array: [Int]) -> (min: Int, max: Int)? {
    if array.isEmpty { return nil }
    var currentMin = array[0]
    var currentMax = array[0]
    for value in array[1..<array.count] {
        if value < currentMin {
            currentMin = value
        } else if value > currentMax {
            currentMax = value
        }
    }
    return (currentMin, currentMax)
}
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
    print("最小值为 \(bounds.min),最大值为 \(bounds.max)")
}

外部参数名

func pow(firstArg a: Int, secondArg b: Int) -> Int {
   var res = a
   for _ in 1..<b {
      res = res * a
   }
   print(res)
   return res
}
pow(firstArg:5, secondArg:3)

可变参数

func vari<N>(members: N...){
    for i in members {
        print(i)
    }
}

vari(members: 4,3,5)
vari(members: 4.5, 3.1, 5.6)
vari(members: "Google", "Baidu", "Runoob")

常量变量及I/O参数

一般默认在函数中定义的参数都是常量参数,也就是这个参数你只可以查询使用,不能改变它的值。

如果想要声明一个变量参数,可以在参数定义前加 inout 关键字,这样就可以改变这个参数的值了。

一般默认的参数传递都是传值调用的,而不是传引用。所以传入的参数在函数内改变,并不影响原来的那个参数。传入的只是这个参数的副本。 当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改。

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}


var x = 1
var y = 5
swapTwoInts(&x, &y)
print("x 现在的值 \(x), y 现在的值 \(y)")

使用函数类型

var addition: (Int, Int) -> Int = sum
func sum(a: Int, b: Int) -> Int {
   return a + b
}

var addition: (Int, Int) -> Int = sum
print("输出结果: \(addition(40, 89))")
// 输出结果: 129

函数类型作参数类型

func sum(a: Int, b: Int) -> Int {
    return a + b
}

func returnFuncSum() -> ((Int, Int) -> Int) {
    return sum;
}

var addition: (Int, Int) -> Int = returnFuncSum()
print("输出结果: \(addition(40, 89))")

func another(addition: (Int, Int) -> Int, a: Int, b: Int) {
    print("输出结果: \(addition(a, b))")
}

another(addition: sum, a: 10, b: 20)

函数类型作返回类型

func sum(a: Int, b: Int) -> Int {
    return a + b
}

func returnFuncSum() -> ((Int, Int) -> Int) {
    return sum;
}

函数嵌套

函数嵌套指的是函数内定义一个新的函数,外部的函数可以调用函数内定义的函数。

func calcDecrement(forDecrement total: Int) -> () -> Int {
    // 闭包
    var overallDecrement = 0
    func decrementer() -> Int {
        overallDecrement -= total
        return overallDecrement
    }
    return decrementer
}
let decrem = calcDecrement(forDecrement: 30)
print(decrem()) // -30
print(decrem()) // -60
print(decrem()) // -90
print(decrem()) // -120

闭包

以下定义了一个接收参数并返回指定类型的闭包语法:

{(parameters) -> return type in
   statements
}
let studname = { print("Swift 闭包实例。") }
studname() // Swift 闭包实例。

let divide = {(val1: Int, val2: Int) -> Int in
   return val1 / val2
}
let result = divide(200, 20)
print (result) // 10
let names = ["AT", "AE", "D", "S", "BE"]

func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}
var reversed = names.sorted(by: backwards)
print(reversed)

names.sorted(by: { (s1: String, s2: String) in
    return s1 > s2
})

参数名称缩写

Swift 自动为内联函数提供了参数名称缩写功能,您可以直接通过$0,$1,$2来顺序调用闭包的参数。

let names = ["AT", "AE", "D", "S", "BE"]

print( names.sorted(by: { $0 > $1 }) )

运算符函数

Swift 的String类型定义了关于大于号 (>) 的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。 而这正好与 sort(_:) 方法的第二个参数需要的函数类型相符合。

let names = ["AT", "AE", "D", "S", "BE"]

var reversed = names.sorted(by: >)
print(reversed)
// ["S", "D", "BE", "AT", "AE"]

尾随闭包

尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。

let names = ["AT", "AE", "D", "S", "BE"]

var reversed = names.sorted(by: >)
print(reversed)
// ["S", "D", "BE", "AT", "AE"]
print(names.sorted(){
    $0 > $1
})

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
    print("hello world")
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
    print("hello world")
}

捕获值

闭包可以在其定义的上下文中捕获常量或变量。

即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

func makeIncrementorAndDecrementor(forIncrement amount: Int) -> (() -> Int,() -> Int)  {
    var runningTotal = 0
    // incrementor 捕获了 runningTotal
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    func decrementor() -> Int {
        runningTotal -= amount
        return runningTotal
    }
    return (incrementor, decrementor)
}

var (incrementor, decrementor) = makeIncrementorAndDecrementor(forIncrement: 100)
print(incrementor()) // 100
print(incrementor()) // 200
print(decrementor()) // 100

闭包是引用类型

无论您将函数/闭包赋值给一个常量还是变量,您实际上都是将常量/变量的值设置为对应函数/闭包的引用。

func makeIncrementorAndDecrementor(forIncrement amount: Int) -> (() -> Int,() -> Int)  {
    var runningTotal = 0
    // incrementor 捕获了 runningTotal
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    func decrementor() -> Int {
        runningTotal -= amount
        return runningTotal
    }
    return (incrementor, decrementor)
}

var (incrementor, decrementor) = makeIncrementorAndDecrementor(forIncrement: 100)
print(incrementor()) // 100
print(incrementor()) // 200
print(decrementor()) // 100

// 函数和闭包为引用类型
var incrementorCopy = incrementor
print(incrementorCopy()) // 200
print(incrementor()) // 300

枚举

enum DaysofaWeek {
    case Sunday
    case Monday
    case Tuesday
    case Wednesday
    case Thursday
    case Friday
    case Saturday
}

var weekDay = DaysofaWeek.Thursday

switch weekDay
{
case DaysofaWeek.Sunday:
    print("星期天")
case DaysofaWeek.Monday:
    print("星期一")
case DaysofaWeek.Tuesday:
    print("星期二")
case DaysofaWeek.Wednesday:
    print("星期三")
case DaysofaWeek.Thursday:
    print("星期四")
case DaysofaWeek.Friday:
    print("星期五")
case DaysofaWeek.Saturday:
    print("星期六")
}

相关值

enum Student{
    case Name(String)
    case Mark(Int, Int, Int)
}

var studDetails = Student.Name("Runoob")
var studMarks = Student.Mark(98, 97, 95)

switch studMarks {
case .Name(let studName):
    print("学生的名字是: \(studName)。")
case .Mark(let Mark1, let Mark2, let Mark3):
    print("学生的成绩是: \(Mark1),\(Mark2),\(Mark3)。")
}

原始值

原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。

在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。

enum Month: Int {
    case January = 1, February, March, April, May, June, July, August, September, October, November, December
}

let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)。")

结构体

结构体总是通过被复制的方式在代码中传递,因此它的值是不可修改的。

struct nameStruct { 
   Definition 1
   Definition 2
   ……
   Definition N
}

样例

struct studentMarks {
   var mark1 = 100
   var mark2 = 78
   var mark3 = 98
}

let marks = studentMarks()
print("Mark1 是 \(marks.mark1)")
// Mark1 是 100
print("Mark2 是 \(marks.mark2)")
// Mark2 是 78
print("Mark3 是 \(marks.mark3)")
// Mark3 是 98

结构体是值类型

以下实例化通过结构体实例化时传值并克隆一个结构体:

struct MarksStruct {
   var mark: Int

   init(mark: Int) {
      self.mark = mark
   }
}

var aStruct = MarksStruct(mark: 98)
var bStruct = aStruct // aStruct 和 bStruct 是使用相同值的结构体!
bStruct.mark = 97
print(aStruct.mark) // 98
print(bStruct.mark) // 97

结构体 init

struct markStruct{
    var mark1: Int
    var mark2: Int
    var mark3: Int
    
    init(mark1: Int, mark2: Int, mark3: Int){
        self.mark1 = mark1
        self.mark2 = mark2
        self.mark3 = mark3
    }
}

print("优异成绩:")
var marks = markStruct(mark1: 98, mark2: 96, mark3:100)
print(marks.mark1)
print(marks.mark2)
print(marks.mark3)

print("糟糕成绩:")
var fail = markStruct(mark1: 34, mark2: 42, mark3: 13)
print(fail.mark1)
print(fail.mark2)
print(fail.mark3)

类和结构体对比

共同点:

对比结构体相比,类还有如下的附加功能:

class classname {
   Definition 1
   Definition 2
   ……
   Definition N
}
class MarksStruct {
    var mark: Int
    init(mark: Int) {
        self.mark = mark
    }
}

class studentMarks {
    var mark = 300
}

let marksStudent = MarksStruct(mark: 1)
print("\(marksStudent.mark)") // 1

let marks = studentMarks()
print("成绩为 \(marks.mark)")
// 成绩为 300

作为引用类型访问类属性

类的属性可以通过 . 来访问。格式为:实例化类名.属性名

class MarksStruct {
   var mark: Int
   init(mark: Int) {
      self.mark = mark
   }
}

class studentMarks {
   var mark1 = 300
   var mark2 = 400
   var mark3 = 900
}

let marks = studentMarks()
// Mark1 is 300
print("Mark1 is \(marks.mark1)")
// Mark2 is 400
print("Mark2 is \(marks.mark2)")
// Mark3 is 900
print("Mark3 is \(marks.mark3)")

恒等运算符

因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。

为了能够判定两个常量或者变量是否引用同一个类实例,Swift 内建了两个恒等运算符:

class SampleClass: Equatable {
    let myProperty: String
    init(s: String) {
        myProperty = s
    }
}

func ==(lhs: SampleClass, rhs: SampleClass) -> Bool {
    return lhs.myProperty == rhs.myProperty
}

let spClass1 = SampleClass(s: "Hello")
let spClass2 = SampleClass(s: "Hello")

if spClass1 === spClass2 { // false
    print("引用相同的类实例 \(spClass1)")
}

if spClass1 !== spClass2 { // true
    print("引用不相同的类实例 \(spClass2)")
}

if spClass1 == spClass2 { // true
    print("两实例内部值相同 \(spClass2.myProperty)")
}

属性

Swift 属性将值跟特定的类、结构或枚举关联。属性可分为存储属性和计算属性:

存储属性 计算属性
存储常量或变量作为实例的一部分 计算(而不是存储)一个值
用于类和结构体 用于类、结构体和枚举

存储属性

一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量

struct Number
{
   var digits: Int
   let pi = 3.1415
}

var n = Number(digits: 12345)
n.digits = 67

print("\(n.digits)")
print("\(n.pi)")

如果你定义的是一个常量存储属性,如果尝试修改它就会报错,如下所示:

struct Number
{
    var digits: Int
    let numbers = 3.1415
}

var n = Number(digits: 12345)
n.digits = 67

print("\(n.digits)")
print("\(n.numbers)")
// Cannot assign to property: 'numbers' is a 'let' constant
n.numbers = 8.7

延迟存储属性

延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性,在属性声明前使用lazy来标识一个延迟存储属性

必须将延迟存储属性声明成变量(使用var关键字),因为属性的值在实例构造完成之前可能无法得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

class sample {
    lazy var no = number() // `var` 关键字是必须的
}

class number {
    var name = "Runoob Swift 教程"
}

var firstsample = sample()
print(firstsample.no.name) // 运行时首次访问才会初始化

计算属性

除存储属性外,类、结构体和枚举可以定义计算属性,计算属性不直接存储值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值。

class sample {
    var no1 = 0.0, no2 = 0.0
    var length = 300.0, breadth = 150.0
    
    var middle: (Double, Double) {
        get{
            return (length / 2, breadth / 2)
        }
        set(axis){
            no1 = axis.0 - (length / 2)
            no2 = axis.1 - (breadth / 2)
        }
    }
    
    var testing: (Int) {
        get {
            return 1
        }
        set {
            print(newValue)
        }
    }
}

var result = sample()
// (150.0, 75.0)
print(result.middle)
result.middle = (0.0, 10.0)
// -150.0
print(result.no1)
// -65.0
print(result.no2)
print(result.testing) // 1
result.testing = 12 // 12

只读计算属性

只有 getter 没有 setter 的计算属性就是只读计算属性

必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let 关键字只用来声明常量属性,表示初始化后再也无法修改的值。

class film {
    var head = ""
    var duration = 0.0
    var metaInfo: [String:String] {
        return [
            "head": self.head,
            "duration":"\(self.duration)"
        ]
    }
}

var movie = film()
movie.head = "Swift 属性"
movie.duration = 3.09

// Swift 属性
print(movie.metaInfo["head"]!)
// 3.09
print(movie.metaInfo["duration"]!)

属性观察器

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。

可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。

不需要为无法重载的计算属性添加属性观察器,因为可以通过 setter 直接监控和响应值的变化。

可以为属性添加如下的一个或全部观察器:

class Samplepgm {
    var counter: Int = 0{
        willSet(newTotal){
            print("计数器: \(newTotal)")
        }
        didSet{
            if counter > oldValue {
                print("新增数 \(counter - oldValue)")
            }
        }
    }
}

let NewCounter = Samplepgm()
NewCounter.counter = 100
NewCounter.counter = 800
// 计数器: 100
// 新增数 100
// 计数器: 800
// 新增数 700

静态类型

使用关键字 static 来定义值类型的类型属性,关键字 class 来为类定义类型属性。

// struct:可以有 static stored 和 computed type properties
struct Structname {
    // 推断为 String,或显式写:static var storedTypeProperty: String = "Hello"
    static let storedTypeProperty = "Hello"
    
    static var computedTypeProperty: Int {
        return 42  // 示例返回值
    }
}

// enum:不能有存储型类型属性,只能是 computed(但注意:从 Swift 5.9+,enum 仍不能有 static var stored...)
// 所以 enum 只能定义 computed type properties(且必须是 computed)
enum Enumname {
    // static var storedTypeProperty = " "  // 非法:enum 不允许存储型类型属性
    static var computedTypeProperty: Int {
        return 10  // 必须 return
    }
}

// class:class var 支持 computed(get/set)
// 注意:若需存储,需用 static let/var + private backing storage
class Classname {
    // 注意:这是计算型属性(没有存储),不是存储属性!
    class var computedTypeProperty: Int {
        get {
            return 100
        }
        set {
            print("新值是: \(newValue)")
            // 注意:由于是计算型属性,这里不能真正“赋值”给某个变量,除非用 static var backing 存储
            // 如果你想要可写并持久化,需要改为 static var + private backing,见下方进阶版
        }
    }
}
struct StudMarks {
   static let markCount = 97
   nonisolated(unsafe) static var totalCount = 0
   var InternalMarks: Int = 0 {
      didSet {
         if InternalMarks > StudMarks.markCount {
            InternalMarks = StudMarks.markCount
         }
         if InternalMarks > StudMarks.totalCount {
            StudMarks.totalCount = InternalMarks
         }
      }
   }
}

var stud1Mark1 = StudMarks()
var stud1Mark2 = StudMarks()

stud1Mark1.InternalMarks = 98
// 97
print(stud1Mark1.InternalMarks)

stud1Mark2.InternalMarks = 87
// 87
print(stud1Mark2.InternalMarks)

方法

在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func incrementBy(amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}
// 初始计数值是0
let counter = Counter()

// 计数值现在是1
counter.increment()

// 计数值现在是6
counter.incrementBy(amount: 5)
print(counter.count) // 6

// 计数值现在是0
counter.reset()
print(counter.count) // 0

提供外部参数名称

class multiplication {
    var count: Int = 0
    func incrementBy(first no1: Int, no2: Int) {
        count = no1 * no2
        print(count)
    }
}

let counter = multiplication()
counter.incrementBy(first: 800, no2: 3)
counter.incrementBy(first: 100, no2: 5)
counter.incrementBy(first: 15000, no2: 3)

self 属性

类型的每一个实例都有一个隐含属性叫做self,self 完全等同于该实例本身。

class calculations {
    let a: Int
    let b: Int
    let res: Int
    
    init(a: Int, b: Int) {
        self.a = a
        self.b = b
        res = a + b
        print("Self 内: \(res)")
    }
    
    func tot(c: Int) -> Int {
        return res - c
    }
    
    func result() {
        print("结果为: \(tot(c: 20))")
        print("结果为: \(tot(c: 50))")
    }
}

let pri = calculations(a: 600, b: 300)
let sum = calculations(a: 1200, b: 300)

pri.result()
sum.result()

在实例方法中修改值类型

Swift 语言中结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。

但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。

方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。

struct area {
    var length = 1
    var breadth = 1

    func area() -> Int {
        return length * breadth
    }

    mutating func scaleBy(res: Int) {
        self.length *= res
        self.breadth *= res

        print(length)
        print(breadth)
    }
}

var val = area(length: 3, breadth: 5)
val.scaleBy(res: 3)
val.scaleBy(res: 30)
val.scaleBy(res: 300)

静态方法

class Math
{
    class func abs(number: Int) -> Int
    {
        if number < 0
        {
            return (-number)
        }
        else
        {
            return number
        }
    }
}

struct absno
{
    static func abs(number: Int) -> Int
    {
        if number < 0
        {
            return (-number)
        }
        else
        {
            return number
        }
    }
}

let no = Math.abs(number: -35)
let num = absno.abs(number: -5)

print(no) // 35
print(num) // 5

下标脚本

下标脚本 可以定义在类(Class)、结构体(structure)和枚举(enumeration)这些目标中,可以认为是访问对象、集合或序列的快捷方式,不需要再调用实例的特定的赋值和访问方法。

struct subexample {
    let decrementer: Int
    subscript(index: Int) -> Int {
        return decrementer / index
    }
}
let division = subexample(decrementer: 100)

// 100 除以 9 等于 11
print("100 除以 9 等于 \(division[9])")
// 100 除以 2 等于 50
print("100 除以 2 等于 \(division[2])")
// 100 除以 3 等于 33
print("100 除以 3 等于 \(division[3])")
// 100 除以 5 等于 20
print("100 除以 5 等于 \(division[5])")
// 100 除以 7 等于 14
print("100 除以 7 等于 \(division[7])")
class daysofaweek {
    private var days = ["Sunday", "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "saturday"]
    subscript(index: Int) -> String {
        get {
            return days[index]   // 声明下标脚本的值
        }
        set(newValue) {
            self.days[index] = newValue   // 执行赋值操作
        }
    }
}
var p = daysofaweek()
p[0] = "One"

print(p[0]) // One
print(p[1]) // Monday
print(p[2]) // Tuesday
print(p[3]) // Wednesday
struct Matrix {
    let rows: Int, columns: Int
    var print: [Double]

    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        print = Array(repeating: 0.0, count: rows * columns)
    }

    subscript(row: Int, column: Int) -> Double {
        get {
            return print[(row * columns) + column]
        }
        set {
            print[(row * columns) + column] = newValue
        }
    }
}

// 创建了一个新的 3 行 3 列的Matrix实例
var mat = Matrix(rows: 3, columns: 3)

// 通过下标脚本设置值
mat[0,0] = 1.0
mat[0,1] = 2.0
mat[1,0] = 3.0
mat[1,1] = 5.0

// 通过下标脚本获取值
print("\(mat[0,0])") // 1.0
print("\(mat[0,1])") // 2.0
print("\(mat[1,0])") // 3.0
print("\(mat[1,1])") // 5.0

继承

class SomeClass: SomeSuperclass {
    // 类的定义
}
class StudDetails
{
    var mark1: Int;
    var mark2: Int;
    
    init(stm1:Int, results stm2:Int)
    {
        mark1 = stm1;
        mark2 = stm2;
    }
    
    func show()
    {
        print("Mark1:\(self.mark1), Mark2:\(self.mark2)")
    }
}

class Tom : StudDetails
{
    init()
    {
        super.init(stm1: 93, results: 89)
    }
}

let tom = Tom()
tom.show() // Mark1:93, Mark2:89

override 重写

访问超类的方法、属性及下标脚本,可以用

重写方法

class SuperClass {
    func show() {
        print("这是超类 SuperClass")
    }
}

class SubClass: SuperClass  {
    override func show() {
        print("这是子类 SubClass")
    }
}

let superClass = SuperClass()
superClass.show() // 这是超类 SuperClass

let subClass = SubClass()
subClass.show() // 这是超类 SuperClass

重写属性

class Circle {
    var radius = 12.5
    var area: String {
        return "矩形半径 \(radius) "
    }
}

// 继承超类 Circle
class Rectangle: Circle {
    var print = 7
    override var area: String {
        return super.area + " ,但现在被重写为 \(print)"
    }
}

let rect = Rectangle()
rect.radius = 25.0
rect.print = 3
// Radius 矩形半径 25.0  ,但现在被重写为 3
print("Radius \(rect.area)")

重写属性观察器

class Circle {
    var radius = 12.5
    var area: String {
        return "矩形半径为 \(radius) "
    }
}

class Rectangle: Circle {
    var print = 7
    override var area: String {
        return super.area + " ,但现在被重写为 \(print)"
    }
}

let rect = Rectangle()
rect.radius = 25.0
rect.print = 3
// 半径: 矩形半径为 25.0  ,但现在被重写为 3
print("半径: \(rect.area)")

class Square: Rectangle {
    override var radius: Double {
        didSet {
            print = Int(radius/5.0)+1
        }
    }
}

let sq = Square()
sq.radius = 100.0
// 半径: 矩形半径为 100.0  ,但现在被重写为 21
print("半径: \(sq.area)")

防止重写

如果你重写了final方法,属性或下标脚本,在编译时会报错。

你可以通过在关键字class前添加final特性(final class)来将整个类标记为 final 的,这样的类是不可被继承的,否则会报编译错误。

final class A {
    final var radius = 12.5
    var area: String {
        return "矩形半径为 \(radius) "
    }
}

// Inheritance from a final class 'A'
class B: A {
    var print = 7
}

class C: B {
    // Property overrides a 'final' property
    override var radius: Double {
        didSet {
            print = Int(radius/5.0)+1
        }
    }
}

构造过程

存储型属性的初始赋值

类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。

存储属性在构造器中赋值时,它们的值是被直接设置的,不会触发任何属性观测器。

存储属性在构造器中赋值流程:

构造器

struct rectangle {
    var length: Double
    var breadth: Double
    init() {
        length = 6
        breadth = 12
    }
}

var area = rectangle()
print("矩形面积为 \(area.length*area.breadth)")
// 矩形面积为 72.0

默认属性值

可以在构造器中为存储型属性设置初始值;同样,也可以在属性声明时为其设置默认值。

struct rectangle {
    // 设置默认值
    var length = 6
    var breadth = 12
}
var area = rectangle()
print("矩形的面积为 \(area.length*area.breadth)")

构造参数

可以在定义构造器 init() 时提供构造参数,如下所示:

struct Rectangle {
    var length: Double
    var breadth: Double
    var area: Double

    init(fromLength length: Double, fromBreadth breadth: Double) {
        self.length = length
        self.breadth = breadth
        area = length * breadth
    }

    init(fromLeng leng: Double, fromBread bread: Double) {
        self.length = leng
        self.breadth = bread
        area = leng * bread
    }
}

let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面积为: \(ar.area)") // 面积为: 72.0

let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面积为: \(are.area)") // 面积为: 432.0

内部和外部参数名

如果你在定义构造器时没有提供参数的外部名字,Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名。

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

print("red 值为: \(magenta.red)")
print("green 值为: \(magenta.green)")
print("blue 值为: \(magenta.blue)")

// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let halfGray = Color(white: 0.5)
print("red 值为: \(halfGray.red)")
print("green 值为: \(halfGray.green)")
print("blue 值为: \(halfGray.blue)")

没有外部名称参数

如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线 _ 来显示描述它的外部名。

struct Rectangle {
    var length: Double
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    // 不提供外部名字
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为: \(rectarea.length)")
// 面积为: 180.0

let rearea = Rectangle(frombreadth: 370.0)
print("面积为: \(rearea.length)")
// 面积为: 3700.0

let recarea = Rectangle(frombre: 110.0)
print("面积为: \(recarea.length)")
// 面积为: 3300.0

可选属性类型

struct Rectangle {
    var length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")

let rearea = Rectangle(frombreadth: 370.0)
print("面积为:\(rearea.length)")

let recarea = Rectangle(frombre: 110.0)
print("面积为:\(recarea.length)")
//面积为:Optional(180.0)
//面积为:Optional(3700.0)
//面积为:Optional(3300.0)

构造过程中修改常量属性

只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。

struct Rectangle {
    let length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")

let rearea = Rectangle(frombreadth: 370.0)
print("面积为:\(rearea.length)")

let recarea = Rectangle(frombre: 110.0)
print("面积为:\(recarea.length)")
//面积为:Optional(180.0)
//面积为:Optional(3700.0)
//面积为:Optional(3300.0)

默认构造器

默认构造器将简单的创建一个所有属性值都设置为默认值的实例:

以下实例中,ShoppingListItem类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()


print("名字为: \(item.name)")
print("数理为: \(item.quantity)")
print("是否付款: \(item.purchased)")
// 名字为: nil
// 数理为: 1
// 是否付款: false
struct Rectangle {
    var length = 100.0, breadth = 200.0
}
let area1 = Rectangle(length: 24.0, breadth: 32.0)
let area2 = Rectangle()

print("矩形的面积: \(area1.length)")
print("矩形的面积: \(area1.breadth)")

print("矩形的面积: \(area2.length)")
print("矩形的面积: \(area2.breadth)")
//矩形的面积: 24.0
//矩形的面积: 32.0
//矩形的面积: 100.0
//矩形的面积: 200.0

值类型的构造器代理

C++也有这个功能 叫构造函数委托,构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。 以下实例中,Rect 结构体调用了 Size 和 Point 的构造过程:

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

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)
    }
}


// origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size 结构体初始值: \(basicRect.size.width, basicRect.size.height) ")
print("Rect 结构体初始值: \(basicRect.origin.x, basicRect.origin.y) ")

// 将origin和size的参数值赋给对应的存储型属性
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))

print("Size 结构体初始值: \(originRect.size.width, originRect.size.height) ")
print("Rect 结构体初始值: \(originRect.origin.x, originRect.origin.y) ")


//先通过center和size的值计算出origin的坐标。
//然后再调用(或代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))

print("Size 结构体初始值: \(centerRect.size.width, centerRect.size.height) ")
print("Rect 结构体初始值: \(centerRect.origin.x, centerRect.origin.y) ")

指定构造器

class mainClass {
    var no1 : Int // 局部存储变量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}
class subClass : mainClass {
    var no2 : Int // 新的子类存储变量
    init(no1 : Int, no2 : Int) {
        self.no2 = no2 // 初始化
        super.init(no1:no1) // 初始化超类
    }
}

let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)

print("res 为: \(res.no1)")
// res 为: 10
print("res2 为: \(res2.no1)")
// res 为: 10
print("res2 为: \(res2.no2)")
// res 为: 20

便利构造器

class mainClass {
    var no1 : Int // 局部存储变量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}

class subClass : mainClass {
    var no2 : Int
    init(no1 : Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 便利方法只需要一个参数
    override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)

print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")

required vs init vs convenience init

关键字 作用 是否可继承 示例
init() 普通指定初始化器 ❌ 子类不自动继承,除非无其他指定初始化器 默认 init()
required init() 父类强制要求子类必须实现的初始化器 ✅ 必须在子类中用 required override 重写 required init(name: String)
convenience init() 便利初始化器(必须调用 self.init(...) ❌ 不强制子类实现 UI 中的 init(frame:)

构造器的继承和重载

Swift 中的子类不会默认继承父类的构造器。

当你重写一个父类指定构造器时,你需要写override修饰符。

class SuperClass {
    var corners = 4
    var description: String {
        return "\(corners) 边"
    }
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")
// 矩形: 4 边

class SubClass: SuperClass {
    override init() {  //重载构造器
        super.init()
        corners = 5
    }
}

let subClass = SubClass()
print("五角型: \(subClass.description)")
// 五角型: 5 边
class MainClass {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[匿名]")
    }
}
let main = MainClass(name: "Runoob")
print("MainClass 名字为: \(main.name)")

let main2 = MainClass()
print("没有对应名字: \(main2.name)")

class SubClass: MainClass {
    var count: Int
    init(name: String, count: Int) {
        self.count = count
        super.init(name: name)
    }

    override convenience init(name: String) {
        self.init(name: name, count: 1)
    }
}

let sub = SubClass(name: "Runoob")
print("MainClass 名字为: \(sub.name)")

let sub2 = SubClass(name: "Runoob", count: 3)
print("count 变量: \(sub2.count)")

类的可失败构造器 init?

struct Animal {
    let species: String

    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

//通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功
// someCreature 的类型是 Animal? 而不是 Animal
let someCreature = Animal(species: "长颈鹿")

// 打印 "动物初始化为长颈鹿"
if let giraffe = someCreature {
    print("动物初始化为\(giraffe.species)")
}

let fail = Animal(species: "")
print(fail) // nil
class StudRecord {
    let studname: String!

    init(studname: String) {
        self.studname = nil
    }
}

let stuRecord = StudRecord(studname: "");
print(stuRecord.studname) // nil 无需 stuRecord.studname!

// String! 等价于 Optional<String>,即 String?,
// 但在语义上暗示:这个可选值在初始化完成后将始终有值,不需要显式解包即可使用。

枚举类型的可失败构造器

enum TemperatureUnit {
    // 开尔文,摄氏,华氏
    case Kelvin, Celsius, Fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}


let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("这是一个已定义的温度单位,所以初始化成功。")
}

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("这不是一个已定义的温度单位,所以初始化失败。")
}

// 这是一个已定义的温度单位,所以初始化成功。
// 这不是一个已定义的温度单位,所以初始化失败。

override 可失败构造器

可以用子类的非可失败构造器覆盖一个基类的可失败构造器。

可以用一个非可失败构造器覆盖一个可失败构造器,但反过来却行不通。

可失败构造器 init!

通常来说我们通过在init关键字后添加问号的方式(init?)来定义一个可失败构造器,但你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!)

struct StudRecord {
    let stname: String
    
    init!(stname: String) {
        if stname.isEmpty {return nil }
        self.stname = stname
    }
}

let stmark:Optional<StudRecord> = StudRecord(stname: "Runoob")
if let name = stmark {
    print("指定了学生名")
}

let blankname = StudRecord(stname: "")
if blankname == nil {
    print("学生名为空")
}

init 内部初始化操作顺序规则

Swift 的初始化方法需要保证类型的所有属性都被初始化。所以初始化方法的调用顺序就很有讲究。在某个类的子类中,初始化方法里语句的顺序并不是随意的,我们需要保证在当前子类实例的成员初始化完成后才能调用父类的初始化方法:

class Cat {
    var name: String
    init() {
        name = "cat"
    }
}

class Tiger: Cat {
    let power: Int
    override init() {
        // 随便调下面三行的先后顺序都会编译不通过
        power = 10
        super.init()
        name = "tiger"
    }
}

let tiger = Tiger()

析构过程

析构函数只适用于类类型。

Swift 会自动释放不再需要的实例以释放资源。

Swift 通过自动引用计数(ARC)处理实例的内存管理。

class BaseClass {
    init() {
        print("BaseClass")
    }
    deinit {
        print("~BaseClass")
    }
}

var show: BaseClass? = BaseClass()
show = nil
//BaseClass
//~BaseClass

可选链

可选链(Optional Chaining)是一种可以请求和调用属性、方法和子脚本的过程,用于请求或调用的目标可能为nil。

可选链返回两个值:

多次请求或调用可以被链接成一个链,如果任意一个节点为nil将导致整条链失效。

使用!可选链

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

//将导致运行时错误
let roomCount = john.residence!.numberOfRooms
print("hello")

使用?可选链

class Person {
    var residence: Residence?
    func hello(){
        print("hello")
    }
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

// 链接可选residence?属性,如果residence存在则取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
    print("John 的房间号为 \(roomCount)。")
} else {
    print("不能查看房间号")
}

// 不能查看房间号
let person:Optional<Person> = nil
person?.hello() // 什么都不做也不报错

var dire = Dictionary<String, Array<Int>>()
dire["0"] = [1, 2, 3]
print(dire["0"]?[2]) // 3
dire["1"]?[0] = 999
print(dire["1"]?[0]) // nil

自动引用计数

Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存,但在有些时候我们还是需要在代码中实现内存管理。

ARC 功能

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) 开始初始化")
    }
    deinit {
        print("\(name) 被析构")
    }
}

// 值会被自动初始化为nil,目前还不会引用到Person类的实例
var reference1: Person?
var reference2: Person?
var reference3: Person?

// 创建Person类的新实例
reference1 = Person(name: "Runoob")

//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1

//断开第一个强引用
reference1 = nil
print(1)
//断开第二个强引用
reference2 = nil
print(2)
//断开第三个强引用,并调用析构函数
reference3 = nil
print(3)
//Runoob 开始初始化
//1
//2
//3
//Runoob 被析构

循环强引用内存泄露

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) 被析构") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { print("Apartment #\(number) 被析构") }
}

// 两个变量都被初始化为nil
var runoob: Person?
var number73: Apartment?

// 赋值
runoob = Person(name: "Runoob")
number73 = Apartment(number: 73)

// 意感叹号是用来展开和访问可选变量 runoob 和 number73 中的实例
// 循环强引用被创建
runoob!.apartment = number73
number73!.tenant = runoob

// 断开 runoob 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
runoob = nil
number73 = nil

解决方法,弱引用 和 无主引用

弱引用

class Module {
    let name: String
    init(name: String) { self.name = name }
    var sub: SubModule?
    deinit { print("\(name) 主模块") }
}

class SubModule {
    let number: Int
    
    init(number: Int) { self.number = number }
    // 不会增加引用计数
    weak var topic: Module?

    deinit { print("子模块 topic 数为 \(number)") }
}

var toc: Module?
var list: SubModule?
toc = Module(name: "ARC")
list = SubModule(number: 4)
toc!.sub = list
list!.topic = toc

toc = nil
list = nil

无主引用

class Student {
    let name: String
    var section: Marks?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("\(name)") }
}

class Marks {
    let marks: Int
    unowned let stname: Student

    init(marks: Int, stname: Student) {
        self.marks = marks
        self.stname = stname
    }

    deinit { print("学生的分数为 \(marks)") }
}

var module: Student?
module = Student(name: "ARC")
module!.section = Marks(marks: 98, stname: module!)
module = nil

闭包引起的循环强引用

HTMLElement 类产生了类实例和 asHTML 默认值的闭包之间的循环强引用。

闭包捕获了self,这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用。

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
    
}

// 创建实例并打印信息
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil // 不会调用 deinit

使用无主引用解决

class HTMLElement {
    
    let name: String
    let text: String?
    
    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }
    
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
    
    deinit {
        print("\(name) 被析构")
    }
    
}

//创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

// HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息
paragraph = nil

类型转换

Swift 语言类型转换可以判断实例的类型。也可以用于检测实例类型是否属于其父类或者子类的实例。

类型转换使用 is 和 as 操作符实现,is 用于检测值的类型,as 用于转换类型。

类型转换也可以用来检查一个类是否实现了某个协议。

操作符 is

类型转换用于检测实例类型是否属于特定的实例类型

class Subjects {
    var physics: String
    init(physics: String) {
        self.physics = physics
    }
}

class Chemistry: Subjects {
    var equations: String
    init(physics: String, equations: String) {
        self.equations = equations
        super.init(physics: physics)
    }
}

class Maths: Subjects {
    var formulae: String
    init(physics: String, formulae: String) {
        self.formulae = formulae
        super.init(physics: physics)
    }
}

let sa = [
    Chemistry(physics: "固体物理", equations: "赫兹"),
    Maths(physics: "流体动力学", formulae: "千兆赫"),
    Chemistry(physics: "热物理学", equations: "分贝"),
    Maths(physics: "天体物理学", formulae: "兆赫"),
    Maths(physics: "微分方程", formulae: "余弦级数")]

var chemCount = 0
var mathsCount = 0
var subjectsCount = 0

for item in sa {
    // 如果是一个 Chemistry 类型的实例,返回 true,相反返回 false。
    if item is Chemistry {
        chemCount += 1
    } else if item is Maths {
        mathsCount += 1
    }
    
    if item is Subjects {
        subjectsCount += 1
    }
}

print("化学科目包含 \(chemCount) 个主题,数学包含 \(mathsCount) 个主题 subjectsCount \(subjectsCount)")
// 化学科目包含 2 个主题,数学包含 3 个主题 subjectsCount 5

向下转型

向下转型,用类型转换操作符( as?as! )

class Subjects {
    var physics: String
    init(physics: String) {
        self.physics = physics
    }
}

class Chemistry: Subjects {
    var equations: String
    init(physics: String, equations: String) {
        self.equations = equations
        super.init(physics: physics)
    }
}

class Maths: Subjects {
    var formulae: String
    init(physics: String, formulae: String) {
        self.formulae = formulae
        super.init(physics: physics)
    }
}

let sa = [
    Chemistry(physics: "固体物理", equations: "赫兹"),
    Maths(physics: "流体动力学", formulae: "千兆赫"),
    Chemistry(physics: "热物理学", equations: "分贝"),
    Maths(physics: "天体物理学", formulae: "兆赫"),
    Maths(physics: "微分方程", formulae: "余弦级数")]


let samplechem = Chemistry(physics: "固体物理", equations: "赫兹")
print("实例物理学是: \(samplechem.physics)")
print("实例方程式: \(samplechem.equations)")


let samplemaths = Maths(physics: "流体动力学", formulae: "千兆赫")
print("实例物理学是: \(samplemaths.physics)")
print("实例公式是: \(samplemaths.formulae)")

var chemCount = 0
var mathsCount = 0

for item in sa {
    // 类型转换的条件形式
    if let show = item as? Chemistry {
        print("化学主题是: '\(show.physics)', \(show.equations)")
        // 强制形式
    } else if let example = item as? Maths {
        print("数学主题是: '\(example.physics)',  \(example.formulae)")
    }
}
//实例物理学是: 固体物理
//实例方程式: 赫兹
//实例物理学是: 流体动力学
//实例公式是: 千兆赫
//化学主题是: '固体物理', 赫兹
//数学主题是: '流体动力学',  千兆赫
//化学主题是: '热物理学', 分贝
//数学主题是: '天体物理学',  兆赫
//数学主题是: '微分方程',  余弦级数

Any

Swift 为不确定类型提供了两种特殊类型别名:

// 可以存储Any类型的数组 exampleany
var exampleany = [Any]()

exampleany.append(12)
exampleany.append(3.14159)
exampleany.append("Any 实例")
exampleany.append(Chemistry(physics: "固体物理", equations: "兆赫"))

for item2 in exampleany {
    switch item2 {
    case let someInt as Int:
        print("整型值为 \(someInt)")
    case let someDouble as Double where someDouble > 0:
        print("Pi 值为 \(someDouble)")
    case let someString as String:
        print("\(someString)")
    case let phy as Chemistry:
        print("主题 '\(phy.physics)', \(phy.equations)")
    default:
        print("None")
    }
}

AnyObject

class NotSubjects {
    var num:Int = 100
}

let saprint: [AnyObject] = [
    Chemistry(physics: "固体物理", equations: "赫兹"),
    Maths(physics: "流体动力学", formulae: "千兆赫"),
    Chemistry(physics: "热物理学", equations: "分贝"),
    Maths(physics: "天体物理学", formulae: "兆赫"),
    Maths(physics: "微分方程", formulae: "余弦级数"),
    NotSubjects()
]

扩展

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

扩展可以对一个类型添加新的功能,但是不能重写已有的功能。

扩展声明使用关键字 extension:

extension SomeType {
    // 加到SomeType的新功能写到这里
}

一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议,语法格式如下:

extension SomeType: SomeProtocol, AnotherProctocol {
    // 协议实现写到这里
}

计算型属性

扩展可以向已有类型添加计算型实例属性和计算型类型属性。

extension Int {
   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)")
    
let subtraction = 120.sub
print("减法运算后的值:\(subtraction)")
    
let multiplication = 39.mul
print("乘法运算后的值:\(multiplication)")
    
let division = 55.div
print("除法运算后的值: \(division)")

let mix = 30.add + 34.sub
print("混合运算结果:\(mix)")
// 加法运算后的值:103
// 减法运算后的值:110
// 乘法运算后的值:390
// 除法运算后的值: 11
// 混合运算结果:154

构造器

扩展可以向已有类型添加新的构造器。

这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。

扩展可以向类中添加新的便利构造器 init(),但是它们不能向类中添加新的指定构造器或析构函数 deinit()

struct sum {
    var num1 = 100, num2 = 200
}

struct diff {
    var no1 = 200, no2 = 100
}

struct mult {
    var a = sum()
    var b = diff()
}

extension mult {
    init(x: sum, y: diff) {
        _ = x.num1 + x.num2
        _ = y.no1 + y.no2
    }
}

let a = sum(num1: 100, num2: 200)
let b = diff(no1: 200, no2: 100)

let getMult = mult(x: a, y: b)
print("getMult sum\((getMult.a.num1, getMult.a.num2))")
print("getMult diff\((getMult.b.no1, getMult.b.no2))")
// getMult sum(100, 200)
// getMult diff(200, 100)

方法

扩展可以向已有类型添加新的实例方法和类型方法。

extension Int {
   func topics(summation: () -> ()) {
      for _ in 0..<self {
         summation()
      }
   }
}

4.topics(){
   print("扩展模块内")
}
// 扩展模块内
// 扩展模块内
// 扩展模块内
// 扩展模块内

3.topics(){
   print("内型转换模块内")
}
// 内型转换模块内
// 内型转换模块内
// 内型转换模块内

可变实例方法

通过扩展添加的实例方法也可以修改该实例本身。

结构体和枚举类型中修改self或其属性的方法必须将该实例方法标注为mutating,正如来自原始实现的修改方法一样。

extension Double {
   mutating func square() {
      let pi = 3.1415
      self = pi * self * self
   }
}

var Trial1 = 3.3
Trial1.square()
print("圆的面积为: \(Trial1)")
// 圆的面积为: 34.21093499999999

var Trial2 = 5.8
Trial2.square()
print("圆的面积为: \(Trial2)")
// 圆的面积为: 105.68006

var Trial3 = 120.3
Trial3.square()
print("圆的面积为: \(Trial3)")
// 圆的面积为: 45464.070735

下标

扩展可以向一个已有类型添加新下标。

extension Int {
    subscript(multtable: Int) -> Int {
        var powerOf10 = 1
        var index = multtable
        while index > 0 {
            powerOf10 *= 10
            index -= 1
        }
        return (self / powerOf10) % 10
    }
}

print(12[0])     // 输出:2
print(7869[1])   // 输出:6
print(786543[2]) // 输出:5

嵌套类型

扩展可以向已有的类、结构体和枚举添加新的嵌套类型:

extension Int {
    enum calc
    {
        case add
        case sub
        case mult
        case div
        case anything
    }
    
    var print: calc {
        switch self
        {
        case 0:
            return .add
        case 1:
            return .sub
        case 2:
            return .mult
        case 3:
            return .div
        default:
            return .anything
        }
    }
}

func result(numb: [Int]) {
    for i in numb {
        switch i.print {
        case .add:
            print(" 10 ")
        case .sub:
            print(" 20 ")
        case .mult:
            print(" 30 ")
        case .div:
            print(" 40 ")
        default:
            print(" 50 ")
            
        }
    }
}

result(numb: [0, 1, 2, 3, 4, 7])
// 10 20 30 40 50 50

协议

协议规定了用来实现某一特定功能所必需的方法和属性。有点像 Interface。

// 定义协议
protocol SomeProtocol {
    // 这里可以定义协议要求
    var someProperty: Int { get set }
    func someMethod()
}

// 遵循协议
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 结构体内容
}

// 遵循协议同时有父类
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
    // 类的内容
}

对属性的规则

协议用于指定特定的实例属性或类属性,而不用指定是存储型属性或计算型属性。此外还必须指明是只读的还是可读可写的。

协议中的通常用 var 来声明变量属性,在类型声明后加上 { set get } 来表示属性是可读可写的,只读属性则用 { get } 来表示。

protocol ClassA {
    var marks: Int { get set }
    var result: Bool { get }
    
    func attendance() -> String
    func markssecured() -> String
}

protocol ClassB: ClassA {
    var present: Bool { get set }
    var subject: String { get set }
    var stname: String { get set }
}

class ClassC: ClassB {
    var marks = 96
    let result = true
    var present = false
    var subject = "Swift 协议"
    var stname = "Protocols"
    
    func attendance() -> String {
        return "The \(stname) has secured 99% attendance"
    }
    
    func markssecured() -> String {
        return "\(stname) has scored \(marks) marks"
    }
}

let studdet = ClassC()
studdet.stname = "Swift"
studdet.marks = 98

print(studdet.markssecured())
print(studdet.attendance())
print("Marks: \(studdet.marks)")
print("Result: \(studdet.result)")
print("Present: \(studdet.present)")
print("Subject: \(studdet.subject)")
print("Student Name: \(studdet.stname)")

对 Mutating 方法的规定

protocol Daysofaweek {
    mutating func show()
}

enum Days: Daysofaweek {
    case sun, mon, tue, wed, thurs, fri, sat

    mutating func show() {
        switch self {
        case .sun:
            self = .sun
            print("Sunday")
        case .mon:
            self = .mon
            print("Monday")
        case .tue:
            self = .tue
            print("Tuesday")
        case .wed:
            self = .wed
            print("Wednesday")
        case .thurs:
            self = .thurs
            print("Thursday")
        case .fri:
            self = .fri
            print("Friday")
        case .sat:
            self = .sat
            print("Saturday")
        }
    }
}

var res = Days.wed
res.show()
// Wednesday

对构造器的规定

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

protocol SomeProtocol {
   init(someParameter: Int)
}
protocol tcpprotocol {
   init(aprot: Int)
}

协议构造器规定在类中的实现

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

protocol SomeProtocol {
   init(someParameter: Int)
}

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
      // 构造器实现
   }
}

protocol tcpprotocol {
   init(aprot: Int)
}

class tcpClass: tcpprotocol {
   required init(aprot: Int) {
   }
}

继承加协议

protocol TcpProtocol {
    init(no1: Int)
}

class MainClass {
    var no1: Int // 局部变量
    init(no1: Int) {
        self.no1 = no1 // 初始化
    }
}

class SubClass: MainClass, TcpProtocol {
    var no2: Int
    
    init(no1: Int, no2: Int) {
        self.no2 = no2
        super.init(no1: no1)
    }
    
    // 因为遵循协议,需要加上"required"; 因为继承自父类,需要加上"override"
    required override convenience init(no1: Int) {
        self.init(no1: no1, no2: 0)
    }
}

let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)

print("res is: \(res.no1)")
print("show.no1 is: \(show.no1)")
print("show.no2 is: \(show.no2)")

协议类型

尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。

// 在 Swift 中,associatedtype(关联类型)是协议中用于声明一个占位类型的机制,
// 它允许协议本身不指定具体的类型,而是让遵循该协议的类型在实现时具体指定。
protocol Generator {
    associatedtype Members
    func next() -> Members?
}

// 示例代码使用 Swift 标准库中的迭代器和 `map` 函数

// 使用数组的迭代器
var items = [10, 20, 30].makeIterator()
while let x = items.next() {
    print(x)
}

// 使用 `map` 函数
for list in [1, 2, 3].map({ i in i * 5 }) {
    print(list)
}

// 直接打印数组
print([100, 200, 300])
print([1, 2, 3].map({ i in i * 10 }))

在扩展中添加协议成员

可以可以通过扩展来扩充已存在类型( 类,结构体,枚举等)。

扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。

protocol AgeClassificationProtocol {
    var age: Int { get }
    func agetype() -> String
}

class Person {
    let firstname: String
    let lastname: String
    var age: Int
    
    init(firstname: String, lastname: String, age: Int = 10) {
        self.firstname = firstname
        self.lastname = lastname
        self.age = age
    }
}

extension Person: AgeClassificationProtocol {
    func fullname() -> String {
        return "\(firstname) \(lastname)"
    }
    
    func agetype() -> String {
        switch age {
        case 0...2:
            return "Baby"
        case 3...12: // 修正为从3开始
            return "Child"
        case 13...19:
            return "Teenager"
        case let x where x > 65:
            return "Elderly"
        default:
            return "Normal"
        }
    }
}

// 测试代码
let person1 = Person(firstname: "John", lastname: "Doe", age: 25)
let person2 = Person(firstname: "Jane", lastname: "Smith", age: 70)
let person3 = Person(firstname: "Baby", lastname: "Yoda", age: 1)

print("\(person1.fullname()) is a \(person1.agetype())")
print("\(person2.fullname()) is a \(person2.agetype())")
print("\(person3.fullname()) is a \(person3.agetype())")

协议的继承

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

协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // 协议定义
}
// 基础协议:最简日志能力
protocol BasicLogging {
    func log(message: String)
}

// 定义日志级别枚举(放在协议外)
enum LogLevel {
    case info, warning, error
}

// 升级协议:继承 BasicLogging,并添加日志级别支持
protocol DetailedLogging: BasicLogging {
    func log(level: LogLevel, message: String)
}

// 扩展协议:为所有 DetailedLogging 默认实现 log(message:)
extension DetailedLogging {
    func log(message: String) {
        log(level: .info, message: message)  // 默认用 info 级别
    }
}

// 具体实现:控制台日志
class ConsoleLogger: DetailedLogging {
    func log(level: LogLevel, message: String) {
        let prefix: String
        switch level {
        case .info:    prefix = "[INFO]"
        case .warning: prefix = "[WARNING]"
        case .error:   prefix = "[ERROR]"
        }
        print("\(prefix) \(message)")
    }
}

// 具体实现:带时间戳的日志(可选组合)
class TimestampedLogger: BasicLogging {
    func log(message: String) {
        // ...
    }
}

类专属协议

可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。

该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。格式如下:

protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
    // 协议定义
}

普通协议

protocol Identifiable {
    var id: Int { get }
}

struct UserStruct: Identifiable {
    let id: Int
}

class UserClass: Identifiable {
    let id: Int
    init(id: Int) { self.id = id }
}

enum UserRole: Identifiable {
    case guest(id: Int)
    var id: Int {
        switch self {
        case .guest(let id): return id
        }
    }
}

// ✅ 全部合法!
let u1 = UserStruct(id: 1)
let u2 = UserClass(id: 2)
let u3 = UserRole.guest(id: 3)

类专属协议

protocol IdentifiableClass: class, Identifiable {
    // 或者:protocol IdentifiableClass: class {
    //     var id: Int { get }
    // }
    // 这里继承了上面的 Identifiable,演示组合用法
}

// ✅ 类可以遵循
class AdminUser: IdentifiableClass {
    let id: Int
    init(id: Int) { self.id = id }
}

// ❌ 结构体不能遵循 —— 编译报错!
// struct AdminStruct: IdentifiableClass { ... }
// error: Type 'AdminStruct' does not conform to protocol 'IdentifiableClass'

// ❌ 枚举也不行
// enum AdminEnum: IdentifiableClass { ... }
// error: Enum cannot inherit from class-only protocol 'IdentifiableClass'

协议合成

支持合成多个协议,这在我们需要同时遵循多个协议时非常有用。

protocol Stname {
    var name: String { get }
}

protocol Stage {
    var age: Int { get }
}

struct Person: Stname, Stage {
    var name: String
    var age: Int
}

func show(celebrator: Stname & Stage) {
    print("\(celebrator.name) is \(celebrator.age) years old")
}

let studname = Person(name: "Priya", age: 21)
show(celebrator: studname)
// Priya is 21 years old

let stud = Person(name: "Rehan", age: 29)
show(celebrator: stud)
// Rehan is 29 years old

let student = Person(name: "Roshan", age: 19)
show(celebrator: student)
// Roshan is 19 years old

检验协议的一致性

可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。

import Foundation

// 定义一个协议
protocol Drivable {
    func drive()
}

// 定义遵循协议的类型
struct Car: Drivable {
    func drive() {
        print("🚗 Car is driving")
    }
}

struct Bike: Drivable {
    func drive() {
        print("🚲 Bike is riding")
    }
}

// 一个不遵循协议的类型
struct Robot {
    func speak() {
        print("🤖 Robot is speaking")
    }
}

// 测试对象
let car = Car()
let bike = Bike()
let robot = Robot()

// —— is 操作符:检查是否遵循协议 ——
if car is Drivable {
    print("✅ car 遵循 Drivable 协议")
}

if robot is Drivable {
    print("❌ robot 遵循 Drivable 协议") // 不会执行
} else {
    print("❌ robot 不遵循 Drivable 协议")
}

// —— as? 操作符:安全转换(可选绑定)——
if let driver = bike as? Drivable {
    driver.drive() // ✅ 成功转换并调用
}

if let maybeDriver = robot as? Drivable {
    maybeDriver.drive() // 不会执行
} else {
    print("⚠️ robot 无法转换为 Drivable(返回 nil)")
}

// —— as 操作符:强制转换(失败会崩溃)——
// 仅在确定类型安全时使用!
let forcedCar: Drivable = car as Drivable
forcedCar.drive() // ✅ 正常工作

// 危险操作:若类型不匹配会运行时报错!
// let forcedRobot: Drivable = robot as Drivable // ❌ 运行时崩溃!

泛型

// 定义一个交换两个变量的函数
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numb1 = 100
var numb2 = 200
 
print("交换前数据: \(numb1) 和 \(numb2)")
swapTwoInts(&numb1, &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")

利用泛型

// 定义一个交换两个变量的函数
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numb1 = 100
var numb2 = 200
 
print("交换前数据:  \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交换后数据: \(numb1) 和 \(numb2)")
 
var str1 = "A"
var str2 = "B"
 
print("交换前数据:  \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交换后数据: \(str1) 和 \(str2)")

泛型的栈

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
 
var stackOfStrings = Stack<String>()
print("字符串元素入栈: ")
stackOfStrings.push("google")
stackOfStrings.push("runoob")
print(stackOfStrings.items);
 
let deletetos = stackOfStrings.pop()
print("出栈元素: " + deletetos)
 
var stackOfInts = Stack<Int>()
print("整数元素入栈: ")
stackOfInts.push(1)
stackOfInts.push(2)
print(stackOfInts.items);

扩展泛型类型

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 stackOfStrings = Stack<String>()
print("字符串元素入栈: ")
stackOfStrings.push("google")
stackOfStrings.push("runoob")
 
if let topItem = stackOfStrings.topItem {
    print("栈中的顶部元素是:\(topItem).")
}
 
print(stackOfStrings.items)

类型约束语法

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

上面这个函数有两个类型参数。第一个类型参数 T,有一个要求 T 必须是 SomeClass 子类的类型约束;第二个类型参数 U,有一个要求 U 必须符合 SomeProtocol 协议的类型约束。

关联类

Swift 中使用 associatedtype 关键字来设置关联类型实例。下面例子定义了一个 Container 协议,该协议定义了一个关联类型 ItemType。

// Container 协议
protocol Container {
    associatedtype ItemType
    // 添加一个新元素到容器里
    mutating func append(_ item: ItemType)
    // 获取容器中元素的数
    var count: Int { get }
    // 通过索引值类型为 Int 的下标检索到容器中的每一个元素
    subscript(i: Int) -> ItemType { get }
}

// Stack 结构体遵从 Container 协议
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]
    }
}

var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)

where 语句

可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。

// Container 协议
protocol Container {
    associatedtype ItemType
    // 添加一个新元素到容器里
    mutating func append(_ item: ItemType)
    // 获取容器中元素的数
    var count: Int { get }
    // 通过索引值类型为 Int 的下标检索到容器中的每一个元素
    subscript(i: Int) -> ItemType { get }
}

// 遵循Container协议的泛型TOS类型
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]
    }
}

// 扩展,将 Array 当作 Container 来使用
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
        }
    }
    
    // 所有元素都匹配,返回 true
    return true
}

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


var aos = ["google", "runoob", "taobao"]

// 如果 tos 为 Stack<String> 这里就不行因为where不满足
if allItemsMatch(tos, aos) {
    print("匹配所有元素")
} else {
    print("元素不匹配")
}

访问控制

访问控制可以限定其他源文件或模块中代码对你代码的访问级别。

可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。

协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。

访问控制基于模块与源文件。

模块指的是以独立单元构建和发布的 Framework 或 Application。在 Swift 中的一个模块可以使用 import 关键字引入另外一个模块。

源文件是单个源码文件,它通常属于一个模块, 源文件可以包含多个类和函数 的定义。

访问级别 定义
public 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。
internal 可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。
fileprivate 文件内私有,只能在当前源文件中使用。
private 只能在类中访问,离开了这个类或者结构体的作用域外面就无法访问。
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
 
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}

除非有特殊的说明,否则实体都使用默认的访问级别 internal

函数类型访问权限

数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。

下面的例子定义了一个名为someFunction全局函数,并且没有明确地申明其访问级别。

func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 函数实现
}

函数中其中一个类 SomeInternalClass 的访问级别是 internal,另一个 SomePrivateClass 的访问级别是 private。所以根据元组访问级别的原则,该元组的访问级别是 private(元组的访问级别与元组中访问级别最低的类型一致)。 因为该函数返回类型的访问级别是 private,所以你必须使用 private 修饰符,明确的声明该函数:

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 函数实现
}

将该函数申明为 public 或 internal,或者使用默认的访问级别 internal 都是错误的,因为如果这样你就无法访问 private 级别的返回值。

枚举类型访问权限

枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。

// 枚举 Student 被明确的申明为 public 级别,那么它的成员 Name,Mark 的访问级别同样也是 public:
public enum Student {
    case Name(String)
    case Mark(Int,Int,Int)
}
 
var studDetails = Student.Name("Swift")
var studMarks = Student.Mark(98,97,95)
 
switch studMarks {
case .Name(let studName):
    print("学生名: \(studName).")
case .Mark(let Mark1, let Mark2, let Mark3):
    print("学生成绩: \(Mark1),\(Mark2),\(Mark3)")
}

子类访问权限

子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是 internal,子类的访问级别就不能申明为 public。

public class SuperClass {
    fileprivate func show() {
        print("超类")
    }
}
 
// 访问级别不能高于超类 public > internal
internal class SubClass: SuperClass  {
    override internal func show() {
        print("子类")
    }
}
 
let sup = SuperClass()
sup.show()
 
let sub = SubClass()
sub.show()

常量、变量、属性、下标访问权限

常量、变量、属性不能拥有比它们的类型更高的访问级别。

你定义一个public级别的属性,但是它的类型是private级别的,这是编译器所不允许的。 同样,下标也不能拥有比索引类型或返回类型更高的访问级别。

如果常量、变量、属性、下标索引的定义类型是private级别的,那么它们必须要明确的申明访问级别为private:

private var privateInstance = SomePrivateClass()

Getter 和 Setter 访问权限

常量、变量、属性、下标索引的Getters和Setters的访问级别继承自它们所属成员的访问级别。

Setter的访问级别可以低于对应的Getter的访问级别,这样就可以控制变量、属性或下标索引的读写权限。

private(set) —— 外部只读,内部可写(最常用场景)

class BankAccount {
    // 对外:只读(getter 是 internal),对内:可读写(setter 是 private)
    private(set) var balance: Int = 0

    func deposit(amount: Int) {
        guard amount > 0 else { return }
        balance += amount  // ✅ 类内部可以修改
    }
}

let account = BankAccount()
account.deposit(amount: 100)
// print(account.balance)  // ✅ 可读(外部能访问 getter)
// account.balance = 500    // ❌ 编译错误:'balance' setter is inaccessible

显式定义 private setter(手动控制)

struct User {
    private(set) var name: String  // 同上,等价于 private(set)
    private var _age: Int = 0     // 真实存储用私有变量

    var age: Int {
        get { _age }              // 公开读取
        private set {             // ✅ 显式限制 setter 为 private
            guard value >= 0 else { return }
            _age = value
        }
    }
}

计算属性 + 独立访问控制

class Temperature {
    private var celsius: Double = 0

    var fahrenheit: Double {
        get { celsius * 9 / 5 + 32 }
        private set { celsius = (newValue - 32) * 5 / 9 }  // ✅ setter 私有
    }
}

let t = Temperature()
t.fahrenheit = 98.6  // ❌ 编译错误:fahrenheit 的 setter 是 private
print(t.fahrenheit)  // ✅ OK

构造器和默认构造器访问权限

可以给自定义的初始化方法申明访问级别,但是要不高于它所属类的访问级别。但必要构造器例外,它的访问级别必须和所属类的访问级别相同。

如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。

Swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体值。 默认初始化方法的访问级别与所属类型的访问级别相同。

协议访问权限

如果想为一个协议明确的申明访问级别,那么需要注意一点,就是你要确保该协议只在你申明的访问级别作用域中使用。

如果你定义了一个public访问级别的协议,那么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型,比如,public访问级别的其他类型,他们成员的访问级别为internal。

扩展访问权限

可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如你扩展了一个公共类型,那么你新加的成员应该具有和原始成员一样的默认的internal访问级别。

或者,你可以明确申明扩展的访问级别(比如使用private extension)给该扩展内所有成员申明一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所申明的访问级别所覆盖。

泛型访问权限

泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。

public struct TOS<T> {
    var items = [T]()
    private mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
}
 
var tos = TOS<String>()
tos.push("Swift") // 'push' is inaccessible due to 'private' protection level

类型别名

任何你定义的类型别名都会被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。

比如说,一个private级别的类型别名可以设定给一个public、internal、private的类型,但是一个public级别的类型别名只能设定给一个public级别的类型,不能设定给internal或private 级别的类型。

public protocol Container {
    typealias ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}
 
struct Stack<T>: Container {
    // original Stack<T> implementation
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
    
    // conformance to the Container protocol
    mutating func append(item: T) {
        self.push(item)
    }
    
    var count: Int {
        return items.count
    }
    
    subscript(i: Int) -> T {
        return items[i]
    }
}
 
func allItemsMatch<
    C1: Container, C2: Container
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
    (someContainer: C1, anotherContainer: C2) -> Bool {
        // check that both containers contain the same number of items
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        // check each pair of items to see if they are equivalent
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // all items match, so return true
        return true
}
 
var tos = Stack<String>()
tos.push("Swift")
print(tos.items)
 
tos.push("泛型")
print(tos.items)
 
tos.push("Where 语句")
print(tos.items)
 
var eos = ["Swift", "泛型", "Where 语句"]
print(eos)