美文网首页
swift基础_关键字

swift基础_关键字

作者: 李永开 | 来源:发表于2022-03-10 12:45 被阅读0次

一.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底层原理是啥?

相关文章

  • Swift基础-init详解

    前言 在讲解Swift的init之前,默认都有Swift开发基础,了解关键字designated,Optional...

  • Swift学习

    Swift学习 基础部分 变量声明方式不同,使用let, var关键字,类型推断非常多,需要注意 基础类型的使用,...

  • swift基础_关键字

    一.inout 我给函数传入一个参数,想在方法内部修改它,它报错了Cannot assign to value: ...

  • iOS9新特性之常见关键字

    苹果为什么要推出关键字? 迎合swift,swift强语言,OC弱语言,swift必须描述属性有没有值 关键字注意...

  • swift  defer  关键字 推迟执行

    Swift defer 关键字 延迟执行

  • 基本数据类型

    OC: Swift:注意关键字大写

  • Swift 语言基础

    Swift 语言基础 Swift语言介绍 Swift基础部分 第一个Swift程序 Swift没有main函数,第...

  • 学Swift挣美元01之Swift语言发展历史

    学Swift挣美元01之介绍与基础资源 本期重点 Swift历史 Swift基础资料 Swift的历史 Swift...

  • Swift 难点

    关于Swift的闭包,尾随闭包,Swift 中类型检测使用关键字is,类型转换使用关键字as。Any类,和AnyC...

  • iOS开发 -- Swift之函数与闭包(七)

    函数 函数基础 在Swift中,函数前面要用func关键字声明,参数写在括号里,并用向右的箭头指向返回值的类型。 ...

网友评论

      本文标题:swift基础_关键字

      本文链接:https://www.haomeiwen.com/subject/sipfdrtx.html