1. 1. 需求假设现在正在为一款 App 设计框架,所有的一级页面左上角都有一个菜单按钮,点击以后触发同一事件:弹出警告框。 OOP按照以前的思想,首先,我们会去给一级页面写一个公共父类:MainBaseViewController (当然 MainBaseViewController 很有可能还会有个父类叫 BaseViewController),然后在 MainBaseViewController 去添加菜单按钮与相应的事件。UML 图大致如下: 问题但是在实际项目中,不是所有界面都会继承 MainBaseViewController ,可能还会去继承 UITableViewController 或者 UICollectionViewController 。 方法一:当然,也可以选择放弃使用 UITableViewController ,选择使用 UIViewController + UITableView 来替代,然后开心的继承 MainBaseViewController。 方法二 :试试面向协议编程吧。 解决在这个例子中,POP 的更有一种模块化的感觉,需要什么样的功能(行为),只需要遵循协议即可。关于为什么要将行为封装为协议的案例,可以查看文章 《Mixins 比继承更好》 。 首先创建一个 BurgerMenuManager.swift 的文件,添加一个名为 BurgerMenuManager 的协议,并为此协议添加一个初始化按钮的方法 needBurgerButton。 protocol BurgerMenuManager { func needBurgerManager();} 在新的 Swift 2.0 的语法中,允许协议有默认实现,这也为协议的设计提供了新思路。因为每一个菜单按钮的样式与事件都相同,所以直接给出默认实现就行,而没必要每个遵循协议的类都去实现。在当前文件下,添加以下代码: extension BurgerMenuManager { func needBurgerManager() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: nil, action: nil) // 添加按钮到 navigation 上 }} 暂时不给按钮添加事件,先来考虑如何将 burgerButton 添加到 navigation 上。 在当前的 extension 中,没有办法使用 self ,是因为编译器并不知道会有哪些类型来遵循 BurgerMenuManager 协议,所以,只需要对遵循协议的类型进行限制即可。根据当前需求,所有遵循协议的至少都是 UIViewController ,那么,对刚才的 extension 做以下修改: extension BurgerMenuManager where Self: UIViewController { func needBurgerManager() { ... // 现在就可以添加按钮了 self.navigationItem.leftBarButtonItem = burgerButton }} 接着,需要给按钮添加事件。想想,这还不简单,直接给 target 和 action 不就行了吗。但是这是在 extension 中,要实现起来,还真不是很方便(或许是我没找到优雅的解决方案,希望大家能参与讨论)。 首先试试直接添加 target 和 action 的方式吧: extension BurgerMenuManager { func needBurgerManager() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: self, action: Selector("burgerItemTapped")) self.navigationItem.leftBarButtonItem = burgerButton } func burgerItemTapped { print("tapped") }} 实验之前,先在 Main.Storyboard 中,给 ViewController Embed In 一个 Navigation View Controller 。然后让 ViewController 遵循 BurgerMenuManager 协议,并在 viewDidLoad 中初始化菜单按钮: class ViewController: UIViewController, BurgerMenuManager { override func viewDidLoad() { super.viewDidLoad() needBurgerManager() }} 运行程序,点击菜单按钮,程序崩溃。原因:ViewController 中找不到 burgerItemTapped 方法。因为 action 不能写在 extension 中。 解决方案有两种,但核心都是给 item 绑定 block: 第一种:使用 runtime 给绑定 UIBarButtonItem 绑定 block,然后通过 block 来添加按钮的 action; 第二种:继承 UIBarButtonItem ,添加 block 属性,然后通过 block 来添加按钮的 action; 这几种解决方案在 Objective-C 中并不陌生,即使是在 Swift 中,使用方式也大同小异。根据项目需求,可任意选择其中一种,并没有一定的写法。 如果项目中有很多 UIBarButtonItem 需要使用到 runtime 绑定的方式,那么这里也直接用这种方式就行。当然,也有可能会觉得,Swift 在有意的弱化 runtime,再大量使用或许不是很优雅,那么选择第二种方案也是不错的。 第一种(runtime):首先给 UIBarButtonItem 添加 block。 typealias STBarButtonItemBlock = () -> ()class STBarButtonItemWrapper { var block: STBarButtonItemBlock? init(block: STBarButtonItemBlock) { self.block = block }}struct STConst { static var STBarButtonItemWrapperKey = "STBarButtonItemWrapperKey"}extension UIBarButtonItem { convenience init(title: String?, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init() self.title = title self.style = style target = self action = "buttonTapped" let wrapper = STBarButtonItemWrapper(block: block) objc_setAssociatedObject(self, &STConst.STBarButtonItemWrapperKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } func buttonTapped() { guard let wrapper = objc_getAssociatedObject(self, &STConst.STBarButtonItemWrapperKey) as? STBarButtonItemWrapper else { return } guard let block = wrapper.block else { return } block() }} 需要注意的是,objc_setAssociatedObject 的第三个参数要是对象。所以需要将 block 放入对象中,然后使用runtime 将该对象添加为 UIBarButtonItem 成员变量。 然后,在 protocol 的默认实现中,将 needBurgerButton() 方法实现改为: extension BurgerButtonManager where Self: UIViewController { func needBurgerButton() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain) { print("菜单显示") } self.navigationItem.leftBarButtonItem = burgerButton }} 第二种(继承): 相比 runtime,继承的实现就简单多了。首先给继承类添加便利构造方法,用于绑定 block: typealias STBarButtonItemBlock = ()->()class BlockBarButtonItem: UIBarButtonItem { var block: STBarButtonItemBlock? convenience init(title: String, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init(title: title, style: style, target: nil, action: "buttonTapped") self.target = self self.block = block } convenience init(image: UIImage, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init(image: image, style: style, target: nil, action: "buttonTapped") self.target = self self.block = block } func buttonTapped() { guard let block = block else { return } block() }} 然后,将默认实现的 needBurgerButton() 方法实现改为: extension BurgerButtonManager where Self: UIViewController { func needBurgerButton() { let item = BlockBarButtonItem(title: "菜单", style: .Plain) { print("菜单显示") } self.navigationItem.leftBarButtonItem = item }} 最后完整的 Demo 已上传 Github。在完成过程中,也和翻译组的小伙伴进行了讨论。感谢 @ray16897188 , @小锅 ,@靛青 。 One More Thing在选择绑定 action 的过程中,我也尝试过在 extension 中绑定,有解决方案说在 func 前面加 @objc,但是这并不凑效。详细描述可参见 Apple Thread 16773 。 当然,欢迎评论。 参考Mixins 比继承更好 Github : Swift2-Protocol-Extension-Example iOS 9 Tutorial Series: Protocol-Oriented Programming with UIKit 如果你还在用子类(Subclassing),那就不对了
  2. 2. 需求
  3. 3. OOP按照以前的思想,首先,我们会去给一级页面写一个公共父类:MainBaseViewController (当然 MainBaseViewController 很有可能还会有个父类叫 BaseViewController),然后在 MainBaseViewController 去添加菜单按钮与相应的事件。UML 图大致如下: 问题但是在实际项目中,不是所有界面都会继承 MainBaseViewController ,可能还会去继承 UITableViewController 或者 UICollectionViewController 。 方法一:当然,也可以选择放弃使用 UITableViewController ,选择使用 UIViewController + UITableView 来替代,然后开心的继承 MainBaseViewController。 方法二 :试试面向协议编程吧。 解决在这个例子中,POP 的更有一种模块化的感觉,需要什么样的功能(行为),只需要遵循协议即可。关于为什么要将行为封装为协议的案例,可以查看文章 《Mixins 比继承更好》 。 首先创建一个 BurgerMenuManager.swift 的文件,添加一个名为 BurgerMenuManager 的协议,并为此协议添加一个初始化按钮的方法 needBurgerButton。 protocol BurgerMenuManager { func needBurgerManager();} 在新的 Swift 2.0 的语法中,允许协议有默认实现,这也为协议的设计提供了新思路。因为每一个菜单按钮的样式与事件都相同,所以直接给出默认实现就行,而没必要每个遵循协议的类都去实现。在当前文件下,添加以下代码: extension BurgerMenuManager { func needBurgerManager() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: nil, action: nil) // 添加按钮到 navigation 上 }} 暂时不给按钮添加事件,先来考虑如何将 burgerButton 添加到 navigation 上。 在当前的 extension 中,没有办法使用 self ,是因为编译器并不知道会有哪些类型来遵循 BurgerMenuManager 协议,所以,只需要对遵循协议的类型进行限制即可。根据当前需求,所有遵循协议的至少都是 UIViewController ,那么,对刚才的 extension 做以下修改: extension BurgerMenuManager where Self: UIViewController { func needBurgerManager() { ... // 现在就可以添加按钮了 self.navigationItem.leftBarButtonItem = burgerButton }} 接着,需要给按钮添加事件。想想,这还不简单,直接给 target 和 action 不就行了吗。但是这是在 extension 中,要实现起来,还真不是很方便(或许是我没找到优雅的解决方案,希望大家能参与讨论)。 首先试试直接添加 target 和 action 的方式吧: extension BurgerMenuManager { func needBurgerManager() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: self, action: Selector("burgerItemTapped")) self.navigationItem.leftBarButtonItem = burgerButton } func burgerItemTapped { print("tapped") }} 实验之前,先在 Main.Storyboard 中,给 ViewController Embed In 一个 Navigation View Controller 。然后让 ViewController 遵循 BurgerMenuManager 协议,并在 viewDidLoad 中初始化菜单按钮: class ViewController: UIViewController, BurgerMenuManager { override func viewDidLoad() { super.viewDidLoad() needBurgerManager() }} 运行程序,点击菜单按钮,程序崩溃。原因:ViewController 中找不到 burgerItemTapped 方法。因为 action 不能写在 extension 中。 解决方案有两种,但核心都是给 item 绑定 block: 第一种:使用 runtime 给绑定 UIBarButtonItem 绑定 block,然后通过 block 来添加按钮的 action; 第二种:继承 UIBarButtonItem ,添加 block 属性,然后通过 block 来添加按钮的 action; 这几种解决方案在 Objective-C 中并不陌生,即使是在 Swift 中,使用方式也大同小异。根据项目需求,可任意选择其中一种,并没有一定的写法。 如果项目中有很多 UIBarButtonItem 需要使用到 runtime 绑定的方式,那么这里也直接用这种方式就行。当然,也有可能会觉得,Swift 在有意的弱化 runtime,再大量使用或许不是很优雅,那么选择第二种方案也是不错的。 第一种(runtime):首先给 UIBarButtonItem 添加 block。 typealias STBarButtonItemBlock = () -> ()class STBarButtonItemWrapper { var block: STBarButtonItemBlock? init(block: STBarButtonItemBlock) { self.block = block }}struct STConst { static var STBarButtonItemWrapperKey = "STBarButtonItemWrapperKey"}extension UIBarButtonItem { convenience init(title: String?, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init() self.title = title self.style = style target = self action = "buttonTapped" let wrapper = STBarButtonItemWrapper(block: block) objc_setAssociatedObject(self, &STConst.STBarButtonItemWrapperKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } func buttonTapped() { guard let wrapper = objc_getAssociatedObject(self, &STConst.STBarButtonItemWrapperKey) as? STBarButtonItemWrapper else { return } guard let block = wrapper.block else { return } block() }} 需要注意的是,objc_setAssociatedObject 的第三个参数要是对象。所以需要将 block 放入对象中,然后使用runtime 将该对象添加为 UIBarButtonItem 成员变量。 然后,在 protocol 的默认实现中,将 needBurgerButton() 方法实现改为: extension BurgerButtonManager where Self: UIViewController { func needBurgerButton() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain) { print("菜单显示") } self.navigationItem.leftBarButtonItem = burgerButton }} 第二种(继承): 相比 runtime,继承的实现就简单多了。首先给继承类添加便利构造方法,用于绑定 block: typealias STBarButtonItemBlock = ()->()class BlockBarButtonItem: UIBarButtonItem { var block: STBarButtonItemBlock? convenience init(title: String, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init(title: title, style: style, target: nil, action: "buttonTapped") self.target = self self.block = block } convenience init(image: UIImage, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init(image: image, style: style, target: nil, action: "buttonTapped") self.target = self self.block = block } func buttonTapped() { guard let block = block else { return } block() }} 然后,将默认实现的 needBurgerButton() 方法实现改为: extension BurgerButtonManager where Self: UIViewController { func needBurgerButton() { let item = BlockBarButtonItem(title: "菜单", style: .Plain) { print("菜单显示") } self.navigationItem.leftBarButtonItem = item }} 最后完整的 Demo 已上传 Github。在完成过程中,也和翻译组的小伙伴进行了讨论。感谢 @ray16897188 , @小锅 ,@靛青 。 One More Thing在选择绑定 action 的过程中,我也尝试过在 extension 中绑定,有解决方案说在 func 前面加 @objc,但是这并不凑效。详细描述可参见 Apple Thread 16773 。 当然,欢迎评论。 参考Mixins 比继承更好 Github : Swift2-Protocol-Extension-Example iOS 9 Tutorial Series: Protocol-Oriented Programming with UIKit 如果你还在用子类(Subclassing),那就不对了
  4. 4. OOP
  5. 5. 问题但是在实际项目中,不是所有界面都会继承 MainBaseViewController ,可能还会去继承 UITableViewController 或者 UICollectionViewController 。 方法一:当然,也可以选择放弃使用 UITableViewController ,选择使用 UIViewController + UITableView 来替代,然后开心的继承 MainBaseViewController。 方法二 :试试面向协议编程吧。 解决在这个例子中,POP 的更有一种模块化的感觉,需要什么样的功能(行为),只需要遵循协议即可。关于为什么要将行为封装为协议的案例,可以查看文章 《Mixins 比继承更好》 。 首先创建一个 BurgerMenuManager.swift 的文件,添加一个名为 BurgerMenuManager 的协议,并为此协议添加一个初始化按钮的方法 needBurgerButton。 protocol BurgerMenuManager { func needBurgerManager();} 在新的 Swift 2.0 的语法中,允许协议有默认实现,这也为协议的设计提供了新思路。因为每一个菜单按钮的样式与事件都相同,所以直接给出默认实现就行,而没必要每个遵循协议的类都去实现。在当前文件下,添加以下代码: extension BurgerMenuManager { func needBurgerManager() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: nil, action: nil) // 添加按钮到 navigation 上 }} 暂时不给按钮添加事件,先来考虑如何将 burgerButton 添加到 navigation 上。 在当前的 extension 中,没有办法使用 self ,是因为编译器并不知道会有哪些类型来遵循 BurgerMenuManager 协议,所以,只需要对遵循协议的类型进行限制即可。根据当前需求,所有遵循协议的至少都是 UIViewController ,那么,对刚才的 extension 做以下修改: extension BurgerMenuManager where Self: UIViewController { func needBurgerManager() { ... // 现在就可以添加按钮了 self.navigationItem.leftBarButtonItem = burgerButton }} 接着,需要给按钮添加事件。想想,这还不简单,直接给 target 和 action 不就行了吗。但是这是在 extension 中,要实现起来,还真不是很方便(或许是我没找到优雅的解决方案,希望大家能参与讨论)。 首先试试直接添加 target 和 action 的方式吧: extension BurgerMenuManager { func needBurgerManager() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: self, action: Selector("burgerItemTapped")) self.navigationItem.leftBarButtonItem = burgerButton } func burgerItemTapped { print("tapped") }} 实验之前,先在 Main.Storyboard 中,给 ViewController Embed In 一个 Navigation View Controller 。然后让 ViewController 遵循 BurgerMenuManager 协议,并在 viewDidLoad 中初始化菜单按钮: class ViewController: UIViewController, BurgerMenuManager { override func viewDidLoad() { super.viewDidLoad() needBurgerManager() }} 运行程序,点击菜单按钮,程序崩溃。原因:ViewController 中找不到 burgerItemTapped 方法。因为 action 不能写在 extension 中。 解决方案有两种,但核心都是给 item 绑定 block: 第一种:使用 runtime 给绑定 UIBarButtonItem 绑定 block,然后通过 block 来添加按钮的 action; 第二种:继承 UIBarButtonItem ,添加 block 属性,然后通过 block 来添加按钮的 action; 这几种解决方案在 Objective-C 中并不陌生,即使是在 Swift 中,使用方式也大同小异。根据项目需求,可任意选择其中一种,并没有一定的写法。 如果项目中有很多 UIBarButtonItem 需要使用到 runtime 绑定的方式,那么这里也直接用这种方式就行。当然,也有可能会觉得,Swift 在有意的弱化 runtime,再大量使用或许不是很优雅,那么选择第二种方案也是不错的。 第一种(runtime):首先给 UIBarButtonItem 添加 block。 typealias STBarButtonItemBlock = () -> ()class STBarButtonItemWrapper { var block: STBarButtonItemBlock? init(block: STBarButtonItemBlock) { self.block = block }}struct STConst { static var STBarButtonItemWrapperKey = "STBarButtonItemWrapperKey"}extension UIBarButtonItem { convenience init(title: String?, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init() self.title = title self.style = style target = self action = "buttonTapped" let wrapper = STBarButtonItemWrapper(block: block) objc_setAssociatedObject(self, &STConst.STBarButtonItemWrapperKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } func buttonTapped() { guard let wrapper = objc_getAssociatedObject(self, &STConst.STBarButtonItemWrapperKey) as? STBarButtonItemWrapper else { return } guard let block = wrapper.block else { return } block() }} 需要注意的是,objc_setAssociatedObject 的第三个参数要是对象。所以需要将 block 放入对象中,然后使用runtime 将该对象添加为 UIBarButtonItem 成员变量。 然后,在 protocol 的默认实现中,将 needBurgerButton() 方法实现改为: extension BurgerButtonManager where Self: UIViewController { func needBurgerButton() { let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain) { print("菜单显示") } self.navigationItem.leftBarButtonItem = burgerButton }} 第二种(继承): 相比 runtime,继承的实现就简单多了。首先给继承类添加便利构造方法,用于绑定 block: typealias STBarButtonItemBlock = ()->()class BlockBarButtonItem: UIBarButtonItem { var block: STBarButtonItemBlock? convenience init(title: String, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init(title: title, style: style, target: nil, action: "buttonTapped") self.target = self self.block = block } convenience init(image: UIImage, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) { self.init(image: image, style: style, target: nil, action: "buttonTapped") self.target = self self.block = block } func buttonTapped() { guard let block = block else { return } block() }} 然后,将默认实现的 needBurgerButton() 方法实现改为: extension BurgerButtonManager where Self: UIViewController { func needBurgerButton() { let item = BlockBarButtonItem(title: "菜单", style: .Plain) { print("菜单显示") } self.navigationItem.leftBarButtonItem = item }} 最后完整的 Demo 已上传 Github。在完成过程中,也和翻译组的小伙伴进行了讨论。感谢 @ray16897188 , @小锅 ,@靛青 。 One More Thing在选择绑定 action 的过程中,我也尝试过在 extension 中绑定,有解决方案说在 func 前面加 @objc,但是这并不凑效。详细描述可参见 Apple Thread 16773 。 当然,欢迎评论。 参考Mixins 比继承更好 Github : Swift2-Protocol-Extension-Example iOS 9 Tutorial Series: Protocol-Oriented Programming with UIKit 如果你还在用子类(Subclassing),那就不对了
  6. 6. 问题
  7. 7. 解决
    1. 7.0.1. 第一种(runtime):
  • 8. 最后完整的 Demo 已上传 Github。在完成过程中,也和翻译组的小伙伴进行了讨论。感谢 @ray16897188 , @小锅 ,@靛青 。 One More Thing在选择绑定 action 的过程中,我也尝试过在 extension 中绑定,有解决方案说在 func 前面加 @objc,但是这并不凑效。详细描述可参见 Apple Thread 16773 。 当然,欢迎评论。 参考Mixins 比继承更好 Github : Swift2-Protocol-Extension-Example iOS 9 Tutorial Series: Protocol-Oriented Programming with UIKit 如果你还在用子类(Subclassing),那就不对了
  • 9. 最后
  • 10. One More Thing
  • 11. 参考Mixins 比继承更好 Github : Swift2-Protocol-Extension-Example iOS 9 Tutorial Series: Protocol-Oriented Programming with UIKit 如果你还在用子类(Subclassing),那就不对了
  • 12. 参考
  • 用面向协议的思想打造菜单按钮

    1.10 在北京参加了中国首届 Swift 开发者大会,其中很多人都提到一个观点:POP (protocol oriented programming)面向协议编程。结合之前翻译组一些关于 POP 的译文,觉得 POP 将势不可挡。


    环境信息
    Mac OS X 10.11.3
    Xcode 7.2.1
    iOS 9.2
    Swift 2

    正文

    在看 《Mixins 比继承更好》 一文中,其中提到了 OOP 和 POP 的一些优劣势。其中 OOP 的 God object 是最让框架设计者头疼的。文中列举了一个 Burger Menu 的例子,在此我再描述一下,因为本文会细化他的实现。

    完整 Demo 下载地址:

    https://github.com/saitjr/STBurgerButtonManager.git

    需求

    假设现在正在为一款 App 设计框架,所有的一级页面左上角都有一个菜单按钮,点击以后触发同一事件:弹出警告框。

    OOP

    按照以前的思想,首先,我们会去给一级页面写一个公共父类:MainBaseViewController (当然 MainBaseViewController 很有可能还会有个父类叫 BaseViewController),然后在 MainBaseViewController 去添加菜单按钮与相应的事件。UML 图大致如下:

    问题

    但是在实际项目中,不是所有界面都会继承 MainBaseViewController ,可能还会去继承 UITableViewController 或者 UICollectionViewController

    方法一:当然,也可以选择放弃使用 UITableViewController ,选择使用 UIViewController + UITableView 来替代,然后开心的继承 MainBaseViewController

    方法二 :试试面向协议编程吧。

    解决

    在这个例子中,POP 的更有一种模块化的感觉,需要什么样的功能(行为),只需要遵循协议即可。关于为什么要将行为封装为协议的案例,可以查看文章 《Mixins 比继承更好》

    首先创建一个 BurgerMenuManager.swift 的文件,添加一个名为 BurgerMenuManager 的协议,并为此协议添加一个初始化按钮的方法 needBurgerButton

    protocol BurgerMenuManager {
    func needBurgerManager();
    }

    在新的 Swift 2.0 的语法中,允许协议有默认实现,这也为协议的设计提供了新思路。因为每一个菜单按钮的样式与事件都相同,所以直接给出默认实现就行,而没必要每个遵循协议的类都去实现。在当前文件下,添加以下代码:

    extension BurgerMenuManager {
    func needBurgerManager() {
    let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: nil, action: nil)
    // 添加按钮到 navigation 上
    }
    }

    暂时不给按钮添加事件,先来考虑如何将 burgerButton 添加到 navigation 上。

    在当前的 extension 中,没有办法使用 self ,是因为编译器并不知道会有哪些类型来遵循 BurgerMenuManager 协议,所以,只需要对遵循协议的类型进行限制即可。根据当前需求,所有遵循协议的至少都是 UIViewController ,那么,对刚才的 extension 做以下修改:

    extension BurgerMenuManager where Self: UIViewController {
    func needBurgerManager() {
    ...
    // 现在就可以添加按钮了
    self.navigationItem.leftBarButtonItem = burgerButton
    }
    }

    接着,需要给按钮添加事件。想想,这还不简单,直接给 target 和 action 不就行了吗。但是这是在 extension 中,要实现起来,还真不是很方便(或许是我没找到优雅的解决方案,希望大家能参与讨论)。

    首先试试直接添加 target 和 action 的方式吧:

    extension BurgerMenuManager {
    func needBurgerManager() {
    let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain, target: self, action: Selector("burgerItemTapped"))
    self.navigationItem.leftBarButtonItem = burgerButton
    }

    func burgerItemTapped {
    print("tapped")
    }
    }

    实验之前,先在 Main.Storyboard 中,给 ViewController Embed In 一个 Navigation View Controller 。然后让 ViewController 遵循 BurgerMenuManager 协议,并在 viewDidLoad 中初始化菜单按钮:

    class ViewController: UIViewController, BurgerMenuManager {
    override func viewDidLoad() {
    super.viewDidLoad()
    needBurgerManager()
    }
    }

    运行程序,点击菜单按钮,程序崩溃。原因:ViewController 中找不到 burgerItemTapped 方法。因为 action 不能写在 extension 中。

    解决方案有两种,但核心都是给 item 绑定 block:

    第一种:使用 runtime 给绑定 UIBarButtonItem 绑定 block,然后通过 block 来添加按钮的 action;

    第二种:继承 UIBarButtonItem ,添加 block 属性,然后通过 block 来添加按钮的 action;

    这几种解决方案在 Objective-C 中并不陌生,即使是在 Swift 中,使用方式也大同小异。根据项目需求,可任意选择其中一种,并没有一定的写法。

    如果项目中有很多 UIBarButtonItem 需要使用到 runtime 绑定的方式,那么这里也直接用这种方式就行。当然,也有可能会觉得,Swift 在有意的弱化 runtime,再大量使用或许不是很优雅,那么选择第二种方案也是不错的。

    第一种(runtime):

    首先给 UIBarButtonItem 添加 block。

    typealias STBarButtonItemBlock = () -> ()

    class STBarButtonItemWrapper {
    var block: STBarButtonItemBlock?
    init(block: STBarButtonItemBlock) {
    self.block = block
    }
    }

    struct STConst {
    static var STBarButtonItemWrapperKey = "STBarButtonItemWrapperKey"
    }

    extension UIBarButtonItem {
    convenience init(title: String?, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) {
    self.init()
    self.title = title
    self.style = style
    target = self
    action = "buttonTapped"
    let wrapper = STBarButtonItemWrapper(block: block)
    objc_setAssociatedObject(self, &STConst.STBarButtonItemWrapperKey, wrapper, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }

    func buttonTapped() {
    guard let wrapper = objc_getAssociatedObject(self, &STConst.STBarButtonItemWrapperKey) as? STBarButtonItemWrapper else {
    return
    }
    guard let block = wrapper.block else {
    return
    }
    block()
    }
    }

    需要注意的是,objc_setAssociatedObject 的第三个参数要是对象。所以需要将 block 放入对象中,然后使用runtime 将该对象添加为 UIBarButtonItem 成员变量。

    然后,在 protocol 的默认实现中,将 needBurgerButton() 方法实现改为:

    extension BurgerButtonManager where Self: UIViewController {
    func needBurgerButton() {
    let burgerButton = UIBarButtonItem(title: "菜单", style: .Plain) {
    print("菜单显示")
    }
    self.navigationItem.leftBarButtonItem = burgerButton
    }
    }

    第二种(继承):

    相比 runtime,继承的实现就简单多了。首先给继承类添加便利构造方法,用于绑定 block:

    typealias STBarButtonItemBlock = ()->()

    class BlockBarButtonItem: UIBarButtonItem {
    var block: STBarButtonItemBlock?
    convenience init(title: String, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) {
    self.init(title: title, style: style, target: nil, action: "buttonTapped")
    self.target = self
    self.block = block
    }

    convenience init(image: UIImage, style: UIBarButtonItemStyle, block: STBarButtonItemBlock) {
    self.init(image: image, style: style, target: nil, action: "buttonTapped")
    self.target = self
    self.block = block
    }

    func buttonTapped() {
    guard let block = block else {
    return
    }
    block()
    }
    }

    然后,将默认实现的 needBurgerButton() 方法实现改为:

    extension BurgerButtonManager where Self: UIViewController {
    func needBurgerButton() {
    let item = BlockBarButtonItem(title: "菜单", style: .Plain) {
    print("菜单显示")
    }
    self.navigationItem.leftBarButtonItem = item
    }
    }

    最后

    完整的 Demo 已上传 Github。在完成过程中,也和翻译组的小伙伴进行了讨论。感谢 @ray16897188@小锅@靛青

    One More Thing

    在选择绑定 action 的过程中,我也尝试过在 extension 中绑定,有解决方案说在 func 前面加 @objc,但是这并不凑效。详细描述可参见 Apple Thread 16773

    当然,欢迎评论。

    参考

    Mixins 比继承更好

    Github : Swift2-Protocol-Extension-Example

    iOS 9 Tutorial Series: Protocol-Oriented Programming with UIKit

    如果你还在用子类(Subclassing),那就不对了