swift干货iOS技术笔记收录

Swift 面向Protocol编程浅析:Inheritence

2016-01-21  本文已影响638人  TommyYaphetS

记得从大学学编程开始,对于软件编程听的最多的就是面向对象编程(Object Oriented Programming,OOP)了,它的三大特征:封装,继承,多态.而Swift倡导的面向协议编程(Protocol-oriented programming,POP)是OOP的一个范例,我理解为"封装+协议*结构体+扩展"(Swift2.0开始,你可以扩展一个protocol)

WWDC:Protocol-Oriented Programming in Swift开头Classess Are Awesome,指出OOP中的Classes提供了:数据的封装、访问控制、抽象化、命名空间等,但这些都是Classes才特有的属性吗?事实上,这些都是类型(Type)的所有属性,Classes只是Type的一种实现方法,在Swift中I can do all that with structs and enums(Swift的标准库组成:55个Protocols和102个Structs),这一点可以理解为封装性.而继承和多态,是structenum不具备的,它则是通过遵守protocol来实现.

但,这些是为了说明让我们放弃OOP吗?这是不可能的....想想UIKit....记得刚开始用Swift写项目时,总是告诫自己,不能只是机械的把objc 翻译成 swift.<font color="IndianRed">实际开发项目中,ViewControlView基本都是使用系统的框架,通过继承来实现,无论如何的自定义,都是要围绕苹果的那一套来,OC与swift在这一块保持一致;但在modelhandle/viewModel/manager这一块,更多的通过POP实现,后面会通过一个例子来说明.</font>(PS:在oc中,我们体会的是OOP/FRP(参考一下RAC),那Swift就是OOP/FRP(参考一下RxSwift)/POP;在oc中对于Protocols的理解更多的是UIAppplicationDelegate,UITableViewDelegete,NSCopying,UITextFieldDelegate....,而Swift中Protocols则被赋予了更多的功能和意义:"可定义属性,可组合,可继承,可扩展,支持泛型,支持类/结构体/枚举").在Swift面向协议编程初探中,bz总结的一句话,非常nice:
面向对象编程和面向协议编程最明显的区别在于程序设计过程中对数据类型的抽取(抽象)上,面向对象编程使用类和继承的手段,数据类型是引用类型
面向协议编程使用的是遵守协议的手段,数据类型是值类型(Swift中的结构体或枚举)
PS:值类型引用类型的区别这里不作详叙,可参考Swift:什么时候使用结构体和类

讨厌的"上帝"(Inheritence)

继承带给我们最大的问题,可能就是常常会构造出所谓的God类/super类,带来的坏处也随之可见:

// 父类
class Animal {
    var name: String = ""
    var type: String = ""
    func eat(){} 
//    func fly(){}
}

class Bird: Animal {
    func fly(){
        print("Bird can fly")
    }
}

class Preson: Animal {
    func speak(){
        print("person can speak")
    }
}

class Fish: Animal {
    func swimming() {
        print("fish can swimming")
    }
}

// 假设超人会飞不会游泳,复制飞的方法
class SuperMan: Preson {
    override func speak() {
        print("superman also speak")
    }
    func fly(){
        print("superman also fly")
    }
}

class SuperFishMan: SuperMan {
    func swimming() {
        print("superfishman can swimming")
    }
}

有句话是咋说的:我们区分鸟和鱼,不是因为它们的名字是鸟/鱼,而是通过它们表现的行为,有点乱,_.把所有的行为拆分出来,通过搭积木的形式组合出来,你具备什么就拿什么,那么你的身份也就随之浮现了.

protocol Property {
    var name: String {get}
    var type: String {get}
}

extension Property {
    var name: String {
        return "超人"
    }
    var type: String {
        return "外星类"
    }
}

protocol Speaker {
    func speak()
}

protocol Flyer {
    func fly()
}

protocol Swimer {
    func swimming()
}

struct SuperMan {
}

