swift基础_关键字
一.inout
- 我给函数传入一个参数,想在方法内部修改它,它报错了
Cannot assign to value: 'r' is a 'let' constant
,说我的r是let修饰的,不能更改
func inoutTest(_ r: Double) {
print("test")
r = 100
}
- 那我想改,咋整?
那就加个inout关键字吧,加完就好使了
func inoutTest(_ r: inout Double) {
print("test")
r = 100
}
- 为啥加完就好使了呢?
没加之前,r是个let修饰的属性
// inoutTest(_:)
sil hidden @main.inoutTest(Swift.Double) -> () : $@convention(thin) (Double) -> () {
// %0 "r" // user: %1
bb0(%0 : $Double):
debug_value %0 : $Double, let, name "r", argno 1 // id: %1
%2 = tuple () // user: %3
return %2 : $() // id: %3
} // end sil function 'main.inoutTest(Swift.Double) -> ()'
加完inout,我们看到$@convention(thin) (Double) -> ()
变成了$@convention(thin) (@inout Double)
,bb0(%0 : $Double):
变成了bb0(%0 : $*Double):
// inoutTest(_:)
sil hidden @main.inoutTest(inout Swift.Double) -> () : $@convention(thin) (@inout Double) -> () {
// %0 "r" // user: %1
bb0(%0 : $*Double):
debug_value_addr %0 : $*Double, var, name "r", argno 1 // id: %1
%2 = tuple () // user: %3
return %2 : $() // id: %3
} // end sil function 'main.inoutTest(inout Swift.Double) -> ()'
- 所以结论很简单:
给函数参数加上inout后,传参从值变成了指针。
值是从栈拿过来的暂用的,不可以修改的,而指针拿到的是值的地址,是可以修改的。
二.let & var
let 代表的是Constants,它不是啥单词的缩写官方文档
variable 美 [ˈveriəblˌˈværiəbl] adj. 易变的,多变的;可变的,可调节的;(数)(数字)变量的;
- 如果你想修改某个变量,那就用var修饰它。否则总是应该用let🥴(地道的英式汉语)
针对类
class YKPoint {
var x = 0.0
}
let p = YKPoint() //使用let修饰p,打印100
p.x = 100
print(p.x)
class YKPoint {
var x = 0.0
}
var p = YKPoint() //使用var修饰p,打印还是100
p.x = 100
print(p.x)
我们不管用let还是var修饰了p,都能正常输出是因为p的属性x是可变的
如果用let修饰了p,那么给p这个指针赋新值就会报错,用var则没有问题
看看struct
struct YKPoint {
var x = 0.0
}
var p = YKPoint() //使用var修饰p,打印100
p.x = 100
print(p.x)
struct YKPoint {
var x = 0.0
}
let p = YKPoint() //报错 ->Cannot assign to property: 'p' is a 'let' constant
p.x = 100
print(p.x)
我们看下源码,啥也看不出来。。。用let和var修饰,只有这两处不一样,其他的方法什么的都一样,甚至let修饰的也有getter&setter
使用let修饰
@_hasStorage @_hasInitialValue let p: YKPoint { get }
// p
sil_global hidden [let] @main.p : main.YKPoint : $YKPoint
使用var修饰
@_hasStorage @_hasInitialValue var p: YKPoint { get set }
// p
sil_global hidden @main.p : main.YKPoint : $YKPoint
三.mutating
mutate [ˈmjuːteɪt] 变化,产生突变
- 人家想改下x的值,但是编译器报错了
Left side of mutating operator isn't mutable: 'self' is immutable
,他说操作符 += 左边应该是个可变的,但现在self
却不是可变的
struct Point {
var x = 0.0
func moveBy(_ off: Double) {
self.x += off
}
}
图片.png
- 为啥呢?我们看下函数实现
moveBy会传进来两个参数,%0是Double类型的值,%1是self
而且self使用let修饰的。上面介绍let属性的时候就发现,使用let修饰的结构体是不能修改内部属性的,所以就会报错
// Point.moveBy(_:)
sil hidden @main.Point.moveBy(Swift.Double) -> () : $@convention(method) (Double, Point) -> () {
// %0 "off" // user: %2
// %1 "self" // user: %3
bb0(%0 : $Double, %1 : $Point):
debug_value %0 : $Double, let, name "off", argno 1 // id: %2
debug_value %1 : $Point, let, name "self", argno 2 // id: %3
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function 'main.Point.moveBy(Swift.Double) -> ()'
- 解决办法 价格mutating
struct Point {
var x = 0.0
mutating func moveBy(_ off: Double) {
self.x += off
}
}
看下内部实现,
1.$@convention(method) (Double, Point) -> ()
变成了$@convention(method) (Double, @inout Point) -> ()
, Point加了个inout
属性
2.self变成var了
3.看来加了mutating后编译器就调皮了的改了些东西
// Point.moveBy(_:)
sil hidden @main.Point.moveBy(Swift.Double) -> () : $@convention(method) (Double, @inout Point) -> () {
// %0 "off" // user: %2
// %1 "self" // user: %3
bb0(%0 : $Double, %1 : $*Point):
debug_value %0 : $Double, let, name "off", argno 1 // id: %2
debug_value_addr %1 : $*Point, var, name "self", argno 2 // id: %3
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function 'main.Point.moveBy(Swift.Double) -> ()'
- 总结:
所有的值类型,系统默认用let修饰,如果想要修改值类型,可以使用mutating让其突变
四.final
final关键字 不能被继承,不能被子类重写
五.@objc
import Foundation
class Dog: NSObject{
func eat() {
print("狗吃东西")
}
}
let dog = Dog()
dog.eat()
编写swift文件时,编译器会自动生成对应的target-Swift.h头文件,这样OC就能调用swift的类了
图片.png
但是如果你不给方法或者属性加@objc属性, 头文件里面是不会看到该方法的。
图片.png
给方法加上@objc再编译一下
import Foundation
class Dog: NSObject{
@objc func eat() {
print("狗吃东西")
}
}
let dog = Dog()
dog.eat()
就能看到swift的方法了
图片.png
我们看下sil,发现除了原有的eat方法,还生成了一个@objc eat方法,而这个@objc eat方法内部调用了swift的eat方法。
// Dog.eat()
sil hidden @main.Dog.eat() -> () : $@convention(method) (@guaranteed Dog) -> () {
// %0 "self" // user: %1
bb0(%0 : $Dog):
debug_value %0 : $Dog, let, name "self", argno 1 // id: %1
%2 = tuple () // user: %3
return %2 : $() // id: %3
} // end sil function 'main.Dog.eat() -> ()'
// @objc Dog.eat()
sil hidden [thunk] @@objc main.Dog.eat() -> () : $@convention(objc_method) (Dog) -> () {
// %0 // users: %4, %3, %1
bb0(%0 : $Dog):
strong_retain %0 : $Dog // id: %1
// function_ref Dog.eat()
%2 = function_ref @main.Dog.eat() -> () : $@convention(method) (@guaranteed Dog) -> () // user: %3
%3 = apply %2(%0) : $@convention(method) (@guaranteed Dog) -> () // user: %5
strong_release %0 : $Dog // id: %4
return %3 : $() // id: %5
} // end sil function '@objc main.Dog.eat() -> ()'
六.dynamic
- dynamic -> swift的方法替换,如果继承NSObject会崩溃
- 告诉编译器这个方法是可能被动态调用的,需要将其添加到查找表中, 原理还得学习
在swift中使用kvo需要把属性加上dynamic,否则不起作用
dynamically replaceable variable for LYK_Target.Dog.eat() -> ()
symbol stub for: swift_getFunctionReplacement
import Foundation
class Dog: NSObject{
dynamic func eat() {
}
}
let dog = Dog()
dog.eat()
demo: 在分类中替换原来的eat方法
import Foundation
class Dog{
dynamic func eat() {
print("eat")
}
}
extension Dog {
@_dynamicReplacement(for: eat)
func dynamicEat() {
print("dynamicEat")
}
}
let dog = Dog()
dog.eat() //dynamicEat
七.@objc + dynamic
将eat方法编译成objc_msgSend
方法, 直接走OC底层那一套
0x100003df1 <+49>: callq 0x100003e92 ; symbol stub for: objc_msgSend
八.weak&unowned
这是一个循环引用的例子,因为cls->block->cls循环引用,所以是不会打印LYKClass deinit
的
class LYKClass {
var count = 0
var block: (()->())? = nil
deinit {
print("LYKClass deinit")
}
}
let cls = LYKClass()
cls.block = {
cls.count += 1;
}
我们可以使用weak修饰一下,报错了Value of optional type 'LYKClass?' must be unwrapped to refer to member 'count' of wrapped base type 'LYKClass'
,说我使用weak后,cls有可能为空,需要cls后加个?
let cls = LYKClass()
cls.block = {[weak cls] in
cls.count += 1; 正确写法:cls?.count += 1;
}
//加weak会调用 weakinit方法
如果我们换成unowned
呢?
不用加?了,可以直接编译通过
let cls = LYKClass()
cls.block = {[unowned cls] in
cls.count += 1;
}
- 总结:
weak 弱引用,对象释放后该指针置nil
unowned 也是弱引用,但是对象释放后该指针不变,容易造成野指针错误
九. .self&.Type
对象 -> 类 -> 元类
class LYKClass {
var age: Int = 10
var name: String = "water"
}
let lykCls = LYKClass()
var s1 = lykCls.self
var s2 = LYKClass.self
var s3 = lykCls.self.self.self
print(s1) //macSwiftDemo.LYKClass,代表s1是个LYKClass类型
print(s2) //type metadata for macSwiftDemo.LYKClass,代表s2是个元类
print(s3) //macSwiftDemo.LYKClass,和s1一样,这是因为
//po lykCls:<LYKClass: 0x10113c200> po s1:<LYKClass: 0x10113c200>
//lykCls = s1,所以再多的self还是自己
- LYKClass.Type 类型, *.self属于.Type的一种
十.indirect
如果枚举内部迭代了枚举,需要用到indirect
indirect enum Suanfa{
case number(Int)
case add(Suanfa, Suanfa)
case sub(Suanfa, Suanfa)
}
func calculate(_ yourSuanfa: Suanfa) -> Int{
switch yourSuanfa{
case let .number(i):
return i
case let .add(s1, s2):
return calculate(s1) + calculate(s2)
case let .sub(s1, s2):
return calculate(s1) - calculate(s2)
}
}
let num1 = Suanfa.number(10)
let num2 = Suanfa.number(20)
let add = Suanfa.add(num1, num2)
let sub = Suanfa.sub(add, Suanfa.number(5))
let fianlNum = calculate(sub)
print(fianlNum)//25
本质上是开辟堆空间来存放枚举的值
图片.png
FinalQuestion
- let p = YKPoint() p.x = 100 报错 ->Cannot assign to property: 'p' is a 'let' constant,为什么用let修饰的结构体不能修改内部属性呢?
1.这是个编译器特性吗? 如果绕过编译器,是不是能正常访问?这个得研究下编译器
2.我猜大概率是,如果是let p = YKPoint(),编译器检测到是let就报错。
有一个现象,var p = YKPoint(),和et p = YKPoint()生成的sil基本上是一样的,都有getter&setter,如果let不让改属性,那么为啥还要生成getter&setter呢?多浪费啊
3.怎么验证?修改下编译器,去掉这个判断,然后运行,看能否正常修改属性
// YKPoint.x.getter
sil hidden [transparent] @main.YKPoint.x.getter : Swift.Double : $@convention(method) (YKPoint) -> Double {
// %0 "self" // users: %2, %1
bb0(%0 : $YKPoint):
debug_value %0 : $YKPoint, let, name "self", argno 1 // id: %1
%2 = struct_extract %0 : $YKPoint, #YKPoint.x // user: %3
return %2 : $Double // id: %3
} // end sil function 'main.YKPoint.x.getter : Swift.Double'
// YKPoint.x.setter
- inout和mutating有啥区别
其实他俩底层都一样,都是传指针地址。
不同的是,
inout修饰的是函数里面的参数,
mutating修饰的是结构体(等值引用类型)的方法,本质上是修饰了其内部的self、property等 - dynamic底层原理是啥?