自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

在 Swift 中如何正確傳遞 Unsafe Pointers 參數(shù)

開發(fā)
在過去一個季度抖音規(guī)?;涞?Swift 組件的過程中,我負責(zé)的代碼在 CI 運行單測階段暴露了幾個問題,都與 Swift 中的 unsafe pointers 有關(guān)。

TL;DR

  • Swift 中對于類型大小為空的變量使用 & 取地址是未定義行為,編譯為目標(biāo)碼之后的體現(xiàn)為一個根據(jù)之前代碼執(zhí)行結(jié)果產(chǎn)生的任意數(shù)值。這是一個 feature。
  • Swift 中在多個線程中對同一個變量使用 & 將獲取「寫訪問」,會造成運行時崩潰。
  • Swift 中對 computed property 取地址會取到臨時變量的地址。如果 computed property 是一個鎖,將造成鎖被拷貝到多個線程的執(zhí)行棧上,造成程序錯誤。

平平無奇但錯誤的代碼

在過去一個季度抖音規(guī)?;涞?Swift 組件的過程中,我負責(zé)的代碼在 CI 運行單測階段暴露了幾個問題,都與 Swift 中的 unsafe pointers 有關(guān)。

第一個是通過 Objective-C 中 associated object 技巧擴展出來的 property 在 release build 后再運行,set 之后只能 get 到 nil;debug build 下則正常:

圖片

范例代碼一

第二個是下列代碼在 release build 后,在多線程環(huán)境有可能崩潰在 swift_endAccess 函數(shù)中:

@_implementationOnly import Darwin


public class UnfairLock {


    var _lock: os_unfair_lock
    
    public func withLock<R>(perform action: () -> R) -> R {
        os_unfair_lock_lock(&_lock)
        defer {
            os_unfair_lock_unlock(&_lock)
        }
        return action()
    }
    
    public init() {
        _lock = os_unfair_lock()
    }
    
}

范例代碼二

圖片

是不是覺得很奇怪?以上兩段代碼既符合直覺,也沒有編譯錯誤。那么,為什么會產(chǎn)生上述問題呢?

歸因:ObjC 關(guān)聯(lián)對象訪存出錯

針對范例代碼一里面 ObjC associated object 訪存得到錯誤結(jié)果的問題,我們可以進入?yún)R編模式,看看到底 objc_get(set)AssociatedObject 得到的參數(shù)是什么。首先打開 Xcode 的 Always Show Disassembly(看完文章后記得關(guān)閉哦),在 objc_getAssociatedObject  objc_setAssociatedObject 打下斷點。

圖片

運行后我們可以看到 objc_setAssociatedObject  key 這個參數(shù)(arm64 上的 x1 寄存器)的值是 0x04000001ed295c71,這個地址存儲的值是 0x00000001ed295c71。我們預(yù)期通過 dis -s 指令對這個地址進行反匯編可以獲得該地址對應(yīng)的二進制鏡像名稱,然而這里卻提示「反匯編失敗」。這是為什么呢?

圖片

我們可以進一步檢查 x1 寄存器內(nèi)容的來源。下圖紅線為 x1 寄存器在 MyObject.myProperty.setter (下稱 setter 函數(shù))內(nèi)的數(shù)據(jù)流??梢钥吹剑?/p>

  1. setter 函數(shù)初始棧高為 0x20 (sub sp, sp, #0x20)
  2. x1 為 setter 函數(shù)執(zhí)行?;鶞?zhǔn)地址 + 0x40 - 0x48 = setter 函數(shù)執(zhí)行棧基準(zhǔn)地址 - 0x8。所以對應(yīng) setter 函數(shù)執(zhí)行棧上 0x18 偏移的棧變量

圖片

