標籤:www obj 參數 -o tps gpo let 聲明 經典的
感覺自己給自己釋疑,也是一個極為有趣的過程。這次,我還新增了“猜想”一欄,來嘗試回答一些暫時沒有足夠資料支撐的問題。
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() // --> 1add() // --> 2add() // --> 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() // --> funcAa.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 funcAbook.callFuncA() // --> private funcA
所以: 通過 extension 覆蓋其他模組open類的private方法,不會有任何詭異的問題.兩個實現,都對彼此透明.
更進一步: 模組B以 Optional 方式引入模組A. 如果是在模組B中,通過 extension 覆蓋模組A的private 方法.然後在模組 C 中同時引入了模組 A 和 B,此時模組C中類似的函數調用,會是哪個模組的方法實現生效?
let book = Book()book.funcA() // --> public funcAbook.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 = nilaBook = 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 只能直接捕捉其真實對象.
【自問自答】關於 Swift 的幾個疑問