swift--值类型和引用类型

结构体

struct Teacher{
    var age:Int
    func teach() {
        print("teach")
    }
}

var age = Teacher(age: <#Int#>)

我们声明一个结构体,如果没有初始化赋值,我们需要在创建的时候赋值.
查看SIL

struct Teacher {
  @_hasStorage var age: Int { get set }
  func teach()
  init(age: Int)
}

系统会自动帮我们生成初始化方法,如果是class我们需要自己在init方法中初始化。
如果我们的属性有默认初始值,系统会提供不同的默认初始化方法

struct Teacher {
  @_hasStorage @_hasInitialValue var age: Int { get set }
  func teach()
  init(age: Int = 18)
  init()
}

如果我们自定义初始化方法,系统就不会帮我们生成初始化方法

结构体是值类型

我们首先看一个例子

func test(){
    var age = 18
    var age2 = age
    age = 30
    age2 = 45
    print("age=\(age),age2=\(age2)")
}

通过lldb进行打印

(lldb) po withUnsafeMutablePointer(to: &age, {print($0)})
0x00007ffeefbff410
0 elements

这就是我们栈上的地址

x/4g 0x00007ffeefbff410
0x7ffeefbff410: 0x0000000000000012 0x0000000000000000
0x7ffeefbff420: 0x00007ffeefbff440 0x0000000100000c44

我们在打印age2的信息

(lldb) x/4g 0x00007ffeefbff408
0x7ffeefbff408: 0x0000000000000012 0x0000000000000012
0x7ffeefbff418: 0x0000000000000000 0x00007ffeefbff440

我们后面修改的就是独立地址的值
所以这就是个值类型

  1. 地址存储的就是值
  2. 传递的就是中就是个副本
struct Teacher{
    var age:Int = 18
    var age2:Int = 20
}

var t = Teacher()
················
(lldb) po t
▿ Teacher
  - age : 18
  - age2 : 20

在lldb中po打印的就直接是t的值

(lldb) po withUnsafeMutablePointer(to: &t, {print($0)})
0x0000000100002038
0 elements
(lldb) x/4g 0x0000000100002038
0x100002038: 0x0000000000000012 0x0000000000000014
0x100002048: 0x0000000000000000 0x0000000000000000

可以看到地址里存的直接是值。
通过SIL我们可以知道他没有alloc去堆上开辟空间。

我们再看看class

class Teacher1{
    var age:Int = 18
    var age2:Int = 20
}
var t1 = Teacher1()
············
(lldb) po t1

(lldb) x/4g 0x104100180
0x104100180: 0x0000000100003170 0x0000000600000002
0x104100190: 0x0000000000000012 0x0000000000000014

打印出kind, refCounts, age, age2
类是引用类型,当别的地方修改的时候,其他地方的引用也会被修改

class Teacher1{
    var age:Int = 18
    var age2:Int = 20
}
struct Teacher{
    var age:Int = 18
    var age2:Int = 20
    var teacher = Teacher1()
}
var t = Teacher()
var t1 = t
t.teacher.age = 30;

print(t1.teacher.age)
···············
30

因此我们在结构体中属性是引用类型也会将地址复制过去。所以我们要避免这样的写法。

mutating

struct Stack{
    var items = [Int]()
    func push(item:Int) {
        items.append(item)
    }
}

在结构体里,我们直接向数组里添加是不允许的,会和我们说self是不可变的
我们查看SIL

// Stack.push(item:)
sil hidden @main.Stack.push(item: Swift.Int) -> () : $@convention(method) (Int, @guaranteed Stack) -> () {
// %0                                             // user: %2
// %1                                             // user: %3
bb0(%0 : $Int, %1 : $Stack):
  debug_value %0 : $Int, let, name "item", argno 1 // id: %2
  debug_value %1 : $Stack, let, name "self", argno 2 // id: %3
  %4 = tuple ()                                   // user: %5
  return %4 : $()                                 // id: %5
} // end sil function 'main.Stack.push(item: Swift.Int) -> ()'

在这里self是let类型,我们根据提示加上mutating

// Stack.push(item:)
sil hidden @main.Stack.push(item: Swift.Int) -> () : $@convention(method) (Int, @inout Stack) -> () {
// %0                                             // users: %5, %2
// %1                                             // users: %6, %3
bb0(%0 : $Int, %1 : $*Stack):
  debug_value %0 : $Int, let, name "item", argno 1 // id: %2
  debug_value_addr %1 : $*Stack, var, name "self", argno 2 // id: %3
  %4 = alloc_stack $Int                           // users: %5, %11, %9
  store %0 to %4 : $*Int                          // id: %5
  %6 = begin_access [modify] [static] %1 : $*Stack // users: %10, %7
  %7 = struct_element_addr %6 : $*Stack, #Stack.items // user: %9
  // function_ref Array.append(_:)
  %8 = function_ref @Swift.Array.append(__owned A) -> () : $@convention(method) <τ_0_0> (@in τ_0_0, @inout Array<τ_0_0>) -> () // user: %9
  %9 = apply %8(%4, %7) : $@convention(method) <τ_0_0> (@in τ_0_0, @inout Array<τ_0_0>) -> ()
  end_access %6 : $*Stack                         // id: %10
  dealloc_stack %4 : $*Int                        // id: %11
  %12 = tuple ()                                  // user: %13
  return %12 : $()                                // id: %13
} // end sil function 'main.Stack.push(item: Swift.Int) -> ()'

Stack@inout来修饰。self也由var修饰,仔细观察,self提示我们这里是debug_value_addr地址,而不是值类型。所以mutating本质就是将值类型改为了引用类型。

符号表

    0x100000be0 <+80>:  callq  0x100000cb0               ; swiftTest.Stack.push(item: Swift.Int) -> () at main.swift:14

结构体中的方法调度方式是静态调用,在编译链接结束之后当前的函数地址就已经确定存放在了代码段.swiftTest.Stack.push(item: Swift.Int)而函数名和变量名存储在符号表中。我们可以通过nm .../Build/Products/Debug/swiftTest (可执行文件地址)来查看当前项目的符号

00000100000d20 T _$s9swiftTest5StackV5itemsACSaySiG_tcfC
0000000100000d00 T _$s9swiftTest5StackV5itemsACSaySiG_tcfcfA_
0000000100000c90 T _$s9swiftTest5StackV5itemsSaySiGvM
0000000100000ca0 t _$s9swiftTest5StackV5itemsSaySiGvM.resume.0
0000000100000c20 T _$s9swiftTest5StackV5itemsSaySiGvg
0000000100000ec0 S _$s9swiftTest5StackV5itemsSaySiGvpMV
0000000100000c00 T _$s9swiftTest5StackV5itemsSaySiGvpfi
0000000100000c50 T _$s9swiftTest5StackV5itemsSaySiGvs
0000000100000d30 T _$s9swiftTest5StackVACycfC
0000000100000f08 s _$s9swiftTest5StackVMF
0000000100000e20 T _$s9swiftTest5StackVMa
0000000100001010 s _$s9swiftTest5StackVMf
0000000100000ee4 S _$s9swiftTest5StackVMn
0000000100001018 S _$s9swiftTest5StackVN
0000000100000e00 t _$s9swiftTest5StackVWOh

然后通过xcrun swift-demangle s9swiftTest5StackV5itemsSaySiGvs(符号)
得到真实函数名称

$s9swiftTest5StackV5itemsSaySiGvs ---> swiftTest.Stack.items.setter : [Swift.Int]

也可以通过地址直接找到符号
nm .../Build/Products/Debug/swiftTest (可执行文件地址) | grep 0000000100000cb0(符号地址)

0000000100000cb0 T _$s9swiftTest5StackV4push4itemySi_tF

如果改为release模式,符号表确认地址的符号就会去除。只有没有确定的才会保留,比如lazy
在C语言中函数的名称在符号表里就是添加了一个_,比如test()->_test
OC中:Teacher中-(void)test->[Teacher test]
所以在C语言和OC中是不允许方法重载的,而swift中字符串这么复杂保证的不同参数可以重载。

方法调度

对于结构体中的方法都是静态调用(直接调用),对于类中声明的方法是通过V-table来进行调度的。
V-table在SIL中的表示为

decl ::= sil-vtable 
 sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}' 
 sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-na me

