日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Swift之深入解析“对象”的底层原理

發(fā)布時間:2024/5/21 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Swift之深入解析“对象”的底层原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、Swift 編譯簡介

  • Swift 的編譯環(huán)境配置和編譯流程,請參考我之前的博客:Swift之源碼編譯的環(huán)境搭建和編譯流程;
  • 新建一個 Swift 工程,在 main.swift 中創(chuàng)建一個 YDWTeacher 類,并通過默認的初始化器,創(chuàng)建一個實例對象并賦值給 t,如下:
class YDWTeacher { var age: Int = 18 var name: String = "YDW" } let t = YDWTeacher()
  • 然后在終端中查看抽象語法樹:swiftc -dump-ast main.swift,如下:

  • 接下來,要研究的是這個初始化器到底做了一個什么樣的操作?因此引入 SIL (Swift intermediate language);
  • iOS 的開發(fā)語言,不管是 OC 還是 Swift,底層都是通過 LLVM 編譯的,生成 .o 可執(zhí)行文件,如下所示:

  • 不難看出:
    • OC 中通過 clang 編譯器,編譯成 IR,然后再生成可執(zhí)行文件.o(即機器碼);
    • swift 中通過 swiftc 編譯器,編譯成 IR,然后再生成可執(zhí)行文件;
  • 再來看一下:一個 Swift 文件的編譯過程經歷哪些步驟:

  • 下面是 Swift 中的編譯流程,其中 SIL(Swift Intermediate Language),是 Swift 編譯過程中的中間代碼,主要用于進一步分析和優(yōu)化 Swift 代碼。如下圖所示,SIL 位于在 AST 和 LLVM IR 之間:

  • Swift 與 OC 的區(qū)別在于 Swift 生成了高級的 SIL;Swift 在編譯的過程中使用的前端編譯器是 Swiftc,和我們之前的 OC 中使用的 clang 是有所區(qū)別的。
  • 通過 swiftc -h 終端命令,查看 swiftc 能做什么:

  • 分析說明:
    • -dump-ast 語法和類型檢查,打印 AST 語法樹
    • -dump-parse 語法檢查,打印 AST 語法樹
    • -dump-pcm 轉儲有關預編譯 Clang 模塊的調試信息
    • -dump-scope-maps expanded-or-list-of-line:column
      Parse and type-check input file(s) and dump the scope map(s)
    • -dump-type-info Output YAML dump of fixed-size types from all imported modules
    • -dump-type-refinement-contexts
      Type-check input file(s) and dump type refinement contexts(s)
    • -emit-assembly Emit assembly file(s) (-S)
    • -emit-bc 輸出一個 LLVM 的 BC 文件
    • -emit-executable 輸出一個可執(zhí)行文件
    • -emit-imported-modules 展示導入的模塊列表
    • -emit-ir 展示 IR 中間代碼
    • -emit-library 輸出一個 dylib 動態(tài)庫
    • -emit-object 輸出一個 .o 機器文件
    • -emit-pcm Emit a precompiled Clang module from a module map
    • -emit-sibgen 輸出一個 .sib 的原始 SIL 文件
    • -emit-sib 輸出一個 .sib 的標準 SIL 文件
    • -emit-silgen 展示原始 SIL 文件
    • -emit-sil 展示標準的 SIL 文件
    • -index-file 為源文件生成索引數據
    • -parse 解析文件
    • -print-ast 解析文件并打印(漂亮/簡潔的)語法樹
    • -resolve-imports 解析 import 導入的文件
    • -typecheck 檢查文件類型

二、SIL

① 什么是 SIL 分析?
  • SIL 依賴于 swift 的類型系統(tǒng)和聲明,所以 SIL 語法是 swift 的延伸。一個 sil 文件是一個增加了 SIL 定義的 swift 源文件;
  • SIL 文件中沒有隱式 import,如果使用 swift 或者 Buildin 標準組件的話必須明確的引入;
  • SIL 函數由一個或多個 block 組成,一個 block 是一個指令的線性序列,每個 block 中的最后一條指令將控制轉移到另一個 block,或從函數返回。
  • 如果想要對 SIL 的內容進行詳細地探索,請參考:2015 LLVM Developers’ Meeting。
