【自問自答】關(guān)于 Swift 的幾個(gè)疑問

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

Swift 版本是:4.0.3。不同版本的 Swift,可能無法復(fù)現(xiàn)問題。

個(gè)人記錄,僅供參考,不保證嚴(yán)格意義上的正確性。

swift 中,如何在函數(shù)內(nèi),聲明 static 變量 ?

問題描述:

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

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

解決方案:

可以用內(nèi)嵌類型的 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

猜想:

同一作用域的同名內(nèi)嵌類型,多次執(zhí)行,只會(huì)真正定義一次.

swift 有沒有可以進(jìn)行全局埋點(diǎn)的黑魔法機(jī)制?

問題描述:

全局埋點(diǎn),依賴于 runtime 機(jī)制, 所以換種問法就是: swift 中如何繼續(xù)使用 objc 的runtime 機(jī)制.

解決方案:

純Swift類沒有動(dòng)態(tài)性,但在方法、屬性前添加dynamic修飾可以獲得動(dòng)態(tài)性。

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

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

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

快速驗(yàn)證,可使用:

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 的同時(shí),也必須加 @objc -- 即不允許單獨(dú)加 dynamic 標(biāo)記.

猜想:

dynamic 是在用性能換靈活性.生產(chǎn)環(huán)境下,未來更可能的方案,可能是:

通過協(xié)議,約定必須實(shí)現(xiàn)的統(tǒng)計(jì)相關(guān)的方法 --> 通過單元測試,來保證遵循特定統(tǒng)計(jì)協(xié)議的類型,在特定的時(shí)機(jī)一定會(huì)調(diào)用協(xié)議規(guī)定的統(tǒng)計(jì)方法.

extension 中覆蓋某個(gè)自定義的 framework 中的 open/public class 中的 private 方法,會(huì)發(fā)生什么事?

問題描述:

模塊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() // --> ?

解決方案:

可以直接運(yùn)行觀察:

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

所以: 通過 extension 覆蓋其他模塊open類的private方法,不會(huì)有任何詭異的問題.兩個(gè)實(shí)現(xiàn),都對(duì)彼此透明.

更進(jìn)一步: 模塊B以 Optional 方式引入模塊A. 如果是在模塊B中,通過 extension 覆蓋模塊A的private 方法.然后在模塊 C 中同時(shí)引入了模塊 A 和 B,此時(shí)模塊C中類似的函數(shù)調(diào)用,會(huì)是哪個(gè)模塊的方法實(shí)現(xiàn)生效?

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

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

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

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

此時(shí)模塊C 中的調(diào)用,會(huì)報(bào)錯(cuò):

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,此時(shí)的調(diào)用結(jié)果,會(huì)不會(huì)有什么不同? --> 然而,并沒有什么不同,依然是同樣的 ambiguous 錯(cuò)誤.

總結(jié)一下:

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

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

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

猜想:

擴(kuò)展第三方模塊類時(shí),使用自定義的前綴,總是一個(gè)好的習(xí)慣.

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

問題描述:

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

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

var book = Book.InnerBook.DeeperBook()

解決方案:

直接調(diào)用,會(huì)報(bào)錯(cuò):

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

嘗試修改為:

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

依然報(bào)錯(cuò):

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

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

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

猜想:

內(nèi)嵌類型的方法的訪問級(jí)別,并不會(huì)隨著類型本身訪問級(jí)別的寬松更變得比默認(rèn)的 internal 更寬松.

疑問: 為什么函數(shù)定義外的 closure 不會(huì)引起作用域內(nèi)其他變量引用計(jì)數(shù)的變化?

問題描述:

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

片段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: "風(fēng)之影")
print(aBook!.whoami!())

aBook = nil

/*
輸出:

風(fēng)之影
*/

片段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: "風(fēng)之影")
print(aBook!.whoami!())

aBook?.whoami = nil
aBook = nil

/*
輸出:

風(fēng)之影
風(fēng)之影 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: "風(fēng)之影")

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

/*
輸出:

風(fēng)之影 new
風(fēng)之影 is being deinitialized
*/

片段A, aBook 內(nèi)存泄露,經(jīng)典的 closure self 循環(huán)引用問題.

片段B,是 closure self 循環(huán)引用的一個(gè)可選解決方案,即 self 主動(dòng)切斷對(duì) closure 的引用.

片段C,比較詭異. aBook 引用了一個(gè)新的 closure,新的 closure 內(nèi)又引用了 aBook 一次,但是 aBook 竟然還是可以正確釋放,并沒有預(yù)期中的內(nèi)存泄露問題.令人費(fèi)解!?

解決方案:

片段 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: "風(fēng)之影")

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

/*
輸出:

風(fēng)之影 new
*/

可以看到,這樣 aBook 就會(huì)泄露了.片段 D 與 片段 C 的區(qū)別在于 closure 中的那句 [aBook] in .這個(gè)語法,是我"杜撰"的,語義上近似于以強(qiáng)引用方式捕捉 aBook 對(duì)應(yīng)的真實(shí)對(duì)象.官方文檔中并沒有提到有這種語法.

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

注意: 當(dāng)前復(fù)現(xiàn)此問題用的是 swift 4.0.3 版本,不同版本中的 closure 的行為可能不一致.

猜想:

或許 swift 中,只有內(nèi)部有可能直接使用 self 的 closure,才需要特別考慮closure引起的內(nèi)存泄露問題.

個(gè)人猜測,可能是因?yàn)?self 比較特殊, closure 只能直接捕捉其真實(shí)對(duì)象.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,229評(píng)論 4 61
  • 01 我認(rèn)識(shí)一個(gè)妹妹,她每次在線下結(jié)識(shí)朋友時(shí),都會(huì)隨身帶一個(gè)照片打印機(jī),打印機(jī)不貴,和手機(jī)尺寸差不多大小。和別人合...
    弗蘭克閱讀 14,146評(píng)論 54 535
  • 二零一五年十一月中旬 已是深秋了, 天也黑的很快, 一些背影也漸漸消失在夜幕中。 對(duì)于近視的我而言, 這些背影都是...
    春日夢境閱讀 360評(píng)論 0 0
  • 簡書接龍活動(dòng)第二期 第一章 拓桑之鱗 第二章 人魚之境 第三章 秘境之地 前景提示: 拓桑和大板,況天賦三人來到魚...
    況天賦聲閱讀 391評(píng)論 9 8
  • 早起:自然醒,晨讀+英語朗讀,主動(dòng)高效完成早起清單 吃青蛙,番茄鐘:3個(gè)番茄鐘,完成英語作業(yè)預(yù)估一個(gè)番茄鐘,用時(shí)1...
    翰悅寧心閱讀 109評(píng)論 0 0