例:

class Teacher{
    func teach(){print("teach")}
    func teach1(){print("teach1")}
    func teach2(){print("teach2")}
    func teach3(){print("teach3")}
}
····························
sil_vtable Teacher {
  #Teacher.teach!1: (Teacher) -> () -> () : @main.Teacher.teach() -> () // Teacher.teach()
  #Teacher.teach1!1: (Teacher) -> () -> () : @main.Teacher.teach1() -> ()   // Teacher.teach1()
  #Teacher.teach2!1: (Teacher) -> () -> () : @main.Teacher.teach2() -> ()   // Teacher.teach2()
  #Teacher.teach3!1: (Teacher) -> () -> () : @main.Teacher.teach3() -> ()   // Teacher.teach3()
  #Teacher.init!allocator.1: (Teacher.Type) -> () -> Teacher : @main.Teacher.__allocating_init() -> main.Teacher    // Teacher.__allocating_init()
  #Teacher.deinit!deallocator.1: @main.Teacher.__deallocating_deinit    // Teacher.__deallocating_deinit
}

我们也可以通过源码查看

static void initClassVTable(ClassMetadata *self) {
  const auto *description = self->getDescription();
  auto *classWords = reinterpret_cast(self);

  if (description->hasVTable()) {
    auto *vtable = description->getVTableDescriptor();
    auto vtableOffset = vtable->getVTableOffset(description);
    for (unsigned i = 0, e = vtable->VTableSize; i < e; ++i)
      classWords[vtableOffset + i] = description->getMethod(i);
  }
....
}

