2016-01-16 7 views
-1

Предположим, у меня есть функция, которая принимает функцию обратного вызова с отправителем, как это:Swift: Accept замыкание с изменяющимися параметрами

func performAction(aNumber: Double, completion: (sender: UIButton) -> Void) { 
    // Does some stuff here 
    let button = getAButtonFromSomewhere() 
    completion(button) 
} 

и так один возможный способ для вызова этой функции, передавая в существующие функции для обратного вызова, а не определять замыкание в месте:

performAction(10, completion: myCallback) 

func myCallback(sender: UIButton) { 
    sender.setTitle("foo", forState: .Normal) 
} 

Назад в моем определении для performAction, как может Я определяю блок завершения для принятия UIButton или любого его подкласса?

В качестве примера предположим, что у меня есть подкласс UIButton под названием CustomButton. Поэтому в моем обратном вызове я заинтересован только в принятии CustomButton. Я хотел бы сделать это:

performAction(10, completion: myCallback) 

// This produces a compiler error: 
func myCallback(sender: CustomButton) { 
    sender.setTitle("foo", forState: .Normal) 
} 

// This works, but forces me to cast to my custom class: 
func myCallback(sender: UIButton) { 
    let realButton = sender as! CustomButton 
    realButton.setTitle("foo", forState: .Normal) 
} 

Но компилятор не позволит, потому что определение performAction требует обратного вызова, чтобы принять в UIButton специально (несмотря на то, CustomButtonявляетсяUIButton подкласс).

Я бы хотел, чтобы performAction был общим, чтобы его можно было упаковать в библиотеку и работать с любым подклассом UIButton. Можно ли это сделать в Swift?

EDIT: Я попытался упростить то, что я делаю, с приведенным выше примером, но я думаю, что это просто вызвало путаницу. Вот фактический код, который я пытаюсь сделать работу, с некоторыми улучшениями благодаря @ luk2302:

public extension UIButton { 

    private class Action: AnyObject { 
     private var function: Any 
     init(function: Any) { 
      self.function = function 
     } 
    } 

    // Trickery to add a stored property to UIButton... 
    private static var actionsAssocKey: UInt8 = 0 

    private var action: Action? { 
     get { 
      return objc_getAssociatedObject(self, &UIButton.actionsAssocKey) as? Action 
     } 
     set(newValue) { 
      objc_setAssociatedObject(self, &UIButton.actionsAssocKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) 
     } 
    } 

    internal func performAction(sender: UIButton) { 
     if let function = self.action!.function as?() -> Void { 
      function() 
     // THIS IS WHERE THINGS BREAK NOW: 
     } else if let function = self.action!.function as? (sender: self.Type) -> Void { 
      function(sender: self) 
     } 
    } 

    public func addTarget(forControlEvents event: UIControlEvents, action:() -> Void) { 
     self.action = Action(function: action) 
     self.addTarget(self, action: "performAction:", forControlEvents: event) 
    } 

    public func addTarget<B: UIButton>(forControlEvents: UIControlEvents, actionWithSender: (sender: B) -> Void) { 
     self.action = Action(function: actionWithSender) 
     self.addTarget(self, action: "performAction:", forControlEvents: forControlEvents) 
    } 
} 

Единственная часть, которая ломает сейчас линия, что я заметил, в (sender: self.Type) (self быть либо UIButton, или некоторый подкласс).

Так что это немного отличается от моего первоначального вопроса, но как я могу наложить function на закрытие, принимающее отправителя того же типа, что и self? Этот код работает отлично, если я жестко программирую тип, но он должен иметь возможность работать для любогоUIButton подкласс.

+0

Как работает мифический 'getAButtonFromSomewhere()' на самом деле? – jtbandes

+0

@jtbandes Это был просто пример, поэтому мне не нужно публиковать всю свою реализацию здесь. Ради вопроса, мы можем с уверенностью предположить, что он всегда возвращает «CustomButton» или любой класс, который ожидает пользователь. – hundley

ответ

1

Вы можете сделать UIButton подкласс общий параметр для функции performAction, но тогда вам нужно будет отливать кнопку перед тем передать его в функцию обратного вызова, если вы также имеют общий путь «Начало» правый тип кнопки.

// performAction() works with any type of UIButton 
func performAction<B: UIButton>(aNumber: Double, completion: (sender: B) -> Void) 
{ 
    // Assuming getAButtonFromSomewhere returns UIButton, and not B, you must cast it. 
    if let button = getAButtonFromSomewhere() as? B { 
     completion(sender: button) 
    } 
} 
1

Спросите себя: что произойдет, если вы передадите закрытие типа (CustomButton -> Void) в completion, а затем getAButtonFromSomewhere возвращает экземпляр UIButton? Код тогда не может вызывать замыкание, так как UIButton не является CustomButton.

Компилятор просто не позволит вам пройти (CustomButton -> Void) к (UIButton -> Void) потому (CustomButton -> Void) более ограничительными, чем (UIButton -> Void).Учтите, что вы можете пройти (UIButton -> Void) до закрытия типа (CustomButton -> Void), так как (UIButton -> Void) is less ограничительный - вы можете передать все, что вы переходите на первое и второе.

Поэтому либо сделать func использовать общий тип, как следует @jtbandes или использовать свой первоначальный подход немного улучшились:

func myCallback(sender: UIButton) { 
    if let realButton = sender as? CustomButton { 
     realButton.setTitle("foo", forState: .Normal) 
    } 
} 

Оба решения приведут к setTitle не будут вызываться всякий раз, когда возвращаемое значение getAButtonFromSomewhere не является CustomButton.

+0

'getAButtonFromSomewhere' на самом деле не реально - я просто использовал это ради вопроса. В реальной реализации мы гарантируем, что 'button' всегда является типом, который мы ищем. Но из вашего объяснения имеет смысл, что компилятор не может этого точно знать. – hundley

+0

@hundley * как * вы можете быть уверены? Если вы действительно можете быть уверены, почему закрытие определено для принятия 'UIButton' в первую очередь, а не' CustomButton'? – luk2302

+0

См. Мое редактирование исходного вопроса. Любая идея, как решить эту последнюю крошечную проблему? – hundley