Swift探索(一): 類(lèi)與結(jié)構(gòu)體(上)

1.類(lèi)與結(jié)構(gòu)體

1.1 類(lèi)

class PersonClass {
   
   //1.類(lèi)中可以定義存儲(chǔ)值的屬性
   var age : Int = 18
   var name : String = "小明"
   
   // 2.類(lèi)中可以定義方法
   func test() {
       print("%@多少%@歲", self.name, self.age);
   }
   
   // 3.類(lèi)中可以定義初始化器
   // 指定初始化器 默認(rèn)只有一個(gè)
   init(_ age: Int, _ name: String) {
       self.age = age
       self.name = name
   }
   
   // 便捷初始化器 必須從相同的類(lèi)里調(diào)用另一個(gè)初始化器
   // 安全性能原則:self及其屬性已經(jīng)初始化了
   convenience init(_ age: Int) {
       self.init(18, "小明")
       self.age = age
       self.name = "小王"
   }
   
   // 可失敗初始化
   init?(age: Int, name: String) {
       if age < 18 {return nil}
       self.age = age
       self.name = name
   }
   
   // 必要初始化器 所有該類(lèi)的子類(lèi)都必須 實(shí)現(xiàn)該初始化器
   required init(age1: Int, name1:String) {
       self.age = age1
       self.name = name1
   }
   
   // 類(lèi)有析構(gòu)函數(shù), 結(jié)構(gòu)體沒(méi)有
   deinit {
   }
}

class SubPersonClass: PersonClass {
   var subName: String
   // 子類(lèi)的所有屬性必須在父類(lèi)初始化之前初始化完成
   // 子類(lèi)初始化器必須先向上委托父類(lèi)初始化器,然后才能為繼承的屬性設(shè)置新值。如果不這樣做,指定初始化器賦予的新值將被父類(lèi)中的初始化器所覆蓋
   // 確保成員變量訪問(wèn)安全
   init(_ subName : String) {
       self.subName = subName;
       super.init(18, "小明")
   }
}

1.2 結(jié)構(gòu)體

// 結(jié)構(gòu)體
struct PersonStruct {
    
    //1.結(jié)構(gòu)體中可以定義存儲(chǔ)值的屬性
    var age : Int = 18
    var name : String = "小明"
    
    // 2.結(jié)構(gòu)體中可以定義方法
    func test() {
        print("%@多少%@歲", self.name, self.age);
    }
    
    // 3.結(jié)構(gòu)體中可以定義初始化器
    // 與類(lèi)不同,編譯器會(huì)提供默認(rèn)的初始化方法(前提是我們自己沒(méi)有指定初始化器)
    init(age : Int, name : String) {
        self.age = age
        self.name = name
    }
}

1.3 類(lèi)和結(jié)構(gòu)體的相同點(diǎn)

  • 定義存儲(chǔ)值的屬性
  • 定義方法
  • 定義下標(biāo)以使用語(yǔ)法提供對(duì)其值的訪問(wèn)
  • 定義初始化器
  • 使用extension來(lái)拓展功能
  • 遵循協(xié)議來(lái)提供某種功能

1.4 類(lèi)和結(jié)構(gòu)體的不同點(diǎn)

  • 類(lèi)有繼承的特性,而結(jié)構(gòu)體沒(méi)有
  • 類(lèi)型轉(zhuǎn)換使您能在運(yùn)行時(shí)檢查和解釋class的實(shí)例對(duì)象的類(lèi)型
  • 類(lèi)有析構(gòu)函數(shù)用來(lái)釋放其占用的資源, 而結(jié)構(gòu)體沒(méi)有
  • 類(lèi)有引用計(jì)數(shù),允許對(duì)一個(gè)類(lèi)實(shí)例有多個(gè)引用
  • 類(lèi)是引用類(lèi)型,結(jié)構(gòu)體是值類(lèi)型

2. 引用類(lèi)型和值類(lèi)型

  • 引用類(lèi)型的變量并不直接存儲(chǔ)具體的實(shí)例對(duì)象,而是對(duì)當(dāng)前存儲(chǔ)具體實(shí)例內(nèi)存地址的引用。
  • 值類(lèi)型的變量存儲(chǔ)的就是具體的實(shí)例(或者說(shuō)具體的值)
  • 引用類(lèi)型存儲(chǔ)在
  • 值類(lèi)型存儲(chǔ)在

