簡體   English   中英

Swift:使用帶有“where”子句的Generic方法符合協議

[英]Swift: conformance to the Protocol with Generic method with “where” clause

摘要:

我想創建一個Class<T> ,它將具有相應的ClassDelegate協議,其中包含func<T>

目標:

使用多個對象類重用單個對象和行為。 已經有一個專門的類接收委托回調,而不需要將對象強制轉換為特定的類來處理它。

示例代碼:

具有通用方法的協議:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}

通用基礎UITableViewController子類:

open class GenericTableController<DataType>: UITableViewController {
    weak var delegate: GenericTableControllerDelegate?
    var data = [DataType]()

    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        delegate?.controller(controller: self, didSelect: item)
    }
}

GenericTableController專用版本:

final class SpecializedTableController: GenericTableController<NSObject> {}

SpecializedTableController客戶端 - 實現結果,但需要類型轉換才能訪問專用數據類型:

final class ClientOfTableController: UIViewController, GenericTableControllerDelegate {
    // Works OK
    func controller<T>(controller: GenericTableController<T>, didSelect value: T) {
        if let value = value as? NSObject {
            // Requires unwrapping and casting
        }
    }
}

SpecializedTableController客戶端,具有“where”要求 - 它不能編譯的唯一問題

final class AnotherClientOfTableController: UIViewController, GenericTableControllerDelegate {
    // Works OK
    func controller<T>(controller: GenericTableController<T>, didSelect value: T) where T: NSObject {
        // `value` is String
    }    
}

類型'AnotherClientOfTableController'不符合協議'GenericTableControllerDelegate'您要添加協議存根嗎?

有沒有辦法讓一個協議與泛型方法,並能夠在該方法實現中具有一個具體(專業)類型?

是否有接近滿足類似要求的近似替代方案(具有泛型類但能夠在委托回調中處理具體類型)?

截圖

你的錯誤在協議中:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}

這表示為了成為GTCD,類型必須接受傳遞給該函數的任何類型T 但你不是那個意思。 你的意思是:

public protocol GenericTableControllerDelegate: AnyObject {
    associatedtype DataType
    func controller(controller: GenericTableController<DataType>, didSelect value: DataType)
}

然后,您希望委托的DataType與表視圖的DataType相匹配。 這讓我們進入了PAT的世界(具有相關類型的協議),類型擦除器和廣義存在 (在Swift中不存在),並且實際上它只是變得一團糟。

雖然這是一個用例,廣義存在特別適合(如果它們被添加到Swift中),在很多情況下你可能不想要這個。 委托模式是在添加閉包之前開發的ObjC模式。 以前在ObjC中傳遞函數非常困難,所以即使非常簡單的回調也會變成委托。 在大多數情況下,我認為Richard Topchiy的方法是完全正確的。 只需傳遞一個功能。

但是,如果你真的想保持代表風格怎么辦? 我們可以(差不多)這樣做。 一個小問題是你不能擁有一個名為delegate的屬性。 您可以設置它,但無法獲取它。

open class GenericTableController<DataType>: UITableViewController
{
    // This is the function to actually call
    private var didSelect: ((DataType) -> Void)?

    // We can set the delegate using any implementer of the protocol
    // But it has to be called `controller.setDelegate(self)`.
    public func setDelegate<Delegate: GenericTableControllerDelegate>(_ d: Delegate?)
        where Delegate.DataType == DataType {
            if let d = d {
                didSelect = { [weak d, weak self] in
                    if let self = self { d?.controller(controller: self, didSelect: $0) }
                }
            } else {
                didSelect = nil
            }
    }

    var data = [DataType]()

    // and here, just call our internal method
    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        didSelect?(item)
    }
}

這是一種有用的技術,但在大多數情況下我可能不會使用它。 如果這些方法引用DataType,那么添加更多方法肯定會令人頭疼。 你需要很多樣板。 請注意,由於將self傳遞給delegate方法,因此存在一些混亂。 這是委托方法需要的東西,但是閉包不需要(如果閉包需要它,你總是可以在閉包中捕獲控制器)。

