【自問自答】關於 Swift 的幾個疑問

来源:https://www.cnblogs.com/ios122/archive/2018/01/06/8213602.html
-Advertisement-
Play Games

感覺自己給自己釋疑,也是一個極為有趣的過程。這次,我還新增了“猜想”一欄,來嘗試回答一些暫時沒有足夠資料支撐的問題。 Swift 版本是:4.0.3。不同版本的 Swift,可能無法復現問題。 個人記錄,僅供參考,不保證嚴格意義上的正確性。 ...


感覺自己給自己釋疑,也是一個極為有趣的過程。這次,我還新增了“猜想”一欄,來嘗試回答一些暫時沒有足夠資料支撐的問題。

Swift 版本是:4.0.3。不同版本的 Swift,可能無法復現問題。

個人記錄,僅供參考,不保證嚴格意義上的正確性。

swift 中,如何在函數內,聲明 static 變數 ?

問題描述:

以下語句,是編譯不過的,提示:“static properties may only be declared on a type”

func add() -> Int {
    static var base = 0
    base += 1
    return base
}
add()
add()
add()

解決方案:

可以用內嵌類型的 static 屬性來解決,如:

func add() -> Int {
    struct Temp{
        static var base = 0
    }
    
    Temp.base += 1
    return Temp.base
}

add() // --> 1
add() // --> 2
add() // --> 3

參考:https://stackoverflow.com/a/25354915

猜想:

同一作用域的同名內嵌類型,多次執行,只會真正定義一次.

swift 有沒有可以進行全局埋點的黑魔法機制?

問題描述:

全局埋點,依賴於 runtime 機制, 所以換種問法就是: swift 中如何繼續使用 objc 的runtime 機制.

解決方案:

純Swift類沒有動態性,但在方法、屬性前添加dynamic修飾可以獲得動態性。

繼承自NSObject的Swift類,其繼承自父類的方法具有動態性,其他自定義方法、屬性需要加dynamic修飾才可以獲得動態性。

若方法的參數、屬性類型為Swift特有、無法映射到Objective-C的類型(如Character、Tuple),則此方法、屬性無法添加dynamic修飾(會編譯錯誤)

參考: http://www.infoq.com/cn/articles/dynamic-analysis-of-runtime-swift

快速驗證,可使用:

class A{
    @objc dynamic  func funcA(){
        print("funcA")
    }
}

