天天看點

iOS開發技巧系列---使用鍊式程式設計和Block來實作UIAlertView

UIAlertView是iOS開發過程中最常用的控件之一,是提醒使用者做出選擇最主要的工具.在iOS8及後來的系統中,蘋果更推薦使用UIAlertController來代替UIAlertView.是以本文也并不提倡開發者再使用UIAlertView,

本文的目的是探讨如何将原來的給變量指派和通過Delete來回調的方式變成鍊式程式設計風格和通過Block來回調.通過學習對UIAlertView的改造讓各位iOS開發者能夠學會這種更加便捷的開發方式

什麼是鍊式程式設計

對于有一定開發經驗的開發者來說,鍊式程式設計并不陌生.有很多知名的開源庫使用了這種程式設計風格,比如大名鼎鼎的JQuery,而iOS庫中,Masory和Snapkit也是典型的使用鍊式程式設計的例子.

大體來說,鍊式程式設計就是将多個操作(多行代碼)通過某種操作符(通常是點号.)連結成一句的代碼.便代碼更加緊湊,可讀性也更好,降低了代碼的重複度.比如以下代碼:

1 //使用普通的指派模式
2 txtUserName.placeHolder = "請輸入使用者名"
3 txtUserName.font = UIFont.systemFont(15)
4 txtUserName.textColor = UIColor.GrayColor()
5 //使用鍊式程式設計的語句
6 txtUserName.setPlaceHolder("請輸入使用者名").setFont(15).setTextColor(UIColor.GrayColor())      

通過這個例子讀者應該可以更清楚的了解什麼是鍊式程式設計風格.簡單來說,鍊式程式設計風格要有以下特點

  • 通常是都是調用一個函數來給屬性指派.也就是說,該函數封裝了指派的語句,還可以在裡面加入自己的判斷和一些邏輯.
  • 該函數必須有一個傳回值,通常是它本身.也可以是處理後的資料或者對象.
  • 可以用靜态函數作為起始函數,但是後面的全是執行個體函數.
  • 可以設定一個最終函數,該函數不傳回任何對象,用它來完全最後所有操作.

鍊式程式設計的利弊

使用鍊式程式設計最主要的好處是可以使代碼更簡潔,寫起來一種"爽快"感.設計優秀的鍊式程式設計可以大大降低重複的代碼,增強邏輯感.不足之處就是對開

發者的業務邏輯能力要求較高,同時因為鍊式程式設計都是調用函數,是以有可能會造成過深的函數調用棧.稍微影響性能.

使用Block來回調UIAlertView的Delegate

iOS開發中有很多回調都是使用Delegate完成的,相信各位讀者已經寫過很多次了.相對來說,使用Delegate比較繁瑣,有時還需要在Delegate裡

判斷是哪個對象的Delegate,優點是運作效率較高.而Block則相反,寫起來更直覺,開發效率更高.蘋果也是逐漸使用Block來代替Delegate,iOS

8最新的UIViewController裡的Action已經全部使用Block來實作.而且Swift裡的閉包,匿名函數更上比比皆是.是以大膽地使用Block來代替

Delegate和Target-Action吧,蘋果會幫我們處理好各種性能問題的.但是需要注意的是retian-circle問題,初識Block很容易出現這種問題.

使用鍊式程式設計和Block來改造UIAlertView

目前有兩種方式可以實作将UIAlertView的Delegate改成Block的回調,一是使用運作時給UIAlertView加上Block屬性.二是再寫一個新的類繼承

UIAlertView,本文使用的是第二種方式,寫起來更簡單一點.

先定義一個新的類來繼承UIAlertView,并且實作UIAlertViewDelegate協定
1 class BlockAlert:UIAlertView,UIAlertViewDelegate{
 2     var completion:((buttonIndex:Int,alert:UIAlertView)->Void)? //定義各個Block用來回調,其參數名和Delegate回調的函數參數名一至
 3     var willDismissBlock:((buttonIndex:Int,alert:UIAlertView)->Void)?
 4     var didDismissBlock:((buttonIndex:Int,alert:UIAlertView)->Void)?
 5     var didPresentBlock:((alert:UIAlertView)->Void)?
 6     var willPresentBlock:((alert:UIAlertView)->Void)?
 7     var alertWithCancelBlock:((alert:UIAlertView)->Void)?
 8     override init(frame: CGRect) {
 9         super.init(frame: frame)
10         self.delegate = self //将delegate設為自己
11     }
12     func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
13         if let block = completion{ //最主要的Block,當使用者點了按鈕時回調,先要判斷這個Block存在不?
14             block(buttonIndex: buttonIndex, alert: alertView)
15         }
16     }
17     func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) {
18         if let block = didDismissBlock{//其他根據相應的Delegate回調函數依次調用各個Block
19             block(buttonIndex: buttonIndex, alert: alertView)
20         }
21     }
22     func alertView(alertView: UIAlertView, willDismissWithButtonIndex buttonIndex: Int) {
23         if let block = willDismissBlock{
24             block(buttonIndex: buttonIndex, alert: alertView)
25         }   
26     }
27     // 其他幾個委托方法也省略了,原理都是一樣的
28     required init?(coder aDecoder: NSCoder) {
29         fatalError("init(coder:) has not been implemented")
30     }
31 }      

