簡體   English   中英

在 Swift 中測試類一致性的類型

[英]Testing type for class conformance in Swift

編輯:評論中提到的先前答案沒有回答問題,即如何確定任何給定類型是否為引用類型以及如何安全地將所述類型符合 AnyObject。

對傳遞的類型進行測試不起作用,因為底層類型可能是可選的,也可能是協議,在這種情況下,需要知道傳遞的實例是類類型還是值類型。

我想出的解決方案類似於下面提供的修改后的答案。


所以我有一個新的依賴注入框架Factory

Factory 允許范圍內的實例,基本上允許您在創建服務后緩存它們。 其中一個范圍是共享的。 只要外部世界中的某個人保持對它的強引用,任何共享的實例都將被緩存並返回。 在最后一個引用釋放對象后,緩存釋放對象,並在下一次解析時創建一個新實例。

顯然,這是實現的,只是簡單地維護對已創建對象的弱引用。 如果弱引用為零,則該創建一個新對象了。

這就是問題所在

弱引用只能應用於引用類型。

Factory 在內部使用泛型來管理類型信息。 但我可以創建任何類型的工廠:類、結構、字符串等等。)

范圍在內部使用盒裝類型的字典。 如果一個實例存在於緩存中並且在盒子中它被返回。 所以我想做的是創建這個......

private struct WeakBox<T:AnyObject>: AnyBox {
    weak var boxed: T
}

需要 AnyObject 一致性以允許弱。 否則會出現編譯器錯誤。 現在我想用這樣的東西在我的共享范圍內裝箱和緩存一個對象......

func cache<T>(id: Int, instance: T) {
    cache[id] = WeakBox(boxed: instance)
}

但這也會產生編譯器錯誤。 (通用結構 WeakBox 要求 T 是類類型。)

那么如何從一個連接到另一個? 執行以下操作不起作用。 Swift 顯示警告“從 'T' 到 'AnyObject' 的條件轉換總是成功”,然后無論如何轉換類型。

func cache<T>(id: Int, instance: T) {
    if let instance = instance as? AnyObject {
        cache[id] = WeakBox(boxed: instance)
    }
}

我會對以下情況感到滿意,但同樣的問題。 您不能測試類一致性,也不能有條件地轉換為 AnyObject。 同樣,它總是成功的。

private struct WeakBox: AnyBox {
    weak var boxed: AnyObject?
}
func cache<T>(id: Int, instance: T) {
    if let instance = instance as? AnyObject {
        cache[id] = WeakBox(boxed: instance)
    }
}

我現在正在做的事情就像......

private struct WeakBox: AnyBox {
    weak var boxed: AnyObject?
}
func cache<T>(id: Int, instance: T) {
    cache[id] = WeakBox(boxed: instance as AnyObject)
}

哪個有效,但是instance as AnyObject取決於一些非常奇怪的 Swift 到 Objective-C 的橋接行為。

無法在運行時測試類一致性讓我發瘋,這似乎是語言中的一個半主要漏洞。

您不能測試一致性,也不能強制轉換一致性。

所以,你可以做什么?

正如 Martin 在評論中指出的那樣,任何值都可以在 Swift 中轉換為AnyObject ,因為 Swift 會將值類型包裝在不透明的_SwiftValue類中,並且轉換總是會成功。 不過,有辦法解決這個問題。

在沒有這種隱式轉換的情況下檢查一個值是否是引用類型的方法是檢查它的類型是否is AnyObject.Type ,如下所示:

func printIsObject(_ value: Any) {
    if type(of: value) is AnyObject.Type {
        print("Object")
    } else {
        print("Other")
    }
}

class Foo {}
struct Bar {}
enum Quux { case q }

printIsObject(Foo()) // => Object
printIsObject(Bar()) // => Other
printIsObject(Quux.q) // => Other

請注意,檢查類型是否is AnyObject.Type而不是is AnyObject T.self ,表示值類型的對象,它本身就是一個對象,所以is AnyObject總是會成功。 相反, is AnyObject.Type詢問“這是否繼承自所有對象的元類型”,即“表示類型的對象是否繼承自表示所有對象類型的對象?”


編輯:顯然,我忘記了 Swift 包含AnyClass作為AnyObject.Type的同義詞,因此可以將檢查簡化為is AnyClass 但是,將上述內容作為對其工作原理的略微擴展的解釋。