func methodSwizze(cls: AnyClass, originalSelector: Selector, swizzledSelector:Selector){
    let originalMethod = class_getInstanceMethod(cls, originalSelector)
    let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
    
    if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension A{
    @objc dynamic  func funcB(){
        print("funcB")
    }
}

methodSwizze(cls: A.self, originalSelector: #selector(A.funcA), swizzledSelector: #selector(A.funcB))

let a = A()

a.funcB() // --> funcA
a.funcA() // --> funcB

註意: swift 4 中, 加 dynamic 的同時,也必須加 @objc -- 即不允許單獨加 dynamic 標記.

猜想:

dynamic 是在用性能換靈活性.生產環境下,未來更可能的方案,可能是:

通過協議,約定必須實現的統計相關的方法 --> 通過單元測試,來保證遵循特定統計協議的類型,在特定的時機一定會調用協議規定的統計方法.

extension 中覆蓋某個自定義的 framework 中的 open/public class 中的 private 方法,會發生什麼事?

問題描述:

模塊A:

 open class Book: NSObject {
    private func funcA(){
        print("private funcA")
    }
    
    public func callFuncA(){
        funcA()
    }
}

模塊B:

public extension Book {
    func funcA(){
        print("public funcA")
    }
}

問題:

模塊B 中,以下代碼的輸出是?

let book = Book()
book.funcA()  // --> ?
book.callFuncA() // --> ?

解決方案:

可以直接運行觀察:

let book = Book()
book.funcA()  // --> public funcA
book.callFuncA() // --> private funcA

所以: 通過 extension 覆蓋其他模塊open類的private方法,不會有任何詭異的問題.兩個實現,都對彼此透明.

更進一步: 模塊B以 Optional 方式引入模塊A. 如果是在模塊B中,通過 extension 覆蓋模塊A的private 方法.然後在模塊 C 中同時引入了模塊 A 和 B,此時模塊C中類似的函數調用,會是哪個模塊的方法實現生效?

let book = Book()
book.funcA()  // --> public funcA
book.callFuncA() // --> private funcA

可以看到,仍然是模塊B中的 public 級別的方法生效.

再進一步,如果模塊 A 中的方法,由 private 改為 public,即:

open class Book: NSObject {
    public func funcA(){
        print("original public funcA")
    }
    
    public func callFuncA(){
        funcA()
    }
}

此時模塊C 中的調用,會報錯:

error: ambiguous use of 'funcA()'
book.funcA()
^
A.Book:2:17: note: found this candidate
public func funcA()
^
B.Book:2:17: note: found this candidate
public func funcA()

如果模塊 B 以 Required 方式引入模塊A,模塊C,只引入模塊B,此時的調用結果,會不會有什麼不同? --> 然而,並沒有什麼不同,依然是同樣的 ambiguous 錯誤.

總結一下:

  • 可以安全地在 extension 中覆蓋其他模塊中open/public類中定義的非 public 方法.對於原有模塊,會繼續使用自身的非 public 的方法定義;定義其他模塊,可以正確使用 extension 版本中的模塊代碼.

  • 不要嘗試在 extension 中定義其他模塊中 open/public類中定義的 public 方法.雖然可以定義,但是使用時,會引起 ambiguous 錯誤.

  • 在使用 extension 擴展其他模塊中定義的類時,最好還是給自己擴展的方法加上特定首碼,不然第三方模塊萬一暴露的同名方法,自己的代碼就徹底跪了.

猜想:

擴展第三方模塊類時,使用自定義的首碼,總是一個好的習慣.

嵌套定義的類型,如果外層類型是 private, 內層類型是 open,內層類型.那麼內層類型有可能在其他模塊中被使用嗎 ?

問題描述:

 open class Book: NSObject {
    private class InnerBook{
        open class DeeperBook{
            
        }
    }
}

在另一個 swift 模塊中,能使用類似下麵的類型初始化代碼嗎?

var book = Book.InnerBook.DeeperBook()

解決方案:

直接調用,會報錯:

error: 'InnerBook' is inaccessible due to 'private' protection level

嘗試修改為:

 open class Book: NSObject {
    open class InnerBook{
        open class DeeperBook{
            
        }
    }
}

依然報錯:

error: 'Book.InnerBook.DeeperBook' initializer is inaccessible due to 'internal' protection level

根據提示,再修改下 DeeperBook 的初始化方法的訪問級別:

open class Book: NSObject {
    open class InnerBook{
        open class DeeperBook{
            public init() {
                
            }
        }
    }
}

猜想:

內嵌類型的方法的訪問級別,並不會隨著類型本身訪問級別的寬鬆更變得比預設的 internal 更寬鬆.

疑問: 為什麼函數定義外的 closure 不會引起作用域內其他變數引用計數的變化?

問題描述:

仔細觀察以下不同代碼片段的不同輸出:

片段A:

class Book{
    let name: String
    
    lazy var whoami:(()->String)? = {
        return self.name
    }
    
    init(name:String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var aBook:Book? = Book(name: "風之影")
print(aBook!.whoami!())

aBook = nil

/*
輸出:

風之影
*/

片段B:

class Book{
    let name: String
    
    lazy var whoami:(()->String)? = {
        return self.name
    }
    
    init(name:String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var aBook:Book? = Book(name: "風之影")
print(aBook!.whoami!())

aBook?.whoami = nil
aBook = nil

/*
輸出:

風之影
風之影 is being deinitialized
*/

片段C:

class Book{
    let name: String
    
    lazy var whoami:(()->String)? = {
        return self.name
    }
    
    init(name:String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var aBook:Book? = Book(name: "風之影")

aBook?.whoami = {
    return aBook!.name + " new"
}
        
print(aBook!.whoami!())
        
aBook = nil

/*
輸出:

風之影 new
風之影 is being deinitialized
*/

片段A, aBook 記憶體泄露,經典的 closure self 迴圈引用問題.

片段B,是 closure self 迴圈引用的一個可選解決方案,即 self 主動切斷對 closure 的引用.

片段C,比較詭異. aBook 引用了一個新的 closure,新的 closure 內又引用了 aBook 一次,但是 aBook 竟然還是可以正確釋放,並沒有預期中的記憶體泄露問題.令人費解!?

解決方案:

片段 D:

class Book{
    let name: String
    
    lazy var whoami:(()->String)? = {
        return self.name
    }
    
    init(name:String) {
        self.name = name
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var aBook:Book? = Book(name: "風之影")

aBook?.whoami = {
    [aBook] in
    return aBook!.name + " new"
}
        
print(aBook!.whoami!())
        
aBook = nil

/*
輸出:

風之影 new
*/

可以看到,這樣 aBook 就會泄露了.片段 D 與 片段 C 的區別在於 closure 中的那句 [aBook] in .這個語法,是我"杜撰"的,語義上近似於以強引用方式捕捉 aBook 對應的真實對象.官方文檔中並沒有提到有這種語法.

另外,參考 objc 中block 的行為,我嘗試搜索相關 swift 中 棧(stack) block 的相關信息.如果 closure 也區分棧和堆,倒是還可以勉強解釋.不過,並沒有相關的信息,而且 closure 本身也是不支持 copy 操作的.

註意: 當前復現此問題用的是 swift 4.0.3 版本,不同版本中的 closure 的行為可能不一致.

猜想:

或許 swift 中,只有內部有可能直接使用 self 的 closure,才需要特別考慮closure引起的記憶體泄露問題.

個人猜測,可能是因為 self 比較特殊, closure 只能直接捕捉其真實對象.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 學習目標 -正則元字元串 -SQL語句中使用正則搜索 -SQL語句中使用正則匹配 -SQL語句中使用正則替代字元串 正則表達式 Oracle資料庫10g引入對正則表達式的支持。 實現了符合UNIX可移植操作系統(POSIX)標準,由電氣和電子工程師協會(IEEE)控制,ASCII數據匹配的語義和語法 ...
  • 轉載songdeyouxiang 1、資料庫命名規範採用26個英文字母(區分大小寫)和0-9的自然數(經常不需要)加上下劃線'_'組成;命名簡潔明確(長度不能超過30個字元);例如:user, stat, log, 也可以wifi_user, wifi_stat, wifi_log給資料庫加個首碼; ...
  • 作者:NiceCui 本文謝絕轉載,如需轉載需徵得作者本人同意,謝謝。 本文鏈接:http://www.cnblogs.com/NiceCui/p/8213723.html 郵箱:[email protected] 日期:2017 12 20 mysql安裝、配置 1. yum 下載mysql 僅限 ...
  • 當一張的數據達到幾百萬時,你查詢一次所花的時間會變多,如果有聯合查詢的話,我想有可能會卡在那兒了,那麼分表的目的就在於此,減小資料庫的負擔,縮短查詢時間。 ...
  • 最近項目上裝的mysql服務,分配的磁碟空間太小了,導致binlog兩天時間就能打滿,這裡記錄下處理方式 mysql的binlog日誌是一個很重要的日誌,以事件形式記錄了所有的DDL和DML(除了數據查詢語句)語句,還包含執行的消耗的時間,在數據丟失的緊急情況下,我們可以利用binlog日誌功能進行 ...
  • 學習目標 -理解分層查詢概念 -創建樹形組織報告 -格式化分層數據 -樹形組織排除分支 分層查詢 語法 SELECT [LEVEL],<column>,exper ... FROM <table_name> [WHERE condition(s)] START WITH condition(s) C ...
  • 一、Runtime簡介 1.1 簡單介紹 Runtime簡稱運行時。OC就是運行時機制,也就是在運行時候的一些機制,其中最主要的是消息機制; 對於C語言,函數的調用在編譯的時候會決定調用哪個函數; 對於OC的函數,屬於動態調用的過程,在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候才會根 ...
  • 獲取二維碼 通過後臺介面可以獲取小程式任意頁面的二維碼,掃描該二維碼可以直接進入小程式對應的頁面。目前微信支持兩種二維碼,小程式碼(左),小程式二維碼(右),如下所示: 獲取小程式碼 我們推薦生成並使用小程式碼,它具有更好的辨識度。目前有兩個介面可以生成小程式碼,開發者可以根據自己的需要選擇合適的接 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...