而 setter 函數(shù)開頭調(diào)用的 Optional<Any> 的拷貝初始化函數(shù) outlined init with copy of Swift.Optional<Any> 的第二個參數(shù) x1 為 setter 函數(shù)執(zhí)行棧基準(zhǔn)地址 + 0x40 - 0x60 = setter 函數(shù)執(zhí)行?;鶞?zhǔn)地址 - 0x20。結(jié)合之前獲得的 setter 函數(shù)執(zhí)行棧高 0x20 的信息,所以對應(yīng) setter 函數(shù)執(zhí)行棧上 0x0 偏移的棧變量。

于是我們可以知道,setter 函數(shù)執(zhí)行?;鶞?zhǔn)地址后的所有空間都有可能被 Optional<Any> 的拷貝初始化函數(shù)利用到。而實際上這個拷貝初始化函數(shù)的第二個參數(shù)就是拷貝操作的目標(biāo)地址。加上上圖中藍線的原點在判別完 x21 的內(nèi)容(即 objc_setAssociatedObject 接受到的 x1)之后進行了條件跳轉(zhuǎn)(cbz,即 conditional branching if zero 的縮寫),跳過的內(nèi)容正是把 Any 橋接到 Objective-C(因為中途有調(diào)用 Swift._bridgeAnythingToObjectiveC),所以我們可以大膽猜測:

x21 —— 即 objc_setAssociatedObject 接受到的 x1,實際上是可以判斷 Optional<Any> 為空的信息——而這個并不是我們在源碼中給定的 key——這就是導(dǎo)致我們使用 objc_get(set)AssociatedObject 不正常工作的原因。而我們一開 dis -s 之所以會失敗,是因為我們在嘗試對函數(shù)執(zhí)行棧進行反匯編。

深入:Void 全局變量編譯細節(jié)

為了了解更多代碼生成細節(jié),知道為什么生成了這樣的代碼,我們可以使用 Swift 編譯器的 -emit-sil(gen)  -emit-ir(gen) 參數(shù)來考察 SIL(原始 SIL)和 IR(原始 IR)的生成結(jié)果,看到底是哪一步產(chǎn)生了意外。檢查的順序應(yīng)該 -emit-silgen, -emit-sil, -emit-irgen, -emit-ir,確保先檢查原始 SIL(SILGen)和原始 IR(IRGen),再檢查優(yōu)化后的 SIL 和 IR。檢查 SILGen 的命令如下:

// 這里我保存的文件叫 UnsafePointers.swift
// 我的 Xcode 放置的路徑是 /Applications/Xcode-15.0.app
// 大家可以根據(jù)自己的情況對以下命令進行修改:
xcrun swift-frontend -c UnsafePointers.swift \
    -enable-objc-interop \
    -target arm64-apple-macos14.0 \
    -sdk /Applications/Xcode-15.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
    -emit-silgen > UnsafePointers.silgen.sil

上文的 setter 函數(shù)在 SILGen 中生成了三個區(qū)塊:bb0 是函數(shù)入口,在 363 行進行 switch-case 之后跳轉(zhuǎn)至 bb1  bb2。但不論是 bb1 還是 bb2,最后都會跳轉(zhuǎn)至 bb3(如下圖藍線所示)。所以我們直接看 bb3 好了。

圖片

所以我們直接折疊 bb1  bb2,然后可以畫出 objc_setAssociatedObject 第二個參數(shù) %32 的數(shù)據(jù)流??梢钥吹狡渥罱K來自于 %2,而 %2 會對一個全局 Swift 符號取地址。

圖片

而這個符號正是我們定義的 myKey。

圖片

因為 global_addr 是 SIL 指令,已經(jīng)是 SIL 這一層的「原語(最小不可分割語素)」了,所以我們應(yīng)該進一步查看 IRGen 的結(jié)果。

xcrun swift-frontend -c UnsafePointers.swift \
    -enable-objc-interop \
    -target arm64-apple-macos14.0 \
    -sdk /Applications/Xcode-15.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
    -emit-irgen > UnsafePointers.irgen.ll

