Ios Swift:与协议的一致性,使用“通用方法”;其中;条款
摘要: 我想创建一个Ios Swift:与协议的一致性,使用“通用方法”;其中;条款,ios,swift,generics,protocols,swift-protocols,Ios,Swift,Generics,Protocols,Swift Protocols,摘要: 我想创建一个类,它将有一个相应的ClassDelegate协议,其中包含func 目标: 使用多个对象类重用单个对象和行为。接收已具有专用类的委托回调,而无需将对象强制转换为特定类来使用它 示例代码: 具有通用方法的协议: protocol GenericTableControllerDelegate: AnyObject { func controller<T>(controller: GenericTableController<T>, didSele
类
,它将有一个相应的ClassDelegate
协议,其中包含func
目标:
使用多个对象类重用单个对象和行为。接收已具有专用类的委托回调,而无需将对象强制转换为特定类来使用它
示例代码:
具有通用方法的协议:
protocol GenericTableControllerDelegate: AnyObject {
func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}
GenericTableController的专用版本
:
final class SpecializedTableController: GenericTableController<NSObject> {}
SpecializedTableController的客户机
,具有“where”要求—唯一未编译的问题
final class AnotherClientOfTableController: UIViewController, GenericTableControllerDelegate {
// Works OK
func controller<T>(controller: GenericTableController<T>, didSelect value: T) where T: NSObject {
// `value` is String
}
}
final类另一个ClientOftableController:UIViewController、GenericTableControllerDelegate{
//行吗
func控制器(控制器:GenericTableController,didSelect值:T),其中T:NSObject{
//'value'是字符串
}
}
类型“AnotherClientOfTableController”不符合协议
“GenericTableControllerDelegate”是否要添加协议存根
是否有一种方法可以使协议具有通用方法,并且能够在该方法实现中具有具体(专用)类型?
是否有接近的替代方案可以满足类似的要求(具有泛型类,但能够在委托回调中处理具体类型)?
我认为这在你想要的意义上是行不通的。最接近的是与子类的组合。考虑以下事项:
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)
}
}
看起来这是非常安全的,它永远不会崩溃,但如果你看近一点,我们可以这样做:
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
。没有任何东西正在停止编译器,但如果它停止了,则会出现什么错误
不管怎样,只要你正确使用它,它就会工作。如果你不这样做,它就会崩溃。您可以选择展开,但我不确定是否合适。解决这种情况的可能方法之一是使用回调而不是委派。通过不是传递闭包而是传递实例方法,它看起来与委托模式几乎相同:
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) {
}
}
开放类GenericTableController2:UITableViewController{
var onSelect:((数据类型)->无效)?
变量数据=[DataType]()
打开重写func tableView(tableView:UITableView,didSelectRowAt indexPath:indexPath){
let item=data[indexPath.row]
onSelect?(项目)
}
}
最后一个类回调示例:GenericTableController2{
}
最终类CallBackClient:UIViewController{
重写func viewDidLoad(){
super.viewDidLoad()
设vc=CallbackExample()
vc.onSelect=handleSelection
}
func handleSelection(uObject:NSObject){
}
}
作为一个优点,该代码非常简单,不涉及Swift类型系统的任何高级变通方法,Swift类型系统在处理泛型和协议时经常出现一些问题。您的错误在于协议:
protocol GenericTableControllerDelegate: AnyObject {
func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}
然后您希望委托的数据类型与表视图的数据类型匹配。这让我们进入了PAT(带有相关类型的协议)、类型橡皮擦和(Swift中还没有)的世界,而事实上,它只是变得一团糟
虽然这是一个广义存在论特别适合的用例(如果它们曾被添加到Swift中),但在很多情况下,您可能无论如何都不希望这样。委托模式是在添加闭包之前开发的ObjC模式。过去在ObjC中传递函数非常困难,所以即使是非常简单的回调也会变成委托。在大多数情况下,我认为理查德·托普奇的方法是完全正确的。只需传递一个函数 但是如果你真的想保持代理风格呢?我们几乎可以做到。一个小问题是您不能拥有名为
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)
}
}
有了它,您可以构建简单的策略,如“打印出来”:
或者更有趣的是,更新标签:
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的真正优势。我绝对同意在这里使用回调而不是委托,但像这样传递方法通常会创建一个retain循环,需要非常小心
vc.onSelect=handleSelection
隐式捕获self
作为强引用。在这些情况下,您通常需要使用[弱自我]
。在这种特殊情况下,这不是因为vc
是一个局部变量,但我怀疑在任何实际代码中,vc
都会是一个属性。@RobNapier同意,这是一个很好的观点。你能不能改进一下这个想法,这样我就可以同时使用命名方法(不是匿名)和使CallbackExample
controller捕获它?因此,如果我将对它的引用存储在“CallBackClient”中的某个地方,它仍然会被释放。我刚刚用一个简单的演示项目进行了检查,事实上,当存储对新创建的视图控制器的强引用时,同时存储回调会创建一个保留周期。我不相信有任何类似的语法,但我已经补充了一个答案,进一步说明了这一点。正如你所说,函数是IMO的必经之路。嗨,Rob,感谢你在文章和代码示例中对泛型和存在论进行了全面的概述,以及命令/策略封装的示例。看起来很有希望,但对于我想要的用例来说,这是一种过度的杀伤力。我心目中的目标之一是两者兼而有之,在调用站点上使用干净的语法,并且在内存管理方面没有任何问题
public protocol GenericTableControllerDelegate: AnyObject {
associatedtype DataType
func controller(controller: GenericTableController<DataType>, didSelect value: DataType)
}
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)
}
}
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))