② SIL 分析 mian 函數
  • 查看抽象語法樹之后,繼續(xù)在終端中調用 swiftc -emit-sil main.swift >> ./main.sil && code main.sil 命令,生成 main.sil 文件;
  • 用 VSCode 打開 SIL 文件:
// main //`@main`:標識當前main.swift的`入口函數`,SIL中的標識符名稱以`@`作為前綴 sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { //`%0、%1` 在SIL中叫做寄存器,可以理解為開發(fā)中的常量,一旦賦值就不可修改,如果還想繼續(xù)使用,就需要不斷的累加數字(注意:這里的寄存器,與`register read`中的寄存器是有所區(qū)別的,這里是指`虛擬寄存器`,而`register read`中是`真寄存器`) bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): //`alloc_global`:創(chuàng)建一個`全局變量`,即代碼中的`t`alloc_global @$s4main1tAA10YDWTeacherCvp // id: %2 //`global_addr`:獲取全局變量地址,并賦值給寄存器%3%3 = global_addr @$s4main1tAA10YDWTeacherCvp : $*YDWTeacher // user: %7 //`metatype`獲取`YDWTeacher`的`MetaData`賦值給%4%4 = metatype $@thick YDWTeacher.Type // user: %6 //將`__allocating_init`的函數地址賦值給 %5// function_ref YDWTeacher.__allocating_init()%5 = function_ref @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // user: %6 //`apply`調用 `__allocating_init` 初始化一個變量,賦值給%6%6 = apply %5(%4) : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // user: %7 //將%6的值存儲到%3,即全局變量的地址(這里與前面的%3形成一個閉環(huán))store %6 to %3 : $*YDWTeacher // id: %7 //構建`Int`,并`return`%8 = integer_literal $Builtin.Int32, 0 // user: %9%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10return %9 : $Int32 // id: %10 } // end sil function 'main'
  • 分析:
    • @main 這?標識當前 main.swift 的??函數,SIL 中的標識符名稱以 @ 作為前綴;
    • %0, %1… 在 SIL 也叫做寄存器,這?可以理解為?常開發(fā)中的常量,?旦賦值之后就不可以再修改,如果 SIL 中還要繼續(xù)使?,那么就不斷的累加數值。 同時這?所說的寄存器是虛擬的,最終運?到機器上,會使?真的寄存器;
    • alloc_gobal:創(chuàng)建?個全局變量;
    • global_addr: 拿到全局變量的地址,賦值給 %3;
    • metatype 拿到 YDWTeacher 的 Metadata 賦值給 %4 將 __allocating_init 的函數地址賦值給 %5;
    • __apply 調? __allocating_init , 并把返回值給 %6;
    • 將 %6 的值存儲到 %3(也就是剛剛創(chuàng)建的全局變量的地址);
    • 構建 Int , 并 return;
  • 注意:code 命令是在 .zshrc 中做了如下配置,可以在終端中指定軟件打開相應文件:
$ open .zshrc // ****** 添加以下別名 alias subl='/Applications/SublimeText.app/Contents/SharedSupport/bin/subl' alias code='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code'// ****** 使用 $ code main.sil// 如果想SIL文件高亮,需要安裝插件:VSCode SIL
  • 從 SIL 文件中,可以看出,代碼是經過混淆的,可以通過以下命令還原,以s4main1tAA10YDWTeacherCvp 為例:xcrun swift-demangle s4main1tAA10YDWTeacherCvp,結果如下:
xcrun swift-demangle s4main1tAA10YDWTeacherCvp$s4main1tAA10YDWTeacherCvp ---> main.t : main.YDWTeacher
  • 在 SIL 文件中搜索 s4main10YDWTeacherCACycfC,其內部實現主要是分配內存+初始化變量:
    • allocing_ref:創(chuàng)建一個 YDWTeacher 的實例對象,當前實例對象的引用計數為1;
    • 調用init方法;