借助幾個(gè)LLDB指令來(lái)查看當(dāng)前變量的內(nèi)存結(jié)構(gòu)

  • po 只會(huì)輸出對(duì)應(yīng)的值
  • p 則會(huì)輸出返回值的類(lèi)型以及命令結(jié)果
  • x/8g: 讀取內(nèi)存中的值(8g: 8字節(jié)格式輸出)
  • frame varibale -L xxx:查看各實(shí)例內(nèi)存信息

2.1 引用類(lèi)型

引用賦值給varlet或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份。類(lèi)似于制作一個(gè)文件的替身(快捷方式、鏈接),指向的是同一個(gè)文件。屬于淺拷貝

p和p1都引用了同一個(gè)PersonClass的實(shí)例地址.png

pp1都引用了同一個(gè)PersonClass的實(shí)例地址

p和p1在內(nèi)存中的地址.png

pp1在內(nèi)存中的地址相差8個(gè)字節(jié)(存儲(chǔ)著當(dāng)前實(shí)例對(duì)象的內(nèi)存地址)

類(lèi)的內(nèi)存地址在堆空間.png

2.2 值類(lèi)型

值類(lèi)型賦值給 varlet或者給函數(shù)傳參,是直接將所有內(nèi)容拷貝一份。類(lèi)似于對(duì)文件進(jìn)行復(fù)制粘貼操作,產(chǎn)生了全新的文件副本。屬于深拷貝。

ps和ps1是內(nèi)存當(dāng)中存儲(chǔ)的值.png

psps1是內(nèi)存當(dāng)中存儲(chǔ)的值,并且更改ps1當(dāng)中的agename對(duì)ps沒(méi)有影響,因?yàn)?code>ps和ps1存儲(chǔ)的是兩個(gè)不同的值

結(jié)構(gòu)體的內(nèi)存地址在??臻g.png

3.類(lèi)的生命周期

iOS開(kāi)發(fā)的語(yǔ)言不管是OC還是Swift后端都是通過(guò)LLVM進(jìn)行編譯的,如下圖所示:

OC和Swift都是通過(guò)LLVM進(jìn)行編譯.png

OC通過(guò)clang 編譯器,編譯成IR,然后再生成可執(zhí)行文件 .o(這里也就是我們的機(jī)器碼)
Swift則是通過(guò)Swift編譯器編輯成IR,然后再生成可執(zhí)行文件,具體編譯過(guò)程如下:
Swift編譯流程.png

  • swift code 經(jīng)過(guò)-dump-parse的命令來(lái)進(jìn)行語(yǔ)法分析,解析成我們的AST(也就是抽象語(yǔ)法樹(shù));
  • 通過(guò)-dump-ast語(yǔ)義分析來(lái)檢查我們的語(yǔ)義,(例如類(lèi)型檢查是否準(zhǔn)確安全);
  • 完成之后Swift代碼將會(huì)降級(jí)為SIL,也就是Swift中間語(yǔ)言/代碼(Swift intermediate language);
  • SIL分為Raw SIL(原生的,沒(méi)有開(kāi)啟優(yōu)化選項(xiàng)) 和SILOpt Canonical SIL(經(jīng)過(guò)優(yōu)化的);
  • 最后通過(guò)LLVM降級(jí)為IR,然后通過(guò)后端代碼編譯為不同架構(gòu)的機(jī)器碼

我們也可以通過(guò)swiftc命令去生成對(duì)應(yīng)的文件

// 分析輸出AST
swiftc main.swift -dump-parse
// 分析并且檢查類(lèi)型輸出AST 
swiftc main.swift -dump-ast
// 生成中間體語(yǔ)言(SIL),未優(yōu)化
swiftc main.swift -emit-silgen
// 生成中間體語(yǔ)言(SIL),優(yōu)化后的 
swiftc main.swift -emit-sil
// 生成LLVM中間體語(yǔ)言 (.ll文件) 
swiftc main.swift -emit-ir
// 生成LLVM中間體語(yǔ)言 (.bc文件) 
swiftc main.swift -emit-bc
// 生成匯編
swiftc main.swift -emit-assembly
// 編譯生成可執(zhí)行.out文件 
swiftc -o main.o main.swift

SIL文件中的重要標(biāo)識(shí)可參考:SIL官方文檔

接下來(lái)開(kāi)始分析類(lèi)的初始化

class Person {
    var age : Int = 18
    var name : String = "小明"
}

var p = Person()

通過(guò)swiftc main.swift -emit-sil > ./main.sil生成.sil文件,在.sil文件中 注意到__allocating_init()創(chuàng)建實(shí)例對(duì)象的函數(shù)

// Person.__allocating_init()
sil hidden [exact_self_class] @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person {
// %0 "$metatype"
bb0(%0 : $@thick Person.Type):
  %1 = alloc_ref $Person                          // user: %3
  // function_ref Person.init()
  %2 = function_ref @$s4main6PersonCACycfc : $@convention(method) (@owned Person) -> @owned Person // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned Person) -> @owned Person // user: %4
  return %3 : $Person                             // id: %4
} // end sil function '$s4main6PersonCACycfC'

其中Person.Type元類(lèi)型的作用類(lèi)似于 OC 里的isa
alloc_ref根據(jù)官方文檔中的解釋

Allocates an object of reference type T. The object will be initialized with retain count 1; its state will be otherwise uninitialized. The optional objc attribute indicates that the object should be allocated using Objective-C's allocation methods (+allocWithZone:)(可以理解為去堆區(qū)申請(qǐng)內(nèi)存空間)

進(jìn)入?yún)R編我們可以看到__allocating_init的調(diào)用

__allocating_init的調(diào)用.png

進(jìn)入到__allocating_init內(nèi)部
__allocating_init內(nèi)部.png

可以看到__allocating_init內(nèi)部調(diào)用了一個(gè)函數(shù)swift_allocObject
通過(guò) Swift源碼 中的HeapObject.cpp文件可以發(fā)現(xiàn)

HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
                                     size_t requiredSize,
                                     size_t requiredAlignmentMask) {
  CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}

SWIFT_RUNTIME_EXPORT
HeapObject *(*SWIFT_RT_DECLARE_ENTRY _swift_allocObject)(
    HeapMetadata const *metadata, size_t requiredSize,
    size_t requiredAlignmentMask) = _swift_allocObject_;

swift_allocObject走的是內(nèi)部函數(shù)_swift_allocObject_,進(jìn)入到_swift_allocObject_

static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}

跳轉(zhuǎn)至swift_slowAlloc

void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);
#endif
  } else {
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}

因此我們可以看出Swift對(duì)象的內(nèi)存分配流程如下
__allocating_init--->swift_allocObject ---> _ swift_allocObject ---> swift_slowAlloc--->Malloc
_swift_allocObject_函數(shù)里我們可以看到返回的是HeapObject類(lèi)型,因此Swift對(duì)象內(nèi)存結(jié)構(gòu)為HeapObject

進(jìn)入到HeapObject

/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *__ptrauth_objc_isa_pointer metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

#ifndef __swift__
  HeapObject() = default;

  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  
  // Initialize a HeapObject header for an immortal object
  constexpr HeapObject(HeapMetadata const *newMetadata,
                       InlineRefCounts::Immortal_t immortal)
  : metadata(newMetadata)
  , refCounts(InlineRefCounts::Immortal)
  { }

#ifndef NDEBUG
  void dump() const SWIFT_USED;
#endif

#endif // __swift__
};

我們可以看到HeapObject有兩個(gè)屬性,默認(rèn)占用 16 字節(jié)大?。?/p>

  • 一個(gè)是 HeapMatadata類(lèi)型的metadata,8字節(jié),
  • 一個(gè)是refCount,8字節(jié)

通過(guò)源碼最后可以得出

//HeapMataData的結(jié)構(gòu)如下
struct HeapMataData {
  var kind: Int
  var superClass: Any.Type
  var cacheData: (Int, Int)
  var data: Int
  var classFlags: Int32
  var instanceAddressPoint: UInt32
  var instanceSize: UInt32
  var instanceAlignmentMask: UInt16
  var reserved: UInt16
  var classSize: UInt32
  var classAddressPoint: UInt32
  var typeDescriptor: UnsafeMutableRawPointer
  var iVarDestroyer: UnsafeRawPointer
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容