如果您希望此方法也能夠處理Optional值,您將不得不做一些特殊情況來添加支持。 具體來說,因為Optional<T>是一個enum ,無論T的類型如何,您都需要深入了解T是什么。

有幾種方法可以做到這一點,但因為Optional是一個泛型類型,所以不可能問“這個值是Optional<T>嗎?” 在不知道T是什么預先設置的情況下,一種更簡單、更可靠的方法是引入一個Optional采用的協議,該協議會擦除基礎值的類型,同時仍然允許您訪問它:

protocol OptionalProto {
    var wrappedValue: Any? { get }
}

extension Optional: OptionalProto {
    var wrappedValue: Any? {
        switch self {
        case .none: return nil
        case let .some(value):
             // Recursively reach in to grab nested optionals as needed.
             if let innerOptional = value as? OptionalProto {
                 return innerOptional.wrappedValue
             } else {
                 return value
             }
        }
    }
}

然后我們可以在cache中使用這個協議來發揮我們的優勢:

func cache(id: Int, instance: Any) {
    if let opt = instance as? OptionalProto {
        if let wrappedValue = opt.wrappedValue {
            cache(id: id, instance: wrappedValue)
        }
        
        return
    }
    
    // In production:
    // cache[id] = WeakBox(boxed: instance as AnyObject)

    if type(of: instance) is AnyClass {
        print("\(type(of: instance)) is AnyClass")
    } else {
        print("\(type(of: instance)) is something else")
    }
}

這種方法可以處理所有前面的情況,但也可以處理無限深度嵌套的OptionalOptional內部的協議類型:

class Foo {}
struct Bar {}
enum Quux { case q }

cache(id: 1, instance: Foo()) // => Foo is AnyClass
cache(id: 2, instance: Bar()) // => Bar is something else
cache(id: 3, instance: Quux.q) // => Quux is something else

let f: Optional<Foo> = Foo()
cache(id: 4, instance: f) // => Foo is AnyClass

protocol SomeProto {}
extension Foo: SomeProto {}

let p: Optional<SomeProto> = Foo()
cache(id: 5, instance: p) // => Foo is AnyClass

所以這需要一段時間才能弄清楚,甚至更長時間才能找到解決方案所需的線索,所以我提供了我自己的代碼並回答了這個問題

鑒於以下協議...

private protocol OptionalProtocol {
    var hasWrappedValue: Bool { get }
    var wrappedValue: Any? { get }
}

extension Optional : OptionalProtocol {
    var hasWrappedValue: Bool {
        switch self {
        case .none:
            return false
        case .some:
            return true
        }
    }
    var wrappedValue: Any? {
        switch self {
        case .none:
            return nil
        case .some(let value):
            return value
        }
    }
}

還有一個盒子類型來保存弱引用......

private protocol AnyBox {
    var instance: Any { get }
}

private struct WeakBox: AnyBox {
    weak var boxed: AnyObject?
    var instance: Any {
        boxed as Any
    }
}

然后測試和裝箱給定類型的代碼看起來像......


func box<T>(_ instance: T) -> AnyBox? {
    if let optional = instance as? OptionalProtocol {
        if let unwrapped = optional.wrappedValue, type(of: unwrapped) is AnyObject.Type {
            return WeakBox(boxed: unwrapped as AnyObject)
        }
    } else if type(of: instance) is AnyObject.Type {
        return WeakBox(boxed: instance as AnyObject)
    }
    return nil
}

請注意,傳入的類型可以是類、結構或其他值,也可以是協議。 它可以是任何這些東西的可選版本。

因此,如果它是可選的,我們需要打開它並測試實際包裝的類型以查看它是否是一個類。 如果是,那么執行我們的 AnyObject 轉換是安全的。

如果傳遞的值不是可選的,那么我們仍然需要檢查它是否是一個類。

還有一個用於非共享類型緩存的StrongBox類型。

struct StrongBox<T>: AnyBox {
    let boxed: T
    var instance: Any {
        boxed as Any
    }
}

最終的緩存例程看起來像這樣。

func resolve<T>(id: UUID, factory: () -> T) -> T {
    defer { lock.unlock() }
    lock.lock()
    if let box = cache[id], let instance = box.instance as? T {
        if let optional = instance as? OptionalProtocol {
            if optional.hasWrappedValue {
                return instance
            }
        } else {
            return instance
        }
    }
    let instance: T = factory()
    if let box = box(instance) {
        cache[id] = box
    }
    return instance
}

整個項目的源代碼位於Factory 存儲庫中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM