1. 程式人生 > >swift函數語言程式設計Tips (一)

swift函數語言程式設計Tips (一)

Protocol(協議)專題

demo連結–>https://github.com/PeipeiQ/MySwift
我的個人部落格->http://www.peipeiq.cn
最近在公司用swift做開發,也開始關注一些swift的語言風格,所以接下來的部落格以swift語言為主。oc或者swift有什麼問題可以一起交流。

一、委託模式

1、使用過程

  協議最常見的用法莫過於進行代理傳值,這就是委託模式。常用的應用場景有:controller中自定義了一個view,view中又添加了一個自定義view。在自定義的view中如果有些函式或者屬性需要到controller中去呼叫,委託模式的做法就是規定一個協議,讓controller去遵守一個協議並提供實現,那麼在自定義view中就能使用協議中的方法。
  舉個例子,現在想在一個controller中新增一個自定義view,可以實現點選view中按鈕更改controller中label的值。簡單的程式碼如下:
  自定義view

//SelectTabbar.swift
@objc protocol SelectTabbarDelegate {
    func changeLabel(_ str: String)
}
//SelectTabbar.swift
 class SelectTabbar: UIView {
    var keywords : [String]?
    var buttons : [UIButton]?
    weak public var delegate : SelectTabbarDelegate?

    init(frame: CGRect,keywords:[String]) {
        super.init
(frame: frame) self.keywords = keywords renderView() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() } private func renderView(){ buttons = keywords?.enumerated
().map({ (index,key) ->UIButton in let buttonWidth = kScreenWidth/CGFloat((keywords?.count)!) let button = UIButton.init(frame: CGRect.init(x: CGFloat(index)*buttonWidth, y: 0, width: buttonWidth, height: 50)) button.setTitle(key, for: .normal) button.setTitleColor(UIColor.blue, for: .normal) button.backgroundColor = UIColor.gray button.tag = index button.addTarget(self, action: #selector(tapButton(sender:)), for: .touchUpInside) addSubview(button) return button }) } @objc func tapButton(sender: UIButton){ delegate?.changeLabel(keywords![sender.tag]) } }

  controller:

class TestViewController: UIViewController,SelectTabbarDelegate {

    lazy var label : UILabel = {
        var label = UILabel(frame: CGRect.init(x: 50, y: 200, width: 100, height: 30))
        label.text = labelStr
        label.backgroundColor = UIColor.red
        return label
    }()

    private var labelStr : String? {
        didSet{
            label.text = labelStr
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        view.addSubview(label)
        setupSelectTabbar()
    }

    func setupSelectTabbar(){
        let selectTabbar = SelectTabbar(frame: CGRect.init(x: 0, y: kNavigationHeightAndStatuBarHeight, width: kScreenWidth, height: 50),keywords:["aa","bb"])
        selectTabbar.delegate = self
        view.addSubview(selectTabbar)
    }

    func changeLabel(_ str: String) {
        labelStr = str
    }

}

  這樣就能比較清楚的表明自己的邏輯。否則,如果要在view操作controller的內容,則需要在外部操作controller的例項,這就造成一個問題,就是無法操作例項中的私有屬性和私有方法(雖然iOS是一門動態語言,不存在絕對的私有,但是誰會去一直去使用runtime來進行操作呢)。
  

2、注意點

  在 ARC 中,對於一般的 delegate,我們會在宣告中將其指定為 weak,在這個 delegate 實際的物件被釋放的時候,會被重置回 nil。這可以保證即使 delegate 已經不存在時,我們也不會由於訪問到已被回收的記憶體而導致崩潰。ARC 的這個特性杜絕了 Cocoa 開發中一種非常常見的崩潰錯誤,說是救萬千程式設計師於水火之中也毫不為過。
  在 Swift 中我們當然也會希望這麼做。但是當我們嘗試書寫這樣的程式碼的時候,編譯器不會讓我們通過:

  'weak' cannot be applied to non-class type

原因:這是因為 Swift 的 protocol 是可以被除了 class 以外的其他型別遵守的,而對於像 struct 或是 enum 這樣的型別,本身就不通過引用計數來管理記憶體,所以也不可能用 weak 這樣的 ARC 的概念來進行修飾。
兩種解決方法:
1、使用@objc
2、宣告類型別專屬協議。通過新增 class 關鍵字來限制協議只能被類型別遵循,而結構體或列舉不能遵循該協議。class 關鍵字必須第一個出現在協議的繼承列表中,在其他繼承的協議之前
protocol SelectTabbarDelegate : class

二、AOP程式設計思想的運用

首先我們理解下AOP的含義。

In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns. It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a “pointcut” specification, such as “log all function calls when the function’s name begins with ‘set’”. This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code, core to the functionality. AOP forms a basis for aspect-oriented software development.

在swift簡單來說,就是利用協議去切入某些程式碼中,將額外的功能單獨出來而不產生耦合,可以將這些與主邏輯關係不大的程式碼統一放到一起。
常用的場景:日誌記錄,效能統計,安全控制,事務處理,異常處理等等。
接上面的例子,我們需要在開啟TestViewController的時候統計一次,點選兩個按鈕的時候也進行統計,統計的內容由identifer進行區分。
我們先建立一個Statistician.swift 來存放我們的統計邏輯。(模擬實現)
申明一個StatisticianProtocal協議並提供他的預設實現。

import Foundation
enum LogIdentifer:String {
    case button1 = "button1"
    case button2 = "button2"
    case testViewController = "testViewController"
}

protocol StatisticianProtocal {
    func statisticianLog(fromClass:AnyObject, identifer:LogIdentifer)
    func statisticianUpload(fromClass:AnyObject, identifer:LogIdentifer)
    //用一個尾隨閉包來擴充套件功能
    func statisticianExtension(fromClass:AnyObject, identifer:LogIdentifer, extra:()->())
}

extension StatisticianProtocal{
    func statisticianLog(fromClass:AnyObject, identifer:LogIdentifer) {
        print("statisticianLog--class:\(fromClass) from:\(identifer.rawValue)")
    }

    func statisticianUpload(fromClass:AnyObject, identifer:LogIdentifer) {
        print("statisticianUpload--class:\(fromClass) from:\(identifer.rawValue)")
    }

    func statisticianExtension(fromClass:AnyObject, identifer:LogIdentifer, extra:()->()){
        extra()
    }
}

class Statistician: NSObject {

}

接下來在任何需要統計的類裡面,我們讓這個類去遵守這個協議,然後在需要的地方呼叫協議中的方法即可。如果在某個特定的類中需要呼叫的方法略有不同,重寫協議中的方法即可。

class SelectTabbar: UIView,StatisticianProtocal {
    var keywords : [String]?
    var buttons : [UIButton]?
    weak public var delegate : SelectTabbarDelegate?

    init(frame: CGRect,keywords:[String]) {
        super.init(frame: frame)
        self.keywords = keywords
        renderView()
        //進行一次統計
        operateStatistician(identifer: .testViewController)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    override func layoutSubviews() {
        super.layoutSubviews()
    }

    private func renderView(){
        buttons = keywords?.enumerated().map({ (index,key) ->UIButton in
            let buttonWidth = kScreenWidth/CGFloat((keywords?.count)!)
            let button = UIButton.init(frame: CGRect.init(x: CGFloat(index)*buttonWidth, y: 0, width: buttonWidth, height: 50))
            button.setTitle(key, for: .normal)
            button.setTitleColor(UIColor.blue, for: .normal)
            button.backgroundColor = UIColor.gray
            button.tag = index
            button.addTarget(self, action: #selector(tapButton(sender:)), for: .touchUpInside)
            addSubview(button)
            return button
        })
    }

    @objc func tapButton(sender: UIButton){
        //進行一次統計
        switch sender.tag {
          case 0:operateStatistician(identifer: .button1)
          default:operateStatistician(identifer: .button2)
        }
        delegate?.changeLabel(keywords![sender.tag])
    }

    func operateStatistician(identifer:LogIdentifer){
        statisticianLog(fromClass: self, identifer: identifer)
        statisticianUpload(fromClass: self, identifer: identifer)
        statisticianExtension(fromClass: self, identifer: identifer) {
            print("extra: in SelectTabbar class")
        }
    }

}

以上程式碼實現了三處統計的邏輯,而不用把統計的邏輯寫入controller檔案中,降低了功能上的耦合度。

三、用來代替extension,增強程式碼可讀性

  使用擴充套件,可以很方便的為一些繼承它的子類增添一些函式。這就帶來一個問題,就是所有的子類都擁有了這個方法,但是方法的本身可能不明確,或者是隻是想讓少數子類來使用這個方法。這時候可以使用協議來代替extension。

//定義了一個Shakable協議,遵守這個協議的類即可使用裡面的方法,併為該方法提供一個預設的實現
//where Self:UIView表明了只有uiview的子類可以遵守這個協議
protocol Shakable {
    func shakeView()
}

extension Shakable where Self:UIView{
    func shakeView(){
        print(Self.self)
    }
}

這時候可以讓某個子類來遵守協議。例如剛才上面的例子。

class SelectTabbar: UIView,Shakable

  如果不在類中重新實現這個方法,則可以實現預設的方法。這個意思表明,SelectTabbar類的子類是遵守Shakable協議的,間接等於SelectTabbar():Shakable?。這樣我們就可以愉快的讓SelectTabbar物件去使用這個方法。(Self關鍵字只能用在協議或者類中,表示當前類,可作為返回值使用)。
  一旦不想讓某個子類使用shakeView()方法,很簡單,只要把class SelectTabbar: UIView,Shakable中的Shakable協議幹掉即可。
  其他實踐:
  利用AOP去分離tableview的資料來源和事件源的方法,可以單獨處理裡面的邏輯,使tableview的代理方法不顯得那麼冗餘。
  

總結

關於協議,還有很多種用法。以上是目前比較常用的場景。日後開發中如果發現協議在其他地方中有更好的應該,將會更新本文。