ReactiveKit和Bond Part 1.1

Swift中的Target-Action模式

在我的上一篇文章中,我承诺要深入了解如何在反应式框架中制作香肠,以便更好地了解ReactiveKit和Bond中的情况。 这将是一个多步骤的过程,第一步是了解目标动作模式以及如何在Swift中实现它。 我将大量借鉴Ole Begemann在该主题上的文章。 如果您从未检查过他与克里斯·艾德霍夫(Chris Eidhof)所写的书,那我就百分之十推荐。

这是关于Swift的怪异/酷事:您可以创建对方法的引用,然后使用该方法所属的类的实例调用该方法。

在这里,马克·华伯格让我告诉你我的意思。 Ole使用“银行帐户”类进行解释,但这很无聊。 我们将使用蝙蝠侠类:

 蝙蝠侠班:{ 
  var罪犯Biffed:Int = 0 
  func biffCriminals(amount:Int){ 
罪犯+ =金额
  } 
  } 
 让bruceWayne = Batman() 
bruceWayne.biffCriminals(金额:2)
打印(bruceWayne.criminalsBiffed)
//打印“ 2”

好。 超级容易。 现在是棘手的部分。 让我们创建对罪犯的引用Biffed(amount:Int)

 让biffer = Batman.biffCriminals 

“ biffer”不是对方法的调用。 这是一个常量,它引用方法Batman.biffCriminals。 Biffer的类型为(蝙蝠侠)->(内部)->()。 然后,我们可以创建一个新引用,该引用将Batman的实例作为参数:

  let wayneBiffer = biffer(布鲁斯·韦恩) 

然后,我们可以使用参数调用新方法:

  wayneBiffer(数量:10) 
打印(bruceWayne.biffedCriminals)
//现在打印出“ 12”

可以缩短为一行:

  biffer(布鲁斯·韦恩)(10) 

这就是所谓的咖喱函数。 在最终方法被调用之前,部分应用了函数并设置了一些参数。 当我们使用一个函数时,我们将返回另一个部分实现的函数。

真正有用的地方在于以一种类型安全的方式实现Target-Action模式。 Target-Action是在事件发生后执行回调的一种不错的半响应方式。 正如Ole所指出的,这可能是对使用闭包的一种改进,因为它迫使您在实现时处理任何强引用和弱引用,而不是在调用函数时将它们留待处理。

在处理UI元素时,您始终会看到Target-Action模式:

 让按钮= UIButton() 
button.addTarget(self,action:#selector(buttonTapped),用于:.touchUpInside)
  func buttonTapped(){ 
打印(“点击按钮!”)
  } 

#selector在这里是因为Target-Action的这种实现依赖于Objective-C。 我们可以在Swift中使这种类型安全。 这是Ole对Target-Action的实现,并添加了我自己的注释:

protocol TargetAction { 
func performAction()
}
 struct TargetActionWrapper : TargetAction { 
weak var target: T?
  //这是我们的咖喱函数 
let action: (T) -> () -> ()

func performAction() -> () {
if let t = target {
action(t)() //when performAction is called, we're calling on the curried function stored in the TargetActionWrapper
}
}
}

enum ControlEvent {
case touchUpInside
case valueChanged
// ...
}

class Control {
// This dictionary will store the actions for each control event
var actions = [ControlEvent: TargetAction]()
 //Adds a target and links it to an action 
func setTarget(target: T, action: (T) -> () -> (), controlEvent: ControlEvent) {
actions[controlEvent] = TargetActionWrapper(target: target, action: action)
}
//Removes a target
func removeTargetForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent] = nil
}
 //This function should be called when an event occurs - eg a button tap 
func performActionForControlEvent(controlEvent: ControlEvent) {
actions[controlEvent]?.performAction()
}
}

因此,让我们进行一些重构以了解其工作原理。 这是一个API Client类,具有从API获取令牌的功能:

 最终课程ApiClient { 
  //初始化,声明属性等 
  func getToken(completion:@转义(String)-> Void){ 
让端点=“ https://token.endpoint”
 警卫让url = URL(string:端点)else {return} 
var request = URLRequest(url:url)
let session = URLSession(配置:
URLSessionConfiguration.default)
let task = session.dataTask(with:request){(data,response,error)in
如果让数据=数据{
做{
//解析数据以获取令牌
完成(令牌)
}抓{}
}其他{}
}
task.resume()
}
  } 

这是针对Target-Action重构的方式。

 最后一课ApiClient: 控件 { 
  //初始化,声明属性等 
var令牌:字符串?
  func getToken(completion:@转义(String)-> Void){ 
让端点=“ https://token.endpoint”
 警卫让url = URL(string:端点)else {return} 
var request = URLRequest(url:url)
let session = URLSession(配置:
URLSessionConfiguration.default)
let task = session.dataTask(with:request){ [弱自身] (数据,响应,错误)
如果让数据=数据{
做{
//解析数据以获取令牌
self.token =令牌
self.performActionForControlEvent(controlEvent:.tokenReceived)
}抓{}
}其他{}
}
task.resume()
}
  } 

现在,在我们的视图控制器中,我们需要获取令牌:

 类ViewController:UIViewController { 
 让apiClient = ApiClient() 
 覆盖func viewDidLoad(){ 
super.viewDidLoad()
apiClient.setTarget(target:self,action:
LoginViewController.printToken,controlEvent:.tokenReceived)
apiClient.getToken()
  } 
func printToken(){
打印(apiClient.token)
}

}

所以你有它。 如前所述,这很好,因为它无需处理代码高层中的参考循环。 下次,我们将看到它如何使我们更接近反应式编程。 有什么想法吗? 把它们留在下面!