swift进阶(二):swift方法的调用

本文主要介绍了Swift中结构体和类的相关知识。结构体是值类型,方法调度为静态派发;类是引用类型,方法调度有动态和静态派发。还讲解了final、@objc、dynamic、inout、mutaing等关键字的作用,如final修饰方法直接调度,@objc将方法暴露给OC等。

结构体

结构体的常用写法

//***** 写法一 *****
struct QTeacher {
    var age: Int = 18
    
    func teach(){
        print("teach")
    }
}
var t = QTeacher()

//***** 写法二 *****
struct QTeacher {
    var age: Int
    
    func teach(){
        print("teach")
    }
}
var t = QTeacher(age: 18)
  • 在结构体中,如果不给属性默认值,编译是不会报错的。即在结构体中属性可以赋值,也可以不赋值
    在这里插入图片描述
  • init方法可以重写,也可以使用系统默认的

结构体的SIL分析

  • 如果没有init,系统会提供默认初始化方法

在这里插入图片描述

  • 如果自定义的init,就只显示自定义的init方法
    在这里插入图片描述

结构体是值类型

  • 定义结构体,打印结构体对象
struct QTeacher {
    var age: Int = 18
    var name: String = "teacher"
}
var  t = QTeacher()
print("end")
  • 发现t的打印直接就是值,没有任何与地址有关的信息

在这里插入图片描述

  • 获取t的内存地址,并查看其内存情况

    • 获取地址:po withUnsafePointer(to: &t){print($0)}
    • 查看内存情况: x/4gx 0x0000000100008180

在这里插入图片描述

  • 此时将t赋值给t1,如果修改了t1,t会发生改变吗?
import Foundation

struct QTeacher {
    var age: Int = 18
    var name: String = "teacher"
}
var  t = QTeacher()
var t1 = t
t1.age = 14
print("end")
  • 直接打印t及t1,可以发现t并没有因为t1的改变而改变,主要是因为因为t1和t之间是值传递,即t1和t是不同内存空间,是直接将t中的值拷贝至t1中。t1修改的内存空间,是不会影响t的内存空间的
    在这里插入图片描述

结构体的方法调度 : 静态派发

  • 声明t变量,调用teach方法,并在调用方法处打断点
    在这里插入图片描述
  • 进入汇编调试
    在这里插入图片描述
  • callq命令是调用的意思,结构体在调用方法时,直接调用的是地址,那说明在编译时期结构体的方法已经确定好了地址,在调用时直接调用其地址。
    在这里插入图片描述

类的常用写法

//****** 写法一 *******
class QTeacher {
    var age: Int = 18
    
    func teach(){
        print("teach")
    }
    init(_ age: Int) {
        self.age = age
    }
}
var t = QTeacher.init(20)

//****** 写法二 *******
class QTeacher {
    var age: Int?
    
    func teach(){
        print("teach")
    }
    init(_ age: Int) {
        self.age = age
    }
}
var t = QTeacher.init(20)
  • 在类中,如果属性没有赋值,也不是可选项,编译会报错
    在这里插入图片描述
  • 需要自己实现init方法

类是引用类型

定义一个QTeacher1类,并查看对象t1

class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
}
var t1 = QTeacher1()
  • 对象t1,存储在全局区,打印出来的是一个指针地址

在这里插入图片描述

  • 获取t1变量的地址,并查看其内存情况

    • 获取t1指针地址:po withUnsafePointer(to: &t1){print($0)}
    • 查看t1全局区地址内存情况:x/4g 0x0000000100008358
    • 查看t1地址中存储的堆区地址内存情况:x/4g 0x0000000100509790
      在这里插入图片描述

引用类型 特点

  • 1、地址中存储的是堆区地址

  • 2、堆区地址中存储的是值

此时将t1赋值给t2,如果修改了t2,会导致t1的修改

class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
}
var t1 = QTeacher1()
var t2 = t1
t2.age = 180

在这里插入图片描述

类的方法调度

动态派发

  • 声明test1方法,调用,并查看汇编

class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
    
    func test1(){
        self.age = 19
        print(self.age)
    }
    
    func test2(){}
    func test3(){}
}
var t1 = QTeacher1()
t1.test1()
  • 汇编指令补充

    • blr:带返回的跳转指令,跳转到指令后边跟随寄存器中保存的地址
    • mov:将某一寄存器的值复制到另一寄存器(只能用于寄存器与起存起或者 寄存器与常量之间 传值,不能用于内存地址)
      • mov x1, x0 将寄存器x0的值复制到寄存器x1中
    • ldr:将内存中的值读取到寄存器中
      • ldr x0, [x1, x2] 将寄存器x1和寄存器x2 相加作为地址,取该内存地址的值翻入寄存器x0中
    • str:将寄存器中的值写入到内存中
      • str x0, [x0, x8] 将寄存器x0的值保存到内存[x0 + x8]处
    • bl:跳转到某地址
  • 查看sil源码,sil_vtable

在这里插入图片描述

sil_vtable:关键字

QTeacher:表示是QTeacher类的函数表

其次就是当前方法的声明对应着方法的名称