extension SuperMan: Property,Flyer,Speaker {
    func fly() {
        print("superman also fly")
    }
    func speak() {
        print("superman also speak")
    }
}

struct SuperFishMan {
}

extension SuperFishMan: Property,Flyer,Speaker,Swimer {
    var name: String {
        return "超水人"// 好蠢的名字...
    }
    func fly() {
        print("...")
    }
    func speak() {
        print("...")
        
    }
    func swimming() {
        print("....")
    }
}

组合(Composition),哎哟不错
第一个例子属于对于model定义,接下来看一个view层所表现出的问题.
手机QQ底部tabbar的三个标签首页都带有一个头像控件,最开始我们采取继承的形式来实现一个baseVC

class KQUserAvatarView: UIView {
}

class KQBaseViewController: UIViewController {
    var userAvatarView: KQUserAvatarView!

    func setupUserAvatarView() {
    }
    
    func clickOnAvatarView() {
    }
}

在objc/Swift1.2之前的,我们用组合来代替继承,这是非常常见的一种做法.借助中间件,解耦+转移逻辑代码,减轻VC的负担.

class KQUserAvatarView: UIView {
    var btn: UIButton!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

typealias ClickButtonAction = () -> ()
class KQUserAvatarViewManager {
    var userAvatarView: KQUserAvatarView!
    var tapHandle: ClickButtonAction?
    
    func setupUserAvatarViewAtContainView(view: UIView,tapHandle: ClickButtonAction?) {
        userAvatarView = KQUserAvatarView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        userAvatarView.backgroundColor = UIColor.orangeColor()
        view.addSubview(userAvatarView)
        self.tapHandle = tapHandle
        userAvatarView.btn.addTarget(self, action: "clickOnAvatarView", forControlEvents: .TouchUpInside)
    }
    
    func clickOnAvatarView() {
        if let block = self.tapHandle {
            block()
        }
    }
}

class ViewController: UIViewController {
    var manager: KQUserAvatarViewManager!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        manager.setupUserAvatarViewAtContainView(self.view) {
            print("点击了按钮")
        }
        
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

POP的实现(Protocol)

如果说组合的缺点,调用时必须通过中间变量,管理它的创建和释放,多了一层构造(缺点是相对的,在POP之前都这样用..优点都是对比出来的)

typealias ClickButtonAction = () -> ()
class KQUserAvatarView: UIView {
    var btn: UIButton!
    var tapBlock: ClickButtonAction?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        btn = UIButton(type: .ContactAdd)
        btn.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
        self.addSubview(btn)
        
        btn.addTarget(self, action: "clickOnAvatarView", forControlEvents: .TouchUpInside)
    }
    
