Swift函数作为类中的参数

时间:2017-03-28 18:37:19

标签: swift function

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

我无法将功能指定为参数。

我已经定义了这个结构:

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),但后来我收到了一个错误:

  

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

所以我有点卡住了。

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

3 个答案:

答案 0 :(得分:2)

问题是您在 {{1}之前尝试在实例属性的初始化中引用实例方法testStaticTabletestEditableTable完全初始化。因此,编译器不能将这些方法部分应用self作为隐式参数,而只能self - 类型为(MasterDispatchController) -> () -> ()

可能有人试图将dispatchItems属性标记为offer you the curried versions,以便属性初始化程序在属性的首次访问上运行,而{{1} 已完全初始化。

self

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

现在编译,因为你现在可以引用方法的部分应用版本(即类型class MasterDispatchController : UITableViewController { lazy private(set) var dispatchItems: [DispatchItem] = [ DispatchItem(description: "Static Table", f: self.testStaticTable), DispatchItem(description: "Editable Table", f: self.testEditableTable) ] // ... } ),并可以将它们称为:

() -> Void

然而,您现在有一个保留周期,因为您在dispatchItems[indexPath.row].f() 上存储了强烈捕获self的闭包。这是因为当用作值时,self会解析为强烈捕获self.someInstanceMethod的部分应用的闭包。

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

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,以及咖喱功能 - 然后您可以按需应用它[&#39]:

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使用unowned个引用,例如显示lazy。但是,如果您保证您的self个实例不会超过DispatchItem(您希望self成为{{}},那么您应该这样做{1}}一个属性。)

答案 1 :(得分:1)

这里的问题是Swift在不同的方面对待类方法和函数。类方法获得一个隐藏的self参数(类似于它在Python中的工作方式),它允许他们知道调用它们的类实例。这就是为什么即使您将testEditableCode声明为() -> (),实际函数的类型为(MasterDispatchController) -> () -> ()。它需要知道它被调用的对象实例。

正确的做法是创建一个调用正确方法的闭包,例如:

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...相同。

答案 2 :(得分:0)

这是达到你想要的方式。

使用您想要的参数实现协议:

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

像这样访问你的UIViewController是不对的,但现在还可以。您可以访问标签,segues等。

为每个所需的测试创建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)")
    }
}