函数表 可以理解为 数组,声明在 class内部的方法在不加任何关键字修饰的过程中,是连续存放在我们当前的地址空间中的。

静态派发

extension中的函数中,方法的调用是直接调用地址。

  • 声明QTeacher1的extension,调用test4方法,并查看汇编
class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
    
    func test1(){
        self.age = 19
        print(self.age)
    }
    
    func test2(){}
    func test3(){}
}

extension QTeacher1{
    func test4(){
        
    }
}

var t1 = QTeacher1()
t1.test4()
  • 直接调用的地址
    在这里插入图片描述
  • V-Table中不存在test4方法

在这里插入图片描述

子类中的方法

  • 新建QTeacher2类,继承于QTeacher1
class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
    
    func test1(){
        self.age = 19
        print(self.age)
    }
    
    func test2(){}
    func test3(){}
}

extension QTeacher1{
    func test4(){
        
    }
}

class QTeacher2 : QTeacher1{
    
}

var t1 = QTeacher1()
t1.test4()
print("end")
  • 查看sil

    • QTeacher2中继承了QTeacher1中的v-table中的方法
    • 但是QTeacher1中的extension方法是没有继承的

在这里插入图片描述

小结
  • 继承方法和属性,不能写extension中。
  • 而extension中创建的函数,一定是只属于自己类

final、@objc、dynamic、inout、mutaing关键字

final

final 修饰的方法是 直接调度的,可以通过 断点验证

  • 方法test1前面添加final关键字修饰
class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
    
    final func test1(){
        self.age = 19
        print(self.age)
    }
    
    func test2(){}
    func test3(){}
}


var t1 = QTeacher1()
t1.test1()
  • 进入符号断点,直接进行调度
    在这里插入图片描述

@objc

使用@objc关键字是将swift中的方法暴露给OC

  • 方法test1前面添加@objc关键字修饰
class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
    
    @objc func test1(){
        self.age = 19
        print(self.age)
    }
    
    func test2(){}
    func test3(){}
}


var t1 = QTeacher1()
t1.test1()
  • 进入符号断点,发现是函数表调度
    在这里插入图片描述
  • 如果只是通过@objc修饰函数,OC还是无法调用swift方法的,因此如果想要OC访问swift,class需要继承NSObject
<!--swift类-->
class QTeacher: NSObject {
    @objc func teach(){ print("teach") }
    func teach2(){ print("teach2") }
    func teach3(){ print("teach3") }
    func teach4(){ print("teach4") }
    @objc deinit{}
    override init(){}
}

<!--桥接文件中的声明-->
SWIFT_CLASS("_TtC9_3_指针10QTeacher")
@interface QTeacher : NSObject
- (void)teach;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

<!--OC调用-->
//1、导入swift头文件
#import "QOCTest-Swift.h"
//2、调用
QTeacher *t = [[QTeacher alloc] init];
[t teach];
  • 查看SIL文件发现被@objc修饰的函数声明有两个:swift + OC(内部调用的swift中的teach函数)
    在这里插入图片描述

dynamic

dynamic是方法具有动态性

  • 方法test1前面添加dynamic关键字修饰
class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
    
    dynamic func test1(){
        self.age = 19
        print(self.age)
    }
    
    func test2(){}
    func test3(){}
}


var t1 = QTeacher1()
t1.test1()
print("end")
  • 进入符号断点,发现是函数表调度;

    • 其中test1函数的调度还是 函数表调度,可以通过断点调试验证,使用dynamic的意思是可以动态修改,意味着当类继承自NSObject时,可以使用method-swizzling
      在这里插入图片描述
@objc + dynamic
class QTeacher1 {
    var age: Int = 18
    var age2: Int = 20
    
    @objc dynamic func test1(){
        self.age = 19
        print(self.age)
    }
    
    func test2(){}
    func test3(){}
}


var t1 = QTeacher1()
t1.test1()
print("end")
  • 通过断点调试,走的是objc_msgSend流程,即 动态消息转发
    在这里插入图片描述
  • swift中实现方法交换

在swift中的需要交换的函数前,使用dynamic修饰,然后通过:@_dynamicReplacement(for: 函数符号)进行交换,如下所示

在这里插入图片描述

inout

一般情况下,在函数的声明中,默认的参数都是不可变的,如果想要直接修改,需要给参数加上inout关键字

  • 添加inout关键字,可以给参数赋值
    在这里插入图片描述

mutaing

一般使用在结构体中, 通过结构体定义一个栈,主要有push、pop方法,此时我们需要动态修改栈中的数组

  • 如果是以下这种写法,会直接报错,原因是值类型本身是不允许修改属性的
    在这里插入图片描述
  • 将push方法改成下面的方式,查看SIL文件中的push函数
    在这里插入图片描述
  • 给push添加mutaing,发现可以添加到数组了

在这里插入图片描述

  • 查看其SIL文件,找到push函数,发现与之前有所不同,push添加mutaing(只用于值类型)后,本质上是给值类型函数添加了inout关键字,相当于在值传递的过程中,传递的是引用(即地址)
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值