// ********* main入口函數中代碼 *********%5 = function_ref @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // s4main10YDWTeacherCACycfC 實際就是__allocating_init()// YDWTeacher.__allocating_init()sil hidden [exact_self_class] @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher {// %0 "$metatype"bb0(%0 : $@thick YDWTeacher.Type):// 堆上分配內存空間%1 = alloc_ref $YDWTeacher // user: %3// function_ref YDWTeacher.init() 初始化當前變量%2 = function_ref @$s4main10YDWTeacherCACycfc : $@convention(method) (@owned YDWTeacher) -> @owned YDWTeacher // user: %3// 返回%3 = apply %2(%1) : $@convention(method) (@owned YDWTeacher) -> @owned YDWTeacher // user: %4return %3 : $YDWTeacher // id: %4} // end sil function '$s4main10YDWTeacherCACycfC'
  • SIL語言對于Swift源碼的分析是非常重要的,關于其更多的語法信息,可以參考:Swift Intermediate Language (SIL)。

三、符號斷點調試

  • 在我們的 TestSwift 工程中設置“__allocating_init”符號斷點;

  • 然后執(zhí)行,可以看到:內部調用的是swift_allocObject;

四、源碼分析

  • 在 VSCode 中的REPL(命令交互行,類似于python的,可以在這里編寫代碼)中編寫如下代碼(也可以拷貝),并搜索 *_swift_allocObject 函數加一個斷點,如下所示:

  • 然后初始化一個實例對象t,回車:

  • 這里的 Local 中可以看出:requiredSize 是內存大小,requiredAlignmentMask 是內存對齊方式;requiredAlignmentMask 是 swift 中的字節(jié)對齊方式,這個和 OC 中是一樣的,必須是8的倍數,不足的會自動補齊,目的是以空間換時間,來提高內存操作效率;
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; }
  • swift_allocObject 的源碼如下,主要分為:
    • 通過 swift_slowAlloc 分配內存,并進行內存字節(jié)對齊;
    • 通過 new + HeapObject + metadata 初始化一個實例對象;
    • 函數的返回值是 HeapObject 類型,所以當前對象的內存結構就是 HeapObject 的內存結構;
  • 進入 swift_slowAlloc 函數,其內部主要是通過 malloc 在堆中分配 size 大小的內存空間,并返回內存地址,主要是用于存儲實例變量:
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__)p = malloc_zone_malloc(DEFAULT_ZONE(), size); #else// 堆中創(chuàng)建size大小的內存空間,用于存儲實例變量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; }
  • 進入 HeapObject 初始化方法,需要兩個參數:metadata、refCounts:
struct HeapObject {/// This is always a valid pointer to a metadata object.HeapMetadata const *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 objectconstexpr HeapObject(HeapMetadata const *newMetadata,InlineRefCounts::Immortal_t immortal): metadata(newMetadata), refCounts(InlineRefCounts::Immortal){ }
  • 分析:
    • 其中 metadata 類型是 HeapMetadata,是一個指針類型,占8字節(jié);
    • refCounts(引用計數,類型是 InlineRefCounts,而 InlineRefCounts 是一個類 RefCounts 的別名,占8個字節(jié)),swift 采用 arc 引用計數;

五、總結

  • 對于實例對象 t 來說,其本質是一個 HeapObject 結構體,默認 16 字節(jié)內存大小(metadata 8字節(jié) + refCounts 8字節(jié)),與 OC 的對比如下:
    • OC 中實例對象的本質是結構體,是以 objc_object 為模板繼承的,其中有一個 isa 指針,占 8 字節(jié);
    • Swift 中實例對象,默認的比 OC 中多了一個。refCounted 引用計數大小,默認屬性占 16 字節(jié);
  • Swift 中對象的內存分配流程是:_allocating_init --> swift_allocObject --> _swift_allocObject --> swift_slowAlloc --> malloc;
  • init 在其中的職責就是初始化變量,這點與 OC 中是一致的。

總結

以上是生活随笔為你收集整理的Swift之深入解析“对象”的底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。