Currying to Reduce Duplicated Code
Possibly at the Expense of Readability
I was pulling in the smart deselection function written by one of my co-workers into a project, and as the code isn't available by Carthage quite yet, I copied and pasted it:
extension UIViewController {
func rz_smoothlyDeselectRows(tableView tableView: UITableView?) {
let selectedIndexPaths = tableView?.indexPathsForSelectedRows ?? []
if let coordinator = transitionCoordinator() {
coordinator.animateAlongsideTransitionInView(parentViewController?.view, animation: { context in
selectedIndexPaths.forEach {
tableView?.deselectRowAtIndexPath($0, animated: context.isAnimated())
}
}, completion: { context in
if context.isCancelled() {
selectedIndexPaths.forEach {
tableView?.selectRowAtIndexPath($0, animated: false, scrollPosition: .None)
}
}
})
}
else {
selectedIndexPaths.forEach {
tableView?.deselectRowAtIndexPath($0, animated: false)
}
}
}
}
After a brief glance, I decided that it wasn't written in quite the code style that I wanted, in particular I like to avoid multiple inline blocks in an function call. When I was refactoring those bits, I decided to be clever and use currying to avoid semi-duplicated code in the selectedIndexPaths.forEach
blocks.
extension UIViewController {
func rz_smoothlyDeselectRows(tableView tableViewIn: UITableView?) {
guard let tableView = tableViewIn,
selectedIndexPaths = tableView.indexPathsForSelectedRows else {
return
}
let deselect = { (tableView: UITableView, animated: Bool) -> ((NSIndexPath) -> ()) in
return { indexPath in
tableView.deselectRowAtIndexPath(indexPath, animated: animated)
}
}
let reselect = { (tableView: UITableView, animated: Bool) -> ((NSIndexPath) -> ()) in
return { indexPath in
tableView.selectRowAtIndexPath(indexPath, animated: animated, scrollPosition: .None)
}
}
if let coordinator = transitionCoordinator() {
let animation = { (context: UIViewControllerTransitionCoordinatorContext) in
selectedIndexPaths.forEach(deselect(tableView, context.isAnimated()))
}
let completion = { (context: UIViewControllerTransitionCoordinatorContext) in
if context.isCancelled() {
selectedIndexPaths.forEach(reselect(tableView, false))
}
}
let parentView = parentViewController?.view
coordinator.animateAlongsideTransitionInView(parentView, animation: animation, completion: completion)
}
else {
selectedIndexPaths.forEach(deselect(tableView, false))
}
}
}
The body of my function ended up slightly longer, but if the duplicated functions were longer, and more complex it helps to reduce the chances of missing a change, which can lead to annoying copy / paste bugs.