函数
func execFunc() {
print()
print(TempFunc(x: nil, 2.0))
print(testTuple().firstName, testTuple().lastName) // 张 三
print(testTuple1(firstNum: 3, sencondNum: 5).0, testTuple1(firstNum: 3, sencondNum: 5).1) // 8 -2
print(sum(3, 5, 7, 9, 11)) // 35
var m = 200
tempInout(num: &m)
print(m) // 300
print(getLeft("今天很不错", number: 1)) // 今天
}
// 定义函数
// 无返回 ->void也可不写,无return
// Float = 10,可以给参数添加默认值
func TempFunc(x: Int?, _: Float = 10) -> Bool {
guard let i = x else {
print("x参数没有值")
return false
}
print("x参数有值")
return true
}
// 返回元组
func testTuple() -> (firstName: String, lastName: String) {
return ("张", "三")
}
// 标签名,firstNum外面易读,不关心m内部使用
func testTuple1(firstNum m: Int, sencondNum n: Int) -> (Int, Int) {
return (m + n, m - n)
}
// 多参数,外部无标签可是一个数组,内部number使用
func sum(_ numbers: Int...) -> Int {
var sum = 0
print(type(of: numbers)) // Array<Int>
numbers.forEach {
sum += $0
}
return sum
}
// inout 注意传入变量的地址,
// 参数的必须是变量,不能是常量或字面量
// 不可以一个变量同时传递给2个输入输出参数
// 安全性:在函数结束之前,传入的变量的值不能被其他操作影响。
func tempInout(num: inout Int) {
num += 100
}
// 左截函数
func getLeft(_ inString: String, number num: Int) -> String {
var s = inString[inString.startIndex ... inString.index(inString.startIndex, offsetBy: num)]
return String(s) // Substring转化成String
}
方法
枚举、结构体、类都可以定义实例方法、类型方法
1 实例方法(Instance Method):通过实例对象调用
2 类型方法(Type Method):通过类型调用,用static或者class关键字定义
class CountNum {
nonisolated(unsafe) static var count = 0
init() {
CountNum.count += 1
}
static func getCount() -> Int {
count
}
}
let c0 = CountNum()
let c1 = CountNum()
let c2 = CountNum()
print(CountNum.getCount()) // 3
self
在实例方法中代表实例对象
在类型方法中代表类型
在类型方法static func getCount() 中
count等价于self.count、CountNum.self.count、CountNum.count
mutating
1 结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改
类是不存在这个问题
2 在func关键字前加mutating可以允许这种修改行为
在下例中我们不添加mutating,会报错:Left side of mutating operator isn't mutable: 'self' is immutable
struct PointT {
var x = 0.0, y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
// self = Point(x: x + deltaX, y: y + deltaY)
}
var detaX: Double {
set { x += newValue }
get { x }
}
var deltaY: Double {
set { y += newValue }
get { y }
}
}
我们想直接修改x属性方法一
var a = PointT()
a.moveBy(deltaX: 10, deltaY: 10) // 10.0 10.0
print(a.x, a.y)
a.moveBy(deltaX: 20, deltaY: 30) // 30.0 40.0
print(a.x, a.y)
方法二
var b = PointT()
b.detaX = 10
b.deltaY = 10
print(b.x, b.y) // 10.0 10.0
b.detaX = 20
b.deltaY = 30
print(b.x, b.y) // 30.0 40.0
枚举也是这样
enum Positon {
case pointX, pointY
mutating func change() {
switch self {
case .pointX:
self = .pointY
case .pointY:
self = .pointX
}
}
}
@discardableResult
在func前面加个@discardableResult,可以消除:函数调用后返回值未被使用的警告⚠
在下例中如果不添加@discardableResult,返回的元组没有被用到会提示:Result of call to 'moveBy(deltaX:deltaY:)' is unused
struct PointM {
var x = 0.0, y = 0.0
@discardableResult mutating func moveBy(deltaX: Double, deltaY: Double) -> (Double, Double) {
x += deltaX
y += deltaY
// self = Point(x: x + deltaX, y: y + deltaY)
return (x, y)
}
}
var p = PointM()
p.moveBy(deltaX: 10, deltaY: 10) //Result of call to 'moveBy(deltaX:deltaY:)' is unused
下标(subscript)
使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
subscript的语法类似于实例方法、计算属性,本质就是方法\函数(set, get)
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
p[0] = 11.1 //调用下标,set方法,index = 0
p[1] = 22.2
print(p.x) // 11.1
print(p.y) // 22.2
print(p[0]) // 11.1 //获取下标的get方法,index = 0
print(p[1]) // 22.2
注意:
subscript中定义的返回值类型决定了 (subscript(index: Int) -> Double中的Double)
1 get方法的返回值类型 (get方法中返回的类型是Double)
2 set方法中newValue的类型 (newValue的类型是Double)
下标参数不仅支持整型,也支持字符串等其他类型
subscript可以接受多个参数,并且类型任意
下标的细节
下面1和2两条细节和属性很像
1 subscript可以没有set方法,但必须要有get方法。否则会报错:Subscript with a setter must also have a getter
2 如果只有get方法,可以省略get
3 可以设置参数标签
subscript(index i: Int) -> Double { }
使用的时候也加上标签
p[index: 1]
4 下标可以是类型方法,并通过类型访问类方法
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum[10, 20]) // 30
接收多个参数的下标
class Grid {
var data = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
subscript(row: Int, column: Int) -> Int {
set {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return
}
data[row][column] = newValue
}
get {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
return 0
}
return data[row][column]
}
}
}
var grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data) //[[0, 77, 2], [3, 4, 88], [99, 7, 8]]
inout
输入输出函数(In-Out 参数)允许函数对传入的变量进行修改,并且这些修改会在函数调用后反映在原始变量上。
使用 inout 关键字:
在函数定义时,将参数声明为 inout 类型,表示该参数的值可以在函数内部修改,并且修改会影响到原始变量。
调用时需要使用 &:
当你调用一个接受 inout 参数的函数时,需要在传递参数时使用 & 符号,表示传递的是变量的引用,而不是值。
var n = 5 //这里需要用 必须用var 来声明 因为必须是变量 不能是常量 也不能是字面量
func sum(_ num :inout Int) {//inout 关键字将参数num 声明为输入输出参数
num = num + 1
}
sum(&n) //& 用来明确标记这个方法的调用是输入输出参数的函数 里面会对这个n 进行修改
print(n) //n 是 6
注意事项:
In-Out 参数不能是常量:传递给 In-Out 参数的必须是变量,不能是常量或字面量。
In-Out 参数会改变原始值:这意味着函数内部对 In-Out 参数的修改会直接影响外部传递的变量。
安全性:在函数结束之前,传入的变量的值不能被其他操作影响。
适用场景:
交换两个变量的值:如上例所示,使用 In-Out 参数可以很方便地交换两个变量的值。
累加或减少某个值:在需要修改多个变量时可以使用 In-Out 参数。
更改传递进来的数据:某些场景下你可能需要传递一个对象或数据,并在函数内部修改它。
安全性的理解:
In-Out 参数的安全性问题主要与多线程环境下的访问、不可变性以及变量生命周期等有关。
- 防止数据竞争和不一致性
In-Out 参数的机制是将变量的引用传递给函数,这意味着函数直接操作外部变量。如果在多线程环境下,多个线程同时访问或修改同一个变量,会引发数据竞争(Data Race)问题。为避免这种情况,Swift 对 In-Out 参数有一些限制,以提高安全性:
值拷贝行为:在函数执行时,Swift 会对 In-Out 参数进行临时拷贝,函数执行完毕后再将修改后的值赋回原始变量。这种机制可以避免在函数内部处理参数时,对该参数的其他操作产生冲突。
限制可变性:传递给 In-Out 参数的变量必须是变量(var),而不能是常量(let)。这确保了参数在传递前具有可变性。 - 禁止常量和字面量的修改
In-Out 参数不能传递常量或字面量,这是 Swift 设计中对数据安全的一种保障。具体体现在:
常量保护:如果你尝试传递常量或字面量给 In-Out 参数,编译器会报错。这防止了意外修改不应被修改的数据。
例如,以下代码会导致编译错误,因为 a 是常量:
let a = 10
var b = 20
swapTwoInts(&a, &b) //常量保护 编译错误:Cannot pass immutable value 'a' as inout argument
func changeValue(_ value: inout Int) {
value += 10
}
changeValue(&5) // 常量保护 编译错误:Cannot pass immutable value '5' as inout argument
- 控制变量的生命周期,避免并发访问问题
In-Out 参数的使用还涉及到变量的生命周期安全:
引用的时间限制:传递给 In-Out 参数的变量在函数调用时,会被临时锁定为不可访问的状态,直到函数执行完毕,防止函数内外对该变量进行并发访问。
这保证了变量在传入函数时不会被其他代码修改,从而防止数据不一致问题。 - 显式修改,避免隐式副作用:关键符号 &
In-Out 参数的修改是显式的,也就是说,在调用 In-Out 参数函数时,必须通过 & 符号来表明该参数是可修改的。这样设计可以提高代码的可读性和可维护性,因为开发者很清楚哪些参数会被修改,不写就会编译报错 - 编译器的静态检查,防止多个 In-Out 参数修改同一变量
func changeValues(_ a: inout Int, _ b: inout Int) {
a += 1
b += 1
}
var num = 5
changeValues(&num, &num) //不可以一个变量同时传递给2个输入输出参数 错误:Cannot pass the same variable as inout argument multiple times
上面的代码会导致编译错误,因为不能将同一个变量传递给多个 In-Out 参数,防止变量在函数内部同时被多个地方修改,从而造成数据冲突。
警告,inout 参数的修改才会对外部变量生效 ,因此 不要用常量,字面量传递给输入输出参数
inout的本质总结
1 如果实参有物理内存地址(比如存储属性、全局变量),且没有设置属性观察器
直接将实参的内存地址传入函数(实参进行引用传递)
2 如果实参是计算属性 或者 设置了属性观察器
采取了Copy In Copy Out的做法
第一步调用该函数时,先复制实参的值,产生副本【get】
第二步将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
第三步函数返回后,再将副本的值覆盖实参的值【set】
总结:inout的本质就是引用传递(地址传递)
多态
多态:父类指针指向子类对象
多态的实现原理:
1 OC: Runtime
2 C++: 虚表(虚函数表)
3 Swift: 类信息查找函数表
struct调用函数和class调用函数的区别:
struct调用函数编译阶段就可以确定,直接去call 固定的函数地址。
class对象调用函数需要确定是父类还是自己的函数。首先根据前8个字节找到存放类信息的内存地址,类信息里面的方法列表中保存着dog.speak,dog.eat,amimal.speak等等方法。类信息编译完就确定的,查找函数列表的偏移量也是固定的。根据内存偏移量查找函数列表,找到对应的函数。
类信息的内存在全局区。
类信息编译完就确定的。
animal1和animal2的前8个字节是一样的。
var animal1: Animal = Animal()
var anima2l: Animal = Animal()
class Animal {
class func speak() {
print("Animal speak")
}
}
class Dog: Animal {
override func speak() {
super.speak()
print("Dog Speak")
}
}
var animal: Animal = Animal()
animal = Dog()
animal.speak() //Animal speak Dog Speak
将方法赋值给var\let
方法也可以像函数那样,赋值给一个let或者var
注意我们下面的注释,尤其是fn的类型。
struct Person {
var age: Int
func run(_ v: Int) { print("func run", age, v) }
static func run(_ v: Int) { print("static func run", v) }
}
func execAccessControl() {
// let fn = Person.run(T##self: Person##Person)
// 首先我们先把run方法当成一个属性获取
// fn1的类型: @sendable (Person) -> (@sendable int ->()),接收一个person,返回一个函数
// 因为我们的静态run方法重名了,我们才需要给fn1加上类型,否则可以忽略类型
let fn1: (Person) -> ((Int) -> ()) = Person.run
// 然后我们获取类的实例对象fn2
// fn2的类型: @sendable (Int)-> ()
let fn2 = fn1(Person(age: 10))
// 最后实例对象直接调用run方法
fn2(20) // func run 10 20
// 系统也有提示
// let fn = Person.run(self:Person)
let fn = Person.run(Person(age: 20))
fn(17) // func run 20 17
let fnT = Person.run
fnT(99) // static func run 99
}
内联函数
内联函数在C++这个函数里是有的,那么在swift里面,怎么做的呢?swift内是不需要我们去声明这个函数为内联函数的。
如果开启了编译器优化(Realease 模式默认会开启优化),编译器会自动将某些函数变成内联函数。
我们打开项目。
Debug是没有优化的,Release是优化的.
选择target---> Build Settings ---> 输入optimization 如下图:
搜索一下,我们会看到有一个Optimization Level 优化级别,默认Debug情况下是NO Optimization(没有优化)。Release(打包的时候)是Optimization for Speed[-D]是有优化的。而且是speed是最快的,按照速度最快的方式去优化。如果我们开启了优化的话,它会自动将我们的某些函数变成内联函数。也就是说,Debug模式下,不会将你的函数,变成内联函数。Release就变成内联函数。Release发布版会自动将某些函数变成内联函数,也就意味这内联函数这种东西是有用的。肯定是可以优化我们程序的系统的。
如下例func test()优化后只有print("test")
func test() -> () {print("test")}
print("test")
内联函数作用:内联函数会自动将函数调用展开成函数体代码。说白了,是一个怎么样的函数呢?如果你这个test是一个内联函数的话,它会之间将你的函数调用,展开成函数体print("test")。这样就是一种优化,这样可以减少函数的调用开销,就不用开辟栈空间,撤销栈空间。
推荐资料
函数底层分析 https://www.jianshu.com/p/a8f691cf5046