【问题标题】:Swift function as parameter within a classSwift 函数作为类中的参数
【发布时间】:2017-03-28 18:37:19
【问题描述】:

我是一个快速的初学者,所以要温柔......

我在将函数分配为参数时遇到问题。

我已经定义了这个结构:

struct dispatchItem {
   let description: String
   let f: ()->Void

   init(description: String, f: @escaping ()->()) {
      self.description = description
      self.f = f
   }
}

我在一个名为 MasterDispatchController 的类中使用它,如下所示:

class MasterDispatchController: UITableViewController {

   let dispatchItems = [
      dispatchItem(description: "Static Table", f: testStaticTable),
      dispatchItem(description: "Editable Table", f: testEditableTable)
   ]

    func testEditableTable() {
        //some code
    }

    func testStaticTable() {
        //some code
    }

等等

然后我的代码中有一个表格视图,它分派给单击的任何函数(我在上面的代码中显示的不仅仅是两个,但这并不重要)就像这样

   override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
      dispatchItems[indexPath.row].f()
   }

所以...编译器对此并不满意。它说当我定义 dispatchItems let 语句时:

无法将类型 '(MasterDispatchController) -> () -> ()' 的值转换为预期的参数类型 '() -> ()'

我想......好吧......我不确定我是否完全理解这一点,但编译器似乎想知道回调函数将来自哪个类。我明白为什么它可能需要它。所以我有点盲目地遵循编译器给我的模式,并将我的结构更改为:

struct dispatchItem {
   let description: String
   let f: (MasterDispatchController)->()->Void

   init(description: String, f: @escaping (MasterDispatchController)->()->()) {
      self.description = description
      self.f = f
   }
}

编译器很高兴,但现在当我尝试使用 dispatchItems[indexPath.row].f() 调用该函数时,它会说:

调用中缺少参数#1

该函数没有参数,所以我很困惑......

我想也许它是在向我询问有问题的对象的实例,这会有些意义......在我的例子中这将是“自我”,所以我尝试了dispatchItems[indexPath.row].f(self),但后来我得到了一个错误:

表达式解析为未使用的函数

所以我有点卡住了。

对不起,如果这是一个愚蠢的问题。感谢您的帮助。

【问题讨论】:

  • 所有这些代码背后的想法是什么?因为它可能是你做错了。
  • 我几乎可以肯定做错了。 :)
  • 这是我学习 iOS 和 Swift 的测试应用。我有一个表格视图,里面有大量的小测试。例如,在一个小测试中,我制作了一个展示自定义绘图视图的新视图控制器。在另一个我尝试制作一个集合视图。基本上我正在编写一个大型应用程序,我在其中尝试了很多不同的事情。这段代码是调用所有这些小测试的调度程序。在运行时,当我单击表格元素时,我会运行那个有问题的小测试。
  • 如果您刚刚开始和测试,那么只需使用switch indexPath.row {case 0: callFunc1() case1: callFunc2() default: CallSomeDefault()} 并调用您需要的函数即可。因为将函数传递给模型是错误的。
  • 嗯,这就是我正在做的事情......我对我有多个 switch 语句正在进行这一事实感到恼火。一个是我显示表格文本的地方,另一个是我分派给函数的地方。我想把这个逻辑放在一个地方(因此是 dispatchItems 数组)。

标签: swift function


【解决方案1】:

问题是您试图在实例属性的初始化程序中引用实例方法testStaticTabletestEditableTable之前 self 完全初始化。因此,编译器不能部分应用这些以self 作为隐式参数的方法,而是只能使用offer you the curried versions - 类型为(MasterDispatchController) -> () -> ()

然后可能会想将dispatchItems 属性标记为lazy,以便属性初始化程序在属性的第一次访问 上运行,而self 是 完全初始化。

class MasterDispatchController : UITableViewController {

    lazy private(set) var dispatchItems: [DispatchItem] = [
        DispatchItem(description: "Static Table", f: self.testStaticTable),
        DispatchItem(description: "Editable Table", f: self.testEditableTable)
    ]

    // ...
}

(请注意,我重命名了您的结构以符合 Swift 命名约定)

现在编译,因为您现在可以引用方法的部分应用版本(即 () -> Void 类型),并且可以将它们称为:

dispatchItems[indexPath.row].f()

然而,您现在有一个保留周期,因为您将闭包存储在 self 上,这些闭包会强烈捕获 self。这是因为当用作值时,self.someInstanceMethod 解析为部分应用的闭包,该闭包强烈捕获self

您已经接近实现的一个解决方案是使用方法的 curried 版本 - 不要强烈捕获 self,而是必须应用于给定实例以进行操作。

struct DispatchItem<Target> {

    let description: String
    let f: (Target) -> () -> Void

    init(description: String, f: @escaping (Target) -> () -> Void) {
        self.description = description
        self.f = f
    }
}

class MasterDispatchController : UITableViewController {

    let dispatchItems = [
        DispatchItem(description: "Static Table", f: testStaticTable),
        DispatchItem(description: "Editable Table", f: testEditableTable)
    ]

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        dispatchItems[indexPath.row].f(self)()
    }

    func testEditableTable() {}
    func testStaticTable() {}
}

这些函数现在将MasterDispatchController 的给定实例作为参数,并为您返回正确的实例方法来调用该给定实例。因此,您需要先用self 应用它们,通过说f(self) 来调用实例方法,然后用() 调用结果函数。

尽管使用self 经常应用这些功能可能会很不方便(或者您甚至可能无法访问self)。更通用的解决方案是将self 存储为weak 属性在DispatchItem 上,以及咖喱函数——然后您可以“按需”应用它:

struct DispatchItem<Target : AnyObject> {

    let description: String

    private let _action: (Target) -> () -> Void
    weak var target: Target?

    init(description: String, target: Target, action: @escaping (Target) -> () -> Void) {
        self.description = description
        self._action = action
    }

    func action() {
        // if we still have a reference to the target (it hasn't been deallocated),
        // get the reference, and pass it into _action, giving us the instance
        // method to call, which we then do with ().
        if let target = target {
            _action(target)()
        }
    }
}

class MasterDispatchController : UITableViewController {

    // note that we've made the property lazy again so we can access 'self' when
    // the property is first accessed, after it has been fully initialised.
    lazy private(set) var dispatchItems: [DispatchItem<MasterDispatchController>] = [
        DispatchItem(description: "Static Table", target: self, action: testStaticTable),
        DispatchItem(description: "Editable Table", target: self, action: testEditableTable)
    ]

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        dispatchItems[indexPath.row].action()
    }

    func testEditableTable() {}
    func testStaticTable() {}
}

这确保您没有保留周期,因为DispatchItem 没有对self 的强引用。

当然,您可以使用unownedself 的引用,例如in this Q&A。但是,只有当您能够保证您的 DispatchItem 实例不会比 self 寿命长(您希望将 dispatchItems 设为 private 属性)时,您才应该这样做。

【讨论】:

  • 解释得很好。非常感谢!
  • 乐于帮助@ErikWestwig
【解决方案2】:

这里的问题是 Swift 在底层对待类方法和函数的方式不同。类方法获得一个隐藏的self 参数(类似于它在 Python 中的工作方式),这使它们能够知道在哪个类实例上调用了它们。这就是为什么即使您将testEditableCode 声明为() -&gt; (),实际函数的类型为(MasterDispatchController) -&gt; () -&gt; ()。它需要知道在哪个对象实例上调用了它。

正确的方法是创建一个调用正确方法的闭包,如下所示:

class MasterDispatchController: UITableViewController {

   let dispatchItems = [
      dispatchItem(description: "Static Table", f: {() in
        self.testStaticTable()
      }),
      dispatchItem(description: "Editable Table", f: {() in
        self.testEditableTable()
      })
   ]

    func testEditableTable() {
        //some code
    }

    func testStaticTable() {
        //some code
    }

如果您熟悉 JavaScript,{() in ...code...} 表示法与 Python 中的 function() { ...code... }lambda: ...code... 相同。

【讨论】:

  • 你是什么意思“你也不能将self传递给你的方法”?使用给定的(MasterDispatchController) -&gt; () -&gt; (),您可以完全使用self 应用它,因为selfMasterDispatchController 实例。你只需要调用结果函数,例如f(self)()
  • 我不确定这是否有效,但我现在无法测试。我将删除关于无法通过自我的部分。我仍然认为在这种情况下使用闭包会更惯用/可读。
  • @Hamish - 但我不知道那种语法!我刚刚写了 f(self)。我没有写 f(self)()... 顺便说一句,当我这样做时,它起作用了!
  • 我承认我不知道类方法表现为柯里化函数,但这确实使您的错误有意义。当您执行 f(self) 时,它返回了一个您从未调用过的 () -&gt; () 类型的函数,这就是编译器所抱怨的。
  • 感谢您的所有帮助。 f(self)() 是我缺少的东西。现在这很有意义。
【解决方案3】:

这是实现你想要的方法。

用你想要的参数实现协议:

protocol Testable {
    func perfromAction()
    var description: String { get set }
    weak var viewController: YourViewController? { get set } //lets assume it is fine for testing
}

像这样访问您的UIViewController 并不完全正确,但现在还可以。您可以访问标签、segue 等。

为您想要的每个测试创建Class

class TestA: Testable {
    var description: String
    weak var viewController: YourViewController?
    func perfromAction() {
        print(description) //do something
        viewController?.testCallback(description: description) //accessing your UIViewController
    }
    init(viewController: YourViewController, description: String) {
        self.viewController = viewController
        self.description = description
    }
}

class TestB: Testable {
    var description: String
    weak var viewController: YourViewController?
    func perfromAction() {
        print(description) //do something
        viewController?.testCallback(description: description) //accessing your UIViewController
    }
    init(viewController: YourViewController, description: String) {
        self.viewController = viewController
        self.description = description
    }
}

您可以为每个Class 添加一些自定义参数,但协议中的这 3 个参数是必需的。

那么你的UIViewController 会是这样的

class YourViewController: UIViewController {
    var arrayOfTest: [Testable] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        arrayOfTest.append(TestA(viewController: self, description: "testA"))
        arrayOfTest.append(TestB(viewController: self, description: "testB"))
        arrayOfTest[0].perfromAction()
        arrayOfTest[1].perfromAction()
    }

    func testCallback(description: String) {
        print("I am called from \(description)")
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-21
    • 2014-07-25
    • 1970-01-01
    • 1970-01-01
    • 2016-09-24
    • 2018-12-12
    • 1970-01-01
    相关资源
    最近更新 更多