Swift類、對(duì)象、屬性

Swift編譯簡(jiǎn)介

SIL介紹

  • SIL是Swift Intermediate Language的簡(jiǎn)寫(xiě),SIL會(huì)對(duì)Swift進(jìn)行高級(jí)別的語(yǔ)意分析和優(yōu)化,我們通過(guò)解讀SIL代碼就能去了解Swift背后的一些實(shí)現(xiàn)細(xì)節(jié),幫助我們理解一些問(wèn)題。
  • IOS開(kāi)發(fā)語(yǔ)言不管OC還是Swift后端都是通過(guò)LLVM編譯的


    image.png
  • OC通過(guò)Clang編譯器前端,編譯成IR,然后再生成目標(biāo)文件.o
  • Swift是通過(guò)Swift編譯器前端,編譯成IR,再生成目標(biāo)文件.o
  • Swift編譯過(guò)程都經(jīng)歷了哪些步驟


    image.png
  • Swift語(yǔ)言也是基于LLVM架構(gòu)的,你可以看到與現(xiàn)有Objective-C語(yǔ)言有很多相似之處,有相同的后端結(jié)構(gòu),前端的工作流程也基本類似,詞法分析,標(biāo)記,構(gòu)建AST,類型檢查,主要區(qū)別在于生成IR之前,AST之后,增加了SIL,它是AST和LLVM IR之間的另一種中間代碼表示形式。
    這么做的目的是希望彌補(bǔ)一些Clang編譯器的缺陷,如無(wú)法執(zhí)行一些高級(jí)分析,可靠的診斷和優(yōu)化,而AST和LLVM IR都不是合適的選擇。因此,SIL應(yīng)運(yùn)而生,用來(lái)解決現(xiàn)有的缺陷。
  • 通過(guò)命令查看swiftc能做什么樣的事情


    image.png

SIL分析

  • 在main.swift中輸入如下代碼
class XQTeacher {
    var age:Int = 18
    var name:String = "xq"
}

var t = XQTeacher()
  • 通過(guò)命令swiftc -dump-ast main.swift查看語(yǔ)法樹(shù)

    image.png

  • 通過(guò)命令swiftc -emit-sil main.swift >> ./main.sil生成main.sil并用VSCode打開(kāi)它,我們來(lái)研究它的main函數(shù)

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @$s4main1tAA9XQTeacherCvp          // id: %2
  %3 = global_addr @$s4main1tAA9XQTeacherCvp : $*XQTeacher // user: %7
  %4 = metatype $@thick XQTeacher.Type            // user: %6
  // function_ref XQTeacher.__allocating_init()
  %5 = function_ref @$s4main9XQTeacherCACycfC : $@convention(method) (@thick XQTeacher.Type) -> @owned XQTeacher // user: %6
  %6 = apply %5(%4) : $@convention(method) (@thick XQTeacher.Type) -> @owned XQTeacher // user: %7
  store %6 to %3 : $*XQTeacher                    // id: %7
  %8 = integer_literal $Builtin.Int32, 0          // user: %9
  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
  return %9 : $Int32                              // id: %10
} // end sil function 'main'
  • @main這里標(biāo)識(shí)我們當(dāng)前main.swift 的入口函數(shù),SIL中的標(biāo)識(shí)符名稱以@作為前綴
  • %0,%1...在SIL也叫做寄存器,這里我們可以理解為我們?nèi)粘i_(kāi)發(fā)中的常量,一旦賦值之后就不可以再修改,如果SIL中還要繼續(xù)使用,那么就不斷的累加數(shù)字。同時(shí)這里所說(shuō)的寄存器是虛擬的,最終運(yùn)行到我們的機(jī)器上,會(huì)使用真的寄存器。
  • alloc_gobal創(chuàng)建一個(gè)全局變量
  • global_ addr拿到全局變量的地址,賦值給%3
  • metatype拿到XQTeacher的Metadata賦值給%4
  • 將 allocating_init的函數(shù)地址賦值給%5
  • apply調(diào)用 allocating_init,并把返回值給%6
  • 將%6的值存儲(chǔ)到%3(也就是我們剛剛創(chuàng)建的全局變量的地址)
  • 構(gòu)建Int,并return
  • XQTeacher.__allocating_init()的實(shí)現(xiàn)
