Swift之類

文章首發於個人我的博客html

前言

類的定義和結構體相似,但編譯器並無爲類自動生成能夠傳入成員值的初始化器 eg 以下代碼不會報錯git

struct Point {
    var x: Int = 0
	var y: Int = 0 
}
let p1 = Point()
let p2 = Point(x: 10, y: 20)
let p3 = Point(x: 10)
let p4 = Point(y: 20)
複製代碼

可是若是改爲了類就不能編譯經過github

class Point {
    var x: Int = 0
    var y: Int = 0
    
}
let p1 = Point()
let p2 = Point(x: 10, y: 20)//報錯Argument passed to call that takes no arguments
let p3 = Point(x: 10)//報錯Argument passed to call that takes no arguments
let p4 = Point(y: 20)//報錯Argument passed to call that takes no arguments
複製代碼

類的初始化器

  • 若是類的全部成員都在定義的時候指定了初始值,編譯器會爲類生成無參的初始化器
  • 成員的初始化是在這個初始化器中完成的

如下2段代碼徹底等效

struct Point {
    var x: Int = 0
	var y: Int = 0 
}
	
	
struct Point {
    var x: Int
    var y: Int
	init() { 
		x=0 
		y=0
	} 
}

複製代碼

結構體與類的本質區別

結構體是值類型(枚舉也是值類型),類是引用類型(指針類型)

eg:咱們有以下的結構體point 和類 size編程

// 類size
class Size {
    var width = 1
    var height = 2
}

// 類 Point
struct Point {
    var x = 3
    var y = 4
}

// 變量 size 接收類Size
var size = Size()
// 變量 point 接收結構體point
var point = Point()

複製代碼

咱們假設 執行完test() 以後,point的內存地址爲 0x10000 size的內存地址爲 0x10010 能夠用一幅圖來表示swift

上圖表示,point是值拷貝,直接把 3 和 4 放在了point對應的內存中,而 指針變量size是引用拷貝,是放了 Size() 的指針 0x90000 ,而對應的 堆空間 0x90000中才真正的存放1和2,固然了,前面有16個字節,存放了類的信息,和引用計數,由於Swift和OC同樣使用的引用計數來內存管理的,因此Size對象用了32個字節bash

彙編驗證

代碼以下app

unc test1(){
    // 類size
    class Size {
        var width = 1
        var height = 2
    }
    
    // 類 Point
    struct Point {
        var x = 3
        var y = 4
    }
    
   
    // 變量 point 接收結構體point
    var point = Point()
     // 變量 size 接收類Size
    var size = Size()
}

test1()
複製代碼

彙編驗證驗證結構體

上面的代碼,先在 var point = Point() 處打斷點函數

testSwift`__allocating_init() in Size #1 in test1():
    0x100001030 <+0>:  pushq  %rbp
    0x100001031 <+1>:  movq   %rsp, %rbp
    0x100001034 <+4>:  pushq  %r13
    0x100001036 <+6>:  subq   $0x18, %rsp
    0x10000103a <+10>: xorl   %eax, %eax
    0x10000103c <+12>: movl   %eax, %edi
->  0x10000103e <+14>: callq  0x100001250               ; type metadata accessor for Size #1 in testSwift.test1() -> () at <compiler-generated>
    0x100001043 <+19>: movl   $0x20, %ecx
    0x100001048 <+24>: movl   %ecx, %esi
    0x10000104a <+26>: movl   $0x7, %ecx
    0x10000104f <+31>: movl   %ecx, %edi
    0x100001051 <+33>: movq   %rdi, -0x10(%rbp)
    0x100001055 <+37>: movq   %rax, %rdi
    0x100001058 <+40>: movq   -0x10(%rbp), %rax
    0x10000105c <+44>: movq   %rdx, -0x18(%rbp)
    0x100001060 <+48>: movq   %rax, %rdx
    0x100001063 <+51>: callq  0x100005046               ; symbol stub for: swift_allocObject
    0x100001068 <+56>: movq   %rax, %r13
    0x10000106b <+59>: callq  0x100001e30               ; init() -> Size #1 in testSwift.test1() -> () in Size #1 in testSwift.test1() -> () at main.swift:15
    0x100001070 <+64>: addq   $0x18, %rsp
    0x100001074 <+68>: popq   %r13
    0x100001076 <+70>: popq   %rbp
    0x100001077 <+71>: retq   

複製代碼

工具

0x10000103e <+14>: callq  0x100001250 ; type metadata accessor for Size #1 in testSwift.test1() -> () at <compiler-generated> 
複製代碼

處執行lldb命令 si 跟蹤進去性能

testSwift`init() in Point #1 in test1():
->  0x100001030 <+0>:  pushq  %rbp
    0x100001031 <+1>:  movq   %rsp, %rbp
    0x100001034 <+4>:  xorps  %xmm0, %xmm0
    0x100001037 <+7>:  movaps %xmm0, -0x10(%rbp)
    0x10000103b <+11>: movq   $0x3, -0x10(%rbp)
    0x100001043 <+19>: movq   $0x4, -0x8(%rbp)
    0x10000104b <+27>: movl   $0x3, %eax
    0x100001050 <+32>: movl   $0x4, %ecx
    0x100001055 <+37>: movl   %ecx, %edx
    0x100001057 <+39>: popq   %rbp
    0x100001058 <+40>: retq   