當您探索這種可重用的代碼時,我鼓勵您更多地考慮封裝策略而不是關於對象和委托協議。 封裝策略的一個示例是將一個SelectionHandler類型交給控制器:

struct SelectionHandler<Element> {
    let didSelect: (Element) -> Void
}

有了它,你可以構建簡單的策略,如“打印出來:”

extension SelectionHandler {
    static func printSelection() -> SelectionHandler {
        return SelectionHandler { print($0) }
    }
}

或者更有趣的是,更新標簽:

static func update(label: UILabel) -> SelectionHandler {
    return SelectionHandler { [weak label] in label?.text = "\($0)" }
}

那么你會得到如下代碼:

controller.selectionHandler = .update(label: self.nameLabel)

或者,更有趣的是,您可以構建更高階的類型:

static func combine(_ handlers: [SelectionHandler]) -> SelectionHandler {
    return SelectionHandler {
        for handler in handlers {
            handler.didSelect($0)
        }
    }
}

static func trace(_ handler: SelectionHandler) -> SelectionHandler {
    return .combine([.printSelection(), handler])
}

controller.selectionHandler = .trace(.update(label: self.nameLabel))

這種方法比委托更有力,並開始釋放Swift的真正優勢。

解決這種情況的可能方法是使用回調而不是委托。 通過傳遞不是閉包而是實例方法,它看起來幾乎與委托模式相同:

open class GenericTableController2<DataType>: UITableViewController {
    var onSelect: ((DataType) -> Void)?
    var data = [DataType]()

    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        onSelect?(item)
    }
}

final class CallbackExample: GenericTableController2<NSObject> {
}

final class CallBackClient: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let vc = CallbackExample()
        vc.onSelect = handleSelection
    }

    func handleSelection(_ object: NSObject) {

    }
}

作為一個好處,代碼非常簡單,並且不涉及Swift類型系統的任何高級變通方法,這在處理泛型和協議時經常會遇到一些問題。

從某種意義上說,我不認為這是可行的。 最接近的是與子類結合。 考慮以下:

protocol MagicProtocol {
    func dooMagic<T>(_ trick: T)
}

class Magician<TrickType> {
    private let listener: MagicProtocol
    private let tricks: [TrickType]
    init(listener: MagicProtocol, tricks: [TrickType]) { self.listener = listener; self.tricks = tricks }
    func abracadabra() { listener.dooMagic(tricks.randomElement()) }
}

class Audience<DataType>: MagicProtocol {

    var magician: Magician<DataType>?

    init() {
        magician?.abracadabra()
    }

    func doExplicitMagic(_ trick: DataType) {

    }

    func dooMagic<T>(_ trick: T) {
        doExplicitMagic(trick as! DataType)
    }

}

現在我可以創建一個子類並將其限制為某種類型:

class IntegerAudience: Audience<Int> {

    override func doExplicitMagic(_ trick: Int) {
        print("This works")
    }

}

問題是2個仿制葯之間沒有相關性。 所以在某些時候必須進行演員表演。 我們在協議方法中這樣做:

doExplicitMagic(trick as! DataType)

看起來這是非常安全的,它永遠不會崩潰,但如果你看得更近,我們可以做到這一點:

func makeThingsGoWrong() {
    let myAudience = IntegerAudience()
    let evilMagician = Magician(listener: myAudience, tricks: ["Time to burn"])
    evilMagician.abracadabra() // This should crash the app
}

這里myAudience對應於協議MagicProtocol ,它可能不限於泛型。 但是myAudience僅限於Int 什么都沒有停止編譯器,但如果它停止了,錯誤是什么?

無論如何,只要你正確使用它,它就可以工作。 如果你不這樣做就會崩潰。 你可以做一個可選的解包,但我不確定它是否合適。

暫無
暫無

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

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