在 IRGen 結(jié)果中,我們可以直接搜索 MyObject.myProperty.setter 的 Swift 改編符號(如果你也使用 UnsafePointers.swift 這個文件名那么就是 $s14UnsafePointers8MyObjectC10myPropertyypSgvs)。我們可以看到,517 行給 objc_setAssociatedObject 的第二個參數(shù)已經(jīng)變成了 undef。

圖片

為了進一步探究 SIL 中的 global_addr 指令為何在 lower 到 IR 之后會得到 undef,我們可以動態(tài)調(diào)試一下 Swift 編譯器。這里我們使用簡化后的代碼以加速編譯器調(diào)試。因為 foo 是全局變量,所以第 4 行的 &foo 仍然會生成 global_addr 指令。

var foo: Void = Void()


func bar() -> UnsafeRawPointer {
    UnsafeRawPointer(&foo)
}

然后我們在 IRGenSILFunction::visitGlobalAddrInst 中打下斷點,編譯上面的源代碼,從 DEBUG CONSOLE 中的 i-> dump() 結(jié)果可以看到,此次 GlobalAddrInst 實例 i 的內(nèi)容為被 & 引用的變量。最后代碼執(zhí)行進入了 2950 行,這里可以看到針對該全局變量的 ti: TypeInfo&,如果其 isKnownEmpty 返回 true 就不會生成符號,因此地址是 undef。這是一個 feature。

圖片

圖片

而 Swift 編譯器中的 TypeInfo 類型負責(zé)記載類型信息對應(yīng)信息。對于 TypeInfo::isKnownEmpty 而言,簡單來說如果可以在編譯器在編譯時可以確定大小的類型,并且大小為空,即可認為其會返回 true

歸因:多線程下取地址崩潰

要理解文章開頭范例代碼二中所出現(xiàn)的「多線程下對實例變量取地址」而導(dǎo)致的崩潰,更直接的方法是打開 Xcode 的 Thread Sanitizer 后運行程序。我們可以看到,Xcode 幫我們檢測出了「訪問競爭(access race)」。這是因為在 Swift 中對變量使用 & 即意味著需要獲取一個「寫訪問(write access)」,而目前的代碼有多個線程在訪問 UnfairLock.withLock,那么也就有多個線程在嘗試對 UnfairLock._lock 獲取「寫訪問」。這個在 Swift 中不符合運行時 exclusivity enforcement,所以會崩潰。

圖片

圖片

想要修復(fù)這個問題,將獲得指針的時機移動至多線程代碼外(即 x.testLock 外)即可。

但是 exclusivity 沖突并不是這個寫法的全部問題,這個寫法還有一個非常隱蔽的問題:& 可能取到的不是變量本身的地址,而是一個臨時分配的變量。要理解這個問題,需要知道 Swift 是如何實現(xiàn)變量取地址的。

深入:Swift 變量取地址實現(xiàn)

在 Swift 中 var 關(guān)鍵字定義的變量滿足以下抽象:

  • 一定包含一個 get accessor
  • 可選包含一個 set accessor
  • 可選包含一個存儲容器

然而,上面只是開發(fā)者在日常開發(fā)中能夠感知到的部分。上述抽象沒有解決的問題是:如果一個變量的存儲容器是可選的,那么我們應(yīng)該如何獲得這個變量的地址呢?所以編譯器還會為我們自動生成:

  • 一定包含一個 _read accessor
  • 可選包含一個 _modify accessor

其中 _read accessor 是一個對變量產(chǎn)生「讀訪問」,并且拋出一個只讀地址的協(xié)程。

 _modify accessor 是一個對變量產(chǎn)生「寫訪問」,并且拋出一個可寫地址的協(xié)程。

而通過上述 _read  _modify accessor,我們就定義了獲取 var 關(guān)鍵字變量地址的手段。

「協(xié)程」可以理解為不保證棧平衡的函數(shù)(或稱「過程」)。協(xié)程本是過程的原始形態(tài)——過程引入棧平衡是為了實現(xiàn)本地變量,而這個特性在協(xié)程中無法實現(xiàn)。但是人們后來發(fā)現(xiàn)非平衡的??梢宰尯罄m(xù)執(zhí)行的代碼沿用之前的棧內(nèi)存內(nèi)容,而不用重復(fù)在棧上傳參,又或者開辟堆空間傳參,所以人們又開始利用起了「協(xié)程」。Swift 引入?yún)f(xié)程的目的也是做性能優(yōu)化。 需要注意的是 Swift Concurrency 并不是協(xié)程。

而在 Swift 中對變量使用 & 本質(zhì)上就是獲取 _modify accessor 拋出的「變量可寫地址」。

我們可以從 SILGen 的結(jié)果中一窺究竟。

下面是 UnfairLock.withLock(perform:) 在未修改前的 SILGen 結(jié)果,紅色的線和方框代表了 &_lock 這句 Swift 源碼在 SIL 層面的數(shù)據(jù)流。

我們可以看到 &_lock 最初來自于 %6,而 %6 正是對 self (%2) 使用了 #UnfiarLock.lock!modify 這個協(xié)程產(chǎn)生的,同時產(chǎn)生的 %7 則是協(xié)程產(chǎn)生的非平衡棧的 resumption 函數(shù),由 89 行的 endApply 調(diào)用,用以恢復(fù)棧平衡。

圖片

而當(dāng)前實現(xiàn)的 UnfiarLock.lock._modify 則是通過 ref_element_addr 這條 SIL 指令直接拋出了 UnfairLock.lock 在當(dāng)前 self 中的地址。

圖片

但是,當(dāng)情況變得復(fù)雜一些的時候,這個 _modify accessor 拋出的將會變成一個臨時變量的地址——對,就是這個協(xié)程在非平衡棧里面分配的臨時變量。要造成這個結(jié)果很簡單:比如,把 os_unfair_lock 打包放入一個叫做 Data 的類型中,然后 var _lock: os_unfair_lock 改成 computed property,從 Data 中訪存 os_unfair_lock

private struct Data {
  var lock = os_unfair_lock()
}


public class UnfairLock {


  private var data = Data()


  internal var _lock: os_unfair_lock {
    get {
      data.lock
    }
    set {
      data.lock = newValue
    }
  }


  public func withLock<R>(perform action: () -> R) -> R {
    os_unfair_lock_lock(&_lock)
    defer {
      os_unfair_lock_unlock(&_lock)
    }
    return action()
  }


  public init() {
    
  }


}

我們可以看到,此時 UnfairLock._lock.modify 的 SILGen 結(jié)果中出現(xiàn)了棧分配(683 行),然后分配后的棧地址內(nèi)又被 store(復(fù)制)了 UnfairLock._lock (687 行),隨后棧分配后的地址被 _modify 拋出,爾后在 _modify 的 resumption 函數(shù)(bb1  bb2)中,棧地址中的內(nèi)容又被重新 set 回了 UnfairLock._lock(694 行及 703 行)。

圖片

上面這種行為在多線程場景就是災(zāi)難性質(zhì)的——因為每一個線程都有自己獨立的執(zhí)行棧,而這種行為就是把一個鎖復(fù)制到了每一個在競爭這把鎖的線程的執(zhí)行棧上再加解鎖——最后的結(jié)果一定是程序出錯。

深入:理解取地址中的臨時變量

要理解對 var 取地址時可能取到臨時變量的地址,還是需要回到 Swift 對 var 關(guān)鍵字定義的變量的抽象:

  • 一定包含一個 get accessor
  • 可選包含一個 set accessor
  • 可選包含一個存儲容器

而編譯器幫助合成 _read 或者 _modify 時,如果變量沒有實際存儲容器,那么也只能通過 get  set 實現(xiàn):當(dāng)出現(xiàn) computed property 時,其 _modify 會先通過 get accessor 創(chuàng)建一個臨時變量,拋出臨時變量地址之后,在 resumption 時再使用 set accessor 寫回這種實現(xiàn)了。

所以,如果開發(fā)者書寫如下代碼:

struct Foo {
    var _bar: Int = 0
    var bar: Int {
        get {
            self._bar
        }
        set {
            self._bar = newValue
        }
    }
}


var foo = Foo()


withUnsafeMutablePointer(to: &foo, body: handleIntPtr)


func handleIntPtr(_ ptr: UnsafeMutablePointer<Int>) {
    // ...
}

那么實際上編譯器會幫助合成并生成如下代碼:

struct Foo {
    var bar: Int {
        // ...
        _read { // 編譯器合成代碼
            let tempFoo = 棧分配
            tempFoo = self.bar.getter
            拋出不可變棧地址 tempFoo
        }
        _read.resumption { // 編譯器合成代碼
            棧析構(gòu) tempFoo
        }
        _modify { // 編譯器合成代碼
            var tempFoo = 棧分配
            tempFoo = self.bar.getter
            拋出可變棧地址 tempFoo
        }
        _modify.resumption { // 編譯器合成代碼
            self.bar.setter = tempFoo
            棧析構(gòu) tempFoo
        }
    }
}


var foo = Foo()


// 棧分配 tempFoo 以及隱式地址到指針轉(zhuǎn)換
let ptrToTempFoo: UnsafeMutablePointer<Int> = Foo.bar._modify(foo)


// 應(yīng)用 withUnsafeMutablePointer 的 body 閉包
handleIntPtr(ptrToTempFoo)


// 將臨時變量 set 回去,并完成 tempFoo 棧析構(gòu)
Foo.bar._modify.resumption(foo)

知道這一點之后,我們也可以嘗試手寫 UnfairLock._lock._modify 的實現(xiàn),直接拋出 data.lock 的地址,來消除臨時變量:

public class UnfairLock {


  private var data = Data()


  internal var _lock: os_unfair_lock {
    _read {
      yield data.lock
    }
    _modify {
      yield &data.lock
    }
  }
  
  // ...


}

此時我們可以通過 SILGen 的結(jié)果看到:UnfairLock._lock.modify 目前會委托到 UnfairLock.data.modify(84 行),然后利用 UnfairLock.data.modify 的結(jié)果,然后取出 Data.lock 的地址(85 行),最后拋出(86 行):

圖片

 UnfairLock.data.modify 則是直接拋出了 UnfairLock.data 在當(dāng)前 self 下的地址(第 61 行)。

圖片

上述技巧繞過了「使用 var 關(guān)鍵字變量的 get 和 set」來實現(xiàn) _modify,同時也產(chǎn)生了一個「副作用」。大家能想到是什么嗎?

最佳實踐及準(zhǔn)入建設(shè)

在實際的日常開發(fā)活動中,我們并不想耗費如此多的心智在如何處理好給 unsafe pointers 傳參上,所以我們需要一套最佳實踐以及自動化準(zhǔn)入機制來保證我們的日常開發(fā)的執(zhí)行結(jié)果。

上述問題可以歸結(jié)為:

  1.  Void 取地址出現(xiàn)無意義數(shù)值
  2. 多線程使用 & 對變量取地址后崩潰
  3. 對 computed property 取地址得到的是臨時變量地址

下面我們分情況討論

ObjC 關(guān)聯(lián)對象訪存出錯

前文「ObjC 關(guān)聯(lián)對象訪存結(jié)果出錯」的本質(zhì)是:Swift 中對于空大小類型的全局變量取地址編譯到 LLVM IR 后是 undef 的,編譯為目標(biāo)碼之后的體現(xiàn)為一個根據(jù)之前代碼執(zhí)行結(jié)果產(chǎn)生的任意數(shù)值。所以「對大小為空的類型的全局變量取地址」本身就是一個未定義行為。這里的最佳實踐就是不允許這樣做。在準(zhǔn)入建設(shè)方面,我們可以設(shè)立靜態(tài)分析規(guī)則進行檢出。依據(jù)公司現(xiàn)有靜態(tài)分析設(shè)施,我們可以分析:1)標(biāo)準(zhǔn)庫以及2)文件內(nèi)定義的空大小類型,并且3)有選擇地加入系統(tǒng)庫的類型定義。


多線程下取地址崩潰

前文「多線程下對實例變量取地址發(fā)生崩潰」的本質(zhì)是:Swift 中,在多個線程中對同一個變量獲取「寫訪問」會引發(fā)「訪問競爭」,這是 Swift 運行時在開啟 runtime exclusivity enforcement 之后所不允許的。相關(guān)最佳實踐應(yīng)該是將「寫訪問」提出多線程代碼:

比如下列代碼通過 DispatchQueue.concurrentPerform 這個并發(fā)執(zhí)行的接口,在多個線程中通過 & 取地址對同一個變量 counter 獲取了「寫訪問」:

// ?
var counter = AtomicIntStorage() // zero init
DispatchQueue.concurrentPerform(iterations: 10) { _ in
  for _ in 0 ..< 1_000_000 {
    atomicFetchAddInt(&counter, 1)  // Exclusivity violation
  }
}
print(atomicLoadInt(&counter) // ???

我們可以將相關(guān)代碼提取出 DispatchQueue.concurrentPerform 的尾閉包:

// ?
var counter = AtomicIntStorage() // zero init
withUnsafeMutablePointer(to: &counter) { pointer in
  DispatchQueue.concurrentPerform(iterations: 10) { _ in
    for _ in 0 ..< 1_000_000 {
      atomicFetchAddInt(pointer, 1) // OK
    }
  }
  print(atomicLoadInt(pointer) // 10_000_000
}

但是上面是發(fā)生在極其局部的問題。泛化而言,面對運行時訪問競爭,蘋果使用 SIL 這種檢測能力很強的靜態(tài)檢測手段亦無法檢出,我們即可判斷:對于運行時的行為,我們需要運行時的設(shè)施進行檢測,所以我們需要研發(fā)流程準(zhǔn)入同時進行調(diào)整:

  1. 研發(fā)流程中加入研判是否需要設(shè)計多線程測試用例的步驟;如果需要設(shè)計,則需要單獨在評審時提供多線程測試用例
  2. 準(zhǔn)入調(diào)整為要求單元測試覆蓋率 100%
  3. 準(zhǔn)入調(diào)整為在 CI 單測流水線在 Xcode test plan 中開啟 thread sanitizer,CI 消費 thread sanitizer 檢出結(jié)果

圖片

取地址得到臨時變量地址

前文「對變量取地址有可能取到臨時地址」的本質(zhì)是:Swift 中對 computed property 的默認實現(xiàn)取地址過程依賴 get  set 來分配臨時變量,然后再通過 set 設(shè)置回去。其在多線程中產(chǎn)生的后果是:鎖在各個線程的執(zhí)行過程中被 get 多份至各線程的執(zhí)行棧上;在普遍的代碼中產(chǎn)生的后果是:取地址的結(jié)果不穩(wěn)定。

所以這里最佳實踐也應(yīng)該分情況討論:

  • 對于使用鎖的需求而言,蘋果的思路是提供封裝好的鎖,而不是鼓勵使用原始鎖(如 os_unfair_lock 或者 pthread_mutex)。但是蘋果在 iOS 16 才想起這件事,所以這里我們需要自行封裝好沒問題的鎖,并且鼓勵開發(fā)者只使用封裝好的鎖。
// 下列代碼是蘋果的封裝
enum MyState {
    case idle
    case loading
    case complete(MyAsset)
    case error(Error)
}
let protectedState = OSAllocatedUnfairLock(initialState: MyState.idle)
func myLoadMethod() {
    protectedState.withLock { state in
        state = .loading
    }
    var (resource, error) = loadMyResources()
    if resource != nil {
        protectedState.withLock { state in
            state = .complete(resource)
        }
    } else {
        protectedState.withLock { state in
            state = .error(error!)
        }
    }
}
  • 對于其他需求,這里最佳實踐應(yīng)該是:
  • 禁用對 get/set 實現(xiàn)的 computed property 取地址。
  • 如果是對 stored property 取地址,也應(yīng)該使用左值存儲指針,而不是直接使用右值送參。且 stored property 及存儲指針的左值的生命周期可以涵蓋使用指針代碼的生命周期——比如 stored property 及存儲指針的左值是一個全局變量。
// ?
private var myKey: Int8 = 0
objc_getAssociatedObject(self, &myKey) // &myKey 是一個右值


extension NSObject {
    var myProperty: Any? {
        get {
            objc_getAssociatedObject(self, &myKey)
        }
        set {
            objc_setAssociatedObject(
                self,
                &myKey, 
                newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
        }
    }
}
// ?
// 全局變量
private var myKey: Int8 = 0
// 全局變量,myKeyPtr 是一個左值。
private let myKeyPtr = UnsafeRawPointer(withUnsafeMutablePointer(to: &myKey) {$0})
// 通常來說 withUnsafeMutablePointer 獲得的指針值只保證在尾閉包中有效
// 這里利用了 myKey 是全局變量,生命周期貫穿全 app 啟動關(guān)閉
// 所以即使 return 了 withUnsafeMutablePointer 中尾閉包的指針值也沒有問題


extension NSObject {
    var myProperty: Any? {
        get {
            objc_getAssociatedObject(self, myKeyPtr)
        }
        set {
            objc_setAssociatedObject(
                self,
                myKeyPtr, 
                newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
        }
    }
}

準(zhǔn)入方面,我們可以:

  1. 通過靜態(tài)檢測對「在 Swift 中使用原始鎖」這種行為進行攔截,并不再鼓勵直接使用原始鎖(如 os_unfair_lock 或者 pthread_mutex);對自己開發(fā)水平自信的開發(fā)者依然可以使用原始鎖,但是相關(guān)代碼需要單獨豁免。
  2. 通過靜態(tài)檢測對「Swift 中 & 取地址之后作為右值使用」這種行為進行攔截,攔截后建議修復(fù)為使用左值存儲指針值后再使用
  3. 通過靜態(tài)檢測對「向 computed property 取地址」這種行為進行攔截。

總結(jié)

上述問題的本質(zhì)、后果總結(jié)如下

圖片

上述最佳實踐及處置手段總結(jié)如下:

圖片


作者介紹:

  • 李禹龍,2020 年加入字節(jié)跳動,來自抖音 iOS 基礎(chǔ)技術(shù)團隊,專注 Swift 語言及 UI DSL 框架。
責(zé)任編輯:龐桂玉 來源: 字節(jié)跳動技術(shù)團隊
相關(guān)推薦

2025-02-12 10:51:51

2024-01-17 06:23:35

SwiftTypeScript定義函數(shù)

2022-06-07 08:31:44

JavaUnsafe

2012-02-21 14:04:15

Java

2017-12-05 08:53:20

Golang參數(shù)傳遞

2021-04-13 09:20:21

JavaUnsafejava8

2021-04-16 20:50:16

URL爬蟲參數(shù)

2014-07-04 09:47:24

SwiftSwift開發(fā)

2010-01-05 14:49:03

JSON格式

2009-06-09 21:54:26

傳遞參數(shù)JavaScript

2014-07-22 09:01:53

SwiftJSON

2022-06-27 09:00:55

SwiftGit Hooks

2024-04-28 11:36:07

LambdaPython函數(shù)

2023-11-17 14:10:08

C++函數(shù)

2015-09-08 10:16:41

Java參數(shù)按值傳遞

2023-03-09 16:39:23

Python傳遞參數(shù)

2010-07-26 13:13:33

Perl函數(shù)參數(shù)

2009-12-08 14:31:31

PHP命令行讀取參數(shù)

2009-12-15 14:09:39

Ruby創(chuàng)建可參數(shù)化類

2010-01-05 15:30:25

JSONP
點贊
收藏

51CTO技術(shù)棧公眾號