这里就是通过内存偏移获得我们的方法地址存放在内存中。

extension

如果我们在extension添加方法不会添加到我们的V-table中,而是直接调用。
如果我们有个子类继承了父类,就会继承他的V-table,当前编译的时候如果又编译到extension就无法插入到子类中,无法知道到底是父类还是子类的。

final

添加了final,表示不允许对其修饰的内容进行继承或者重新操作。

@objc

标记暴露给我们的OC。

***********swift**************
class Teacher:NSObject{
    @objc func teach(){print("teach")}
}
***********oc**************
#import "OCtestSwift-Swift.h"
int main(int argc, const char * argv[]) {
        Teacher *t = [Teacher new];
        [t teach];
    return 0;
}

这样OC中就可也以直接使用swift方法


// Teacher.teach()
sil hidden @main.Teacher.teach() -> () : $@convention(method) (@guaranteed Teacher) -> () {
// %0                                             // user: %1
bb0(%0 : $Teacher):
  debug_value %0 : $Teacher, let, name "self", argno 1 // id: %1
  %2 = tuple ()                                   // user: %3
  return %2 : $()                                 // id: %3
} // end sil function 'main.Teacher.teach() -> ()'

// @objc Teacher.teach()
sil hidden [thunk] @@objc main.Teacher.teach() -> () : $@convention(objc_method) (Teacher) -> () {
// %0                                             // users: %4, %3, %1
bb0(%0 : $Teacher):
  strong_retain %0 : $Teacher                     // id: %1
  // function_ref Teacher.teach()
  %2 = function_ref @main.Teacher.teach() -> () : $@convention(method) (@guaranteed Teacher) -> () // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed Teacher) -> () // user: %5
  strong_release %0 : $Teacher                    // id: %4
  return %3 : $()                                 // id: %5
} // end sil function '@objc main.Teacher.teach() -> ()'

通过SIL文件我们也可以看出来,teach方法生成了两个方法,在暴露给OC的方法中又调用了swift方法。

dynamic

使属性启用Objc的动态转发功能.

@objc + dynamic

如果两个一起动就是我们的动态消息转发。symbol stub for: objc_msgSend

class Teacher{
    dynamic func teach(){print("teach")}
}

extension Teacher{
    @_dynamicReplacement(for: teach)
    func teach1(){print("teach1")}
}
var t = Teacher()
t.teach()
···················
teach1

这个时候我们调用teach的时候就会调用teach1

你可能感兴趣的:(swift--值类型和引用类型)