參考上面的代碼,總體思路是在構造器裡面将委托設為自己,再将Delegate裡面所有的函數用Block來實作,Block裡面的參數和委托回調的函數參數一至.當有委托函數回調時,先判斷這個

Block是不是nil,如果不是,則執行該Block

接下來的操作就很容易了

再直接擴充UIAlertView,加上自己想要的鍊式程式設計函數
1 extension UIAlertView {
 2     static func setMessage(msg:String)->UIAlertView{//在這裡設定這個為靜态函數,在裡面執行個體化一個BlockAlert對象,再傳回該對象
 3         let alert = BlockAlert()
 4         alert.message = msg
 5         return alert
 6     }
 7 
 8     func addAlertStyle(style:UIAlertViewStyle)->UIAlertView{ //直接在各個函數中封裝一些操作.再傳回自身
 9         self.alertViewStyle = style
10         return self
11     }
12 
13     func addTitle(title:String)->UIAlertView{
14         self.title = title
15         return self
16     }
17 
18     func addFirstButton(btnTitle:String)->UIAlertView{
19         self.addButtonWithTitle(btnTitle)
20         return self
21     }
22 
23     func addSecondButton(btnTitle:String)->UIAlertView{
24         self.addButtonWithTitle(btnTitle)
25         return self
26     }
27 
28     func addButtons(btnTitles:[String])->UIAlertView{
29         for title in btnTitles{
30             self.addButtonWithTitle(title)
31         }
32         return self
33     }
34 
35     func addButtonClickEvent(clickButton:((buttonIndex:Int,alert:UIAlertView)->Void)?)->UIAlertView{
36         if let alert = self as? BlockAlert{//對于block,先要判斷它是不是BlockAlert,如果是的話才添加該block,後面都是這種操作
37             alert.completion = clickButton
38         }
39         return self
40     }
41 
42     //這裡也是省略了部分函數,因為原理都是一樣的
43     func addAlertCancelEvent(event:((alert:UIAlertView)->Void)?)->UIAlertView{
44         if let alert = self as? BlockAlert{
45             alert.alertWithCancelBlock = event
46         }
47         return self
48     }
49 
50 
51     func alertWithButtonClick(clickButton:((buttonIndex:Int,alert:UIAlertView)->Void)?){
52         if let alert = self as? BlockAlert{ //這個為終結函數,沒有傳回值,在裡面直接調用了show()方法.
53             alert.completion = clickButton
54             alert.show()
55         }
56     }
57 }      

在這裡面我選擇了用UIAlverView來擴充,而不是BlockAlert,這樣可能會導緻開發過程中踩進陷阱.因為在首個靜态函數傳回的對象是一個BlockAction對象,而不是UIAlertView對象

在後面添加Block時先要判斷本身是不是BlockAction對象,如果是的話再将設定Block,是以在開發過程中要小心這個陷阱.當然讀者也可以直接擴充BlockAlert,這樣會更安全一些.

最後直接使用靜态函數加鍊式程式設計來彈出Alert
UIAlertView.setMessage("這裡要設定資訊").addTitle("我是标題").addFirstButton("取消").addSecondButton("确認").alertWithButtonClick { (buttonIndex, alert) -> Void in
            print("你按下了第\(buttonIndex)個按鍵")
            //你按下了第1個按鍵
            //你按下了第0個按鍵
        }      

如果要使用其他的委托方法也很簡單

1    UIAlertView.setMessage("這裡要設定資訊").addTitle("我是标題").addFirstButton("取消").addSecondButton("确認").addWillPresentEvent { (alert) in
2              print("the alertView will present")
3         }.addDidPresentEvent { (alert) in
4             print("the alertView did present")
5         }.alertWithButtonClick { (buttonIndex, alert) in
6               print("你按下了第\(buttonIndex)個按鍵")
7         }      
1  UIAlertView.setMessage("這裡要設定資訊").addTitle("我是标題").addFirstButton("取消").addSecondButton("确認").addAlertStyle(.LoginAndPasswordInput).addDidPresentEvent { (alert) in
2             print("the alertView did present")
3         }.alertWithButtonClick { (buttonIndex, alert) in
4             let txt1 = alert.textFieldAtIndex(0)?.text
5             let txt2 = alert.textFieldAtIndex(1)?.text
6             print("userName:\(txt1) password:\(txt2)")
7             //列印出the alertView did present
8             //userName:Optional("111") password:Optional("222")
9         }      

帶使用者名密碼的Alert

總結

繼續閱讀