    func clickOnAvatarView() {
        if let blcok = tapBlock {
            blcok()
        }
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

protocol UserAvatarViewAble: class {
    var userAvatarView: KQUserAvatarView! {get set}
    func setupUserAvatarView(tapHandle: ClickButtonAction?)
}

extension UserAvatarViewAble where Self: UIViewController {
    //  扩展不能实现储存属性
    func setupUserAvatarView(tapHandle: ClickButtonAction?) {
        userAvatarView = KQUserAvatarView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        userAvatarView.backgroundColor = UIColor.orangeColor()
        self.view.addSubview(userAvatarView)
        userAvatarView.tapBlock = tapHandle
    }
}

class ViewController: UIViewController, UserAvatarViewAble {
    var userAvatarView: KQUserAvatarView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUserAvatarView {
            print("点击了按钮")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

这样看上去是不是很简洁易用?在Swift中很多场景都能通过它来实现.比如:检查手机号码,用户名的正则表达式判断..../颜色,图片的转换...等一系列的逻辑方法.
LZ之前objc项目中就存在各种:WCXxxUtil,WCXxxHandle,WCRegularUtil...
迁移到Swift中就类似Mixins and Traits in Swift 2.0写到的:

protocol ValidatesUsername {
    func isUsernameValid(password: String) -> Bool
}

extension ValidatesUsername {
    func isUsernameValid(username: String) -> Bool {
        if /* username too short */ {
            return false
        } else if /* username has invalid characters */ {
            return false
        } else {
            return true
        }
    }
}

class LoginViewController: UIViewController, ValidatesUsername, ValidatesPassword {
    @IBAction func loginButtonPressed() {
        if isUsernameValid(usernameTextField.text!) &&
            isPasswordValid(passwordTextField.text!) {
                // proceed with login
        } else {
            // show alert
        }
    }
}

protocol拆分了各种工具,extension实现默认设定,拿来即用,方便无污染.

POP在ViewModel中的体现
实现这样一个功能,写一个通讯录,要有头像和姓名-电话号码...
protocol层(不记得在哪里看到,对于协议的命令用形容词,果然IT最难的是命名...)

protocol PersonPresentAble {
    var nameTelText: String {get}
}

// 可以通过扩展提供默认实现...可用可不用
extension PersonPresentAble {
    var nameTelText: String {
        return "hehe"
    }
}

typealias TapImageViewAction = () -> ()
protocol ImagePresentAble {
    var showImage: UIImage? {get}
    var tapHandle: TapImageViewAction? {get}
}

ViewModel层

struct PersonModel {
    var firstName: String
    var lastName: String
    var fullName: String {
        return lastName + firstName
    }
    var telPhone: String
    var avatarImageUrl: String?
}

typealias TelPersonViewModelAble = protocol<PersonPresentAble,ImagePresentAble>
struct TelPersonViewModel: TelPersonViewModelAble {
    var telPerson: PersonModel
    var nameTelText: String
    var showImage: UIImage?
    var tapHandle: TapImageViewAction?
    
    init(model:PersonModel,tapHandle: TapImageViewAction?) {
        self.telPerson = model
        self.nameTelText = model.fullName + "  " + model.telPhone
        self.showImage = UIImage(named: model.avatarImageUrl!) // 暂时这样,按道理是加载url,否则没必要写到viewmodel中
        self.tapHandle = tapHandle
    }
}

View层和ViewController层

class ContactTableViewCell: UITableViewCell {
    @IBOutlet weak var telTextLabel: UILabel!
    @IBOutlet weak var avatarImageView: UIImageView!
    
    var tapHandle: TapImageViewAction?

    override func awakeFromNib() {
        super.awakeFromNib()
        let tapGesture = UITapGestureRecognizer(target: self, action: "tapAction")
        avatarImageView.addGestureRecognizer(tapGesture)
    }
    
    func configureDataWithViewModel(viewModel: TelPersonViewModelAble) {
        telTextLabel.text = viewModel.nameTelText
        avatarImageView.image = viewModel.showImage
        tapHandle = viewModel.tapHandle
    }
    
    func tapAction() {
        if let block = tapHandle {
            block()
        }
    }
}

// VC
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("hehe", forIndexPath: indexPath) as! ContactTableViewCell
    let testModel = PersonModel(firstName: "明涛", lastName: "胡", telPhone: "15279107716", avatarImageUrl: "麒麟星.jpg")
    let testViewModel = TelPersonViewModel(model: testModel) {
        print("我点击了头像")
    }
    cell.configureDataWithViewModel(testViewModel)
    return cell
}
protocol CompanyPresentAble {
    var positionText: String {get}
}

typealias InvestPersonViewModelAble = protocol<PersonPresentAble,ImagePresentAble,CompanyPresentAble>
...剩下的,你懂怎么写的^_^

参考资料:
Mixins 比继承更好
Swift中的协议编程
Introducing Protocol-Oriented Programming in Swift 2
Updated: Protocol-Oriented MVVM in Swift 2.0
Mixins and Traits in Swift 2.0
iOS应用架构谈 view层的组织和调用方案

上一篇下一篇

猜你喜欢

热点阅读