複製代碼

能夠看到賦值操做 直接是把 $0x3 和 $0x4 賦值給棧空間 (-0x10(%rbp) 和 -0x8(%rbp) )的,沒有調用malloc alloc 等方法,也就是沒有開闢堆空間

彙編驗證驗證類

上面的代碼,先在 var size = Size() 處打斷點

0x100000fe0 <+0>:  pushq  %rbp
    0x100000fe1 <+1>:  movq   %rsp, %rbp
    0x100000fe4 <+4>:  pushq  %r13
    0x100000fe6 <+6>:  subq   $0x28, %rsp
    0x100000fea <+10>: movq   $0x0, -0x10(%rbp)
    0x100000ff2 <+18>: xorps  %xmm0, %xmm0
    0x100000ff5 <+21>: movaps %xmm0, -0x20(%rbp)
    0x100000ff9 <+25>: xorl   %eax, %eax
    0x100000ffb <+27>: movl   %eax, %edi
->  0x100000ffd <+29>: callq  0x100001250               ; type metadata accessor for Size #1 in testSwift.test1() -> () at <compiler-generated>
    0x100001002 <+34>: movq   %rax, %r13
    0x100001005 <+37>: movq   %rdx, -0x28(%rbp)
    0x100001009 <+41>: callq  0x100001030               ; __allocating_init() -> Size #1 in testSwift.test1() -> () in Size #1 in testSwift.test1() -> () at main.swift:15
    0x10000100e <+46>: movq   %rax, -0x10(%rbp)
    0x100001012 <+50>: callq  0x100001080               ; init() -> Point #1 in testSwift.test1() -> () in Point #1 in testSwift.test1() -> () at main.swift:21
    0x100001017 <+55>: movq   %rax, -0x20(%rbp)
    0x10000101b <+59>: movq   %rdx, -0x18(%rbp)
    0x10000101f <+63>: movq   -0x10(%rbp), %rdi
    0x100001023 <+67>: callq  0x1000050ac               ; symbol stub for: swift_release
    0x100001028 <+72>: addq   $0x28, %rsp
    0x10000102c <+76>: popq   %r13
    0x10000102e <+78>: popq   %rbp
    0x10000102f <+79>: retq   
複製代碼

進入

0x100001009 <+41>: callq  0x100001030               ; __allocating_init() -> Size #1 in testSwift.test1() -> () in Size #1 in testSwift.test1() -> () at main.swift:15
複製代碼

一路跟蹤進入,最終來到了以下圖所示位置

也就是確實分配了堆空間,驗證了咱們前面的結論

對象的堆空間申請過程

  • 在Swift中,建立類的實例對象,要向堆空間申請內存,大概流程以下
Class.__allocating_init() 
libswiftCore.dylib:_swift_allocObject_ 
libswiftCore.dylib:swift_slowAlloc 
libsystem_malloc.dylib:malloc
複製代碼
  • 在Mac、iOS中的malloc函數分配的內存大小老是16的倍數
  • 經過class_getInstanceSize能夠得知:類的對象至少須要佔用多少內存

eg:

class Point  {
    var x = 11
    var test = true
	var y = 22 
}
var p = Point() 
class_getInstanceSize(type(of: p)) // 40
class_getInstanceSize(Point.self) // 40
複製代碼

值類型

  • 值類型賦值給var、let或者給函數傳參,是直接將全部內容拷貝一份

  • 相似於對文件進行copy、paste操做,產生了全新的文件副本。屬於深拷貝(deep copy)

  • 在Swift標準庫中,爲了提高性能,String、Array、Dictionary、Set採起了Copy On Write的技術

    • 好比僅當有「寫」操做時,纔會真正執行拷貝操做
    • 對於標準庫值類型的賦值操做,Swift 能確保最佳性能,全部不必爲了保證最佳性能來避免賦值
  • 建議:不須要修改的,儘可能定義成let

引用類型

  • 引用賦值給var、let或者給函數傳參,是將內存地址拷貝一份
  • 相似於製做一個文件的替身(快捷方式、連接),指向的是同一個文件。屬於淺拷貝(shallow copy)

枚舉、結構體、類均可以定義方法

通常把定義在枚舉、結構體、類內部的函數,叫作方法

// 類中定義方法
class Size {
    var width = 10
    var height = 10
    func show() {
        print("width=\(width), height=\(height)")
    }
}
let s = Size()
s.show() // width=10, height=10

// 結構體中定義方法
struct Point {
    var x = 10
    var y = 10
    func show() {
        print("x=\(x), y=\(y)")
    }
}
let p = Point()
p.show() // x=10, y=10

// 枚舉中定義方法
enum grade : Character {
    case a = "a"
    case b = "b"
    func show() {
        print("res is \(rawValue)")
    }
}
let g = grade.a
g.show() // res is a

複製代碼

參考資料:

Swift官方源碼

從入門到精通Swift編程

窺探內存細節的小工具

更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。

相關文章
相關標籤/搜索