// XQTeacher.__allocating_init()
sil hidden [exact_self_class] @$s4main9XQTeacherCACycfC : $@convention(method) (@thick XQTeacher.Type) -> @owned XQTeacher {
// %0 "$metatype"
bb0(%0 : $@thick XQTeacher.Type):
  %1 = alloc_ref $XQTeacher                       // user: %3
  // function_ref XQTeacher.init()
  %2 = function_ref @$s4main9XQTeacherCACycfc : $@convention(method) (@owned XQTeacher) -> @owned XQTeacher // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned XQTeacher) -> @owned XQTeacher // user: %4
  return %3 : $XQTeacher                          // id: %4
} // end sil function '$s4main9XQTeacherCACycfC'

通過(guò)符號(hào)斷點(diǎn)調(diào)試

  • 在代碼新增符號(hào)斷點(diǎn)_allocing_init,運(yùn)行代碼
    image.png
  • 往下執(zhí)行到swift_allocObject
    image.png

源碼調(diào)試

  • 在編譯好的源碼的_swift_allocObject_方法加入一個(gè)斷點(diǎn)
    image.png
  • REPL(命令交互行)中編寫(xiě)代碼(也可以拷貝)
    image.png
  • 查看左上角的調(diào)試數(shù)據(jù)其中requiredSize是分配的實(shí)際內(nèi)存大小,為40
  • requiredAlignmentMask是swift中的字節(jié)對(duì)齊長(zhǎng)度,為8

swift_allocObject

  • _swift_allocObject_的源碼實(shí)現(xiàn)
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));//分配內(nèi)存

  // 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);//初始化實(shí)例對(duì)象

  // 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_slowAlloc分配內(nèi)存
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
    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;
}
  • new (object) HeapObject(metadata);初始化實(shí)例對(duì)象
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;
  // InlineRefCounts refCounts
  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)
  { }
  ......
}
  • 實(shí)例對(duì)象是以HeapObject來(lái)創(chuàng)建的,HeapObject里面有一個(gè)HeapMetadata類型的metadata,還有一個(gè)引用計(jì)數(shù)refCounts
using HeapMetadata = TargetHeapMetadata<InProcess>;
struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};
  • 其內(nèi)部結(jié)構(gòu)如圖


    image.png
  • 得到最終Swiftmetadata結(jié)構(gòu)體的構(gòu)成
struct swift_class_t: NSObject{
    void *kind;//相當(dāng)于OC中的isa,kind的實(shí)際類型是unsigned long
    void *superClass;
    void *cacheData;
    void *data;
    uint32_t flags; //4字節(jié)
    uint32_t instanceAddressOffset;//4字節(jié)
    uint32_t instanceSize;//4字節(jié)
    uint16_t instanceAlignMask;//2字節(jié)
    uint16_t reserved;//2字節(jié)
    
    uint32_t classSize;//4字節(jié)
    uint32_t classAddressOffset;//4字節(jié)
    void *description;
    ...
}

Swift屬性

存儲(chǔ)屬性

  • 存儲(chǔ)屬性要么用let修飾的常量,要么是var修飾的變量
class XQTeacher {
    var age:Int = 18
    var name:String = "xq"
}
  • 上面的兩個(gè)屬性就是變量存儲(chǔ)屬性,我們也可以通過(guò)SIL查看
class XQTeacher {
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @_hasStorage @_hasInitialValue var name: String { get set }
  @objc deinit
  init()
}

計(jì)算屬性

  • 計(jì)算屬性:是指不占用內(nèi)存空間,本質(zhì)是set/get方法的屬性
  • 通過(guò)打印下面這個(gè)類的實(shí)例對(duì)象內(nèi)存大小,打印結(jié)果是24,HeapObject默認(rèn)有16個(gè)字節(jié)的內(nèi)存,再加上width占8個(gè)字節(jié)
class Square {
    var width:Double = 10
    var area:Double {
        get {
            Double(width * width)
        }
        set {
           width = sqrt(newValue)
        }
    }
}
print(class_getInstanceSize(Square.self))
  • 查看sil文件,看到存儲(chǔ)屬性只有width,而area的本質(zhì)就是setget方法
class Square {
  @_hasStorage @_hasInitialValue var width: Double { get set }
  var area: Double { get set }
  @objc deinit
  init()
}

屬性觀察者

  • 屬性觀察者就是willSetdidSet
class XQTeacher {
    var name:String = "xq" {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
}
var t = XQTeacher()
t.name = "dp"
  • 代碼執(zhí)行結(jié)果,可以知道在新值存儲(chǔ)之前調(diào)用了willSet,新值存儲(chǔ)之后調(diào)用了didSet
newValue:dp
oldValue:xq
  • 觀察sil,驗(yàn)證上文的結(jié)論


    image.png
  • 再來(lái)看下一個(gè)情況,在init方法里面調(diào)用set方法
class XQTeacher {
    var name:String = "xq" {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
    init() {
        self.name = "test"
    }
}
var t = XQTeacher()
  • 發(fā)現(xiàn)它并不會(huì)觸發(fā)屬性觀察者,原因是我們?cè)诔跏蓟臅r(shí)候設(shè)置屬性值的時(shí)候,會(huì)先調(diào)用memset清理內(nèi)存空間,因?yàn)檫@塊內(nèi)存可能有殘留的臟數(shù)據(jù),此時(shí)我們?cè)?code>didSet里面調(diào)用其他屬性時(shí)有可能其他屬性內(nèi)存未被分配完成會(huì)訪問(wèn)一片未知的內(nèi)存空間,Swift是一門(mén)安全性的語(yǔ)言這種操作是不允許的

  • 那些地方可以添加屬性觀察者

  • 類中定義的存儲(chǔ)屬性

class XQTeacher {
    var name:String = "xq" {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
}
  • 繼承的存儲(chǔ)屬性
class XQSpecialTeacher: XQTeacher {
    override var name: String {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
}
  • 繼承的計(jì)算屬性
class XQTeacher {
    var age:Int = 18
    
    var age2:Int {
        get {
            return age
        }
        set {
            age = newValue
        }
    }
}

class XQSpecialTeacher: XQTeacher {
    override var age2: Int {
        willSet {
            print("override newValue:\(newValue)")
        }
        didSet {
            print("override oldValue:\(oldValue)")
        }
    }
}
  • 父類和子類都實(shí)現(xiàn)了屬性觀察者,它的調(diào)用順序
class XQTeacher {
    var age:Int = 18 {
        willSet {
            print("newValue:\(newValue)")
        }
        didSet {
            print("oldValue:\(oldValue)")
        }
    }
    
    var age2:Int {
        get {
            return age
        }
        set {
            age = newValue
        }
    }
}

class XQSpecialTeacher: XQTeacher {
    override var age: Int {
        willSet {
            print("override newValue:\(newValue)")
        }
        didSet {
            print("override oldValue:\(oldValue)")
        }
    }
}
  • 調(diào)用順序如下,子類和父類都實(shí)現(xiàn)了屬性觀察者,會(huì)先調(diào)用子類的willSet,再調(diào)用父類的willSet,再調(diào)用父類的didSet,最后調(diào)用子類的didSet
override newValue:99
newValue:99
oldValue:18
override oldValue:18
  • 我們?cè)谧宇惖?code>init方法里面調(diào)用set方法會(huì)不會(huì)觸發(fā)屬性觀察者呢
class XQSpecialTeacher: XQTeacher {
    override var age: Int {
        willSet {
            print("override newValue:\(newValue)")
        }
        didSet {
            print("override oldValue:\(oldValue)")
        }
    }
    override init() {
        super.init()
        self.age = 66
    }
}
  • 通過(guò)打印結(jié)果可以看到,是會(huì)調(diào)用的,原因是調(diào)用了父類的init已經(jīng)有默認(rèn)值了即屬性已經(jīng)初始化完成了,是可以調(diào)用屬性觀察者的。

延遲存儲(chǔ)屬性

  • 1、使用lazy修飾的存儲(chǔ)屬性
class XQTeacher {
   lazy var age:Int = 18
}
  • 2、延遲屬性必須有一個(gè)默認(rèn)的初始值,沒(méi)有初始值會(huì)報(bào)錯(cuò)


    image.png
  • 3、延遲存儲(chǔ)在第一次訪問(wèn)的時(shí)候才被賦值


    image.png
  • 4、延遲存儲(chǔ)屬性并不能保證線程安全

  • 5、延遲存儲(chǔ)屬性對(duì)實(shí)例對(duì)象大小的影響,我們不使用延遲存儲(chǔ)屬性的時(shí)候內(nèi)存大小為24,使用之后為32


    image.png
  • 通過(guò)sil分析延遲存儲(chǔ)屬性

類型屬性

  • 類型屬性屬于這個(gè)類本身,不管有多少實(shí)例,類型屬性只有一份,我們使用static來(lái)聲明一個(gè)類型屬性
class XQTeacher {
    static var age : Int = 18
}
var t = XQTeacher()
XQTeacher.age = 20
  • 通過(guò)查看sil文件,可以看到多出了一個(gè)全局變量,類型屬性其實(shí)是一個(gè)全局變量
// static XQTeacher.age
sil_global hidden @$s4main9XQTeacherC3ageSivpZ : $Int
  • 查看到main函數(shù)獲取age的方法,調(diào)用的是XQTeacher.age.unsafeMutableAddressor
    image.png
  • XQTeacher.age.unsafeMutableAddressor里面調(diào)用了globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
// XQTeacher.age.unsafeMutableAddressor
sil hidden [global_init] @$s4main9XQTeacherC3ageSivau : $@convention(thin) () -> Builtin.RawPointer {
bb0:
  %0 = global_addr @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0 : $*Builtin.Word // user: %1
  %1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer // user: %3
  // function_ref globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
  %2 = function_ref @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 : $@convention(c) () -> () // user: %3
  %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $()
  %4 = global_addr @$s4main9XQTeacherC3ageSivpZ : $*Int // user: %5
  %5 = address_to_pointer %4 : $*Int to $Builtin.RawPointer // user: %6
  return %5 : $Builtin.RawPointer                 // id: %6
} // end sil function '$s4main9XQTeacherC3ageSivau'
  • globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0的實(shí)現(xiàn)
// globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
sil private @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 : $@convention(c) () -> () {
bb0:
  alloc_global @$s4main9XQTeacherC3ageSivpZ       // id: %0 
  %1 = global_addr @$s4main9XQTeacherC3ageSivpZ : $*Int // user: %4 獲取到age的地址
  %2 = integer_literal $Builtin.Int64, 18         // user: %3  初始化的值18
  %3 = struct $Int (%2 : $Builtin.Int64)          // user: %4
  store %3 to %1 : $*Int                          // id: %4  將%3的值放到地址中
  %5 = tuple ()                                   // user: %6
  return %5 : $()                                 // id: %6
} // end sil function 'globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0'
  • 通過(guò)匯編調(diào)試查看


    image.png
  • 繼續(xù)執(zhí)行


    image.png
  • 可以看到會(huì)調(diào)用swift_once,表示全局變量只會(huì)初始化一次,對(duì)應(yīng)sil中的builtin "once"
  • 通過(guò)源碼查看swift_once的實(shí)現(xiàn),它的底層是調(diào)用一個(gè)gcd的單次執(zhí)行函數(shù)
/// Runs the given function with the given context argument exactly once.
/// The predicate argument must point to a global or static variable of static
/// extent of type swift_once_t.
void swift::swift_once(swift_once_t *predicate, void (*fn)(void *),
                       void *context) {
#if defined(__APPLE__)
  dispatch_once_f(predicate, context, fn);
#elif defined(__CYGWIN__)
  _swift_once_f(predicate, context, fn);
#else
  std::call_once(*predicate, [fn, context]() { fn(context); });
#endif
}

實(shí)現(xiàn)單列

  • 通過(guò)上文對(duì)static的了解,我們可以用static來(lái)實(shí)現(xiàn)一個(gè)單列
class XQTeacher {
    static let sharedInstance = XQTeacher()
    
    private init() {
        
    }
}
var t = XQTeacher.sharedInstance
最后編輯于
?著作權(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)容