天天看點

IOS開發中常用的設計模式

說起設計模式,感覺自己把握不了筆頭,是以單拿出iOS開發中的幾種常用設計模式談一下。

單例模式(Singleton)

概念:整個應用或系統隻能有該類的一個執行個體。

在iOS開發我們經常碰到隻需要某類一個執行個體的情況,最常見的莫過于對硬體參數的通路類,比如UIAccelerometer.這個類可以幫助我們獲得硬體在各個方向軸上的加速度,但是我們僅僅需要它的一個執行個體就夠了,再多,隻會浪費記憶體。

是以蘋果提供了一個UIAccelerometer的執行個體化方法+sharedAccelerometer,從名字上我們也能看出此方法讓整個應用共享一個UIAccelerometer執行個體(PS:iOS 的開放中,我們往往能從方法名中就了解這個方法的作用),它内部的如何實作我們暫且不談,先來看看還有哪些類同樣使用了單例模式。

● UIApplication類提供了 +sharedAPplication方法建立和擷取UIApplication單例

● NSBundle類提供了 +mainBunle方法擷取NSBundle單例

● NSFileManager類提供了 +defaultManager方法建立和獲得NSFileManager單例。(PS:有些時候我們得放棄使用單例模式,使用-init方法去實作一個新的執行個體,比如使用委托時)

● NSNotificationCenter提供了 +defaultCenter方法建立和擷取NSNotificationCenter單例(PS:該類還遵循了另一個重要的設計模式:觀察者模式)

● NSUserDefaults類提供了 +defaultUserDefaults方法去建立和擷取NSUserDefaults單例

蘋果的SDK中大量的遵循此設計模式,那麼它的内部是如何實作的呢?

首先給大家介紹一下GCD技術,是蘋果針對于多核CPU的多任務解決方案。你不需要了解更多,隻需要知道是一組基于C語言開發的API。GCD提供了一個dispatch_once函數,這個函數的作用就是保證block(代碼塊:暫時了解為一個跟函數相近的東西)裡的語句在整個應用的生命周期裡隻執行一次。

OK,接下來就給出一個使用了單例模式建立和擷取執行個體的類模版,代碼如下:

<a target="_blank" href="http://www.devstore.cn/new/newInfo/935.html#">?</a>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<code>//Singleton.h</code>

<code>@interface Singleton : NSObject</code>

<code>+ (Singleton *)sharedSingleton; &lt;1&gt;</code>

<code>@end</code>

<code> </code> 

<code>/***************************************************************/</code>

<code>//Singleton.m</code>

<code>#import "Singleton.h"</code>

<code>@implementation Singleton   </code>

<code>static</code> <code>Singleton *sharedSingleton = nil;&lt;2&gt;</code>

<code>+ (Singleton *)sharedSingleton{</code>

<code>    </code><code>static</code> <code>dispatch_once_t once;&lt;3&gt;</code>

<code>    </code><code>dispatch_once(&amp;once,^{</code>

<code>        </code><code>sharedSingleton = [[self alloc] init];&lt;4&gt;</code>

<code>        </code><code>//dosometing</code>

<code>    </code><code>});</code>

<code>    </code><code>return</code> <code>sharedSingleton;&lt;5&gt;</code>

<code>}</code>

上述代碼中有5小步,解釋如下:

1. 聲明一個可以建立和擷取單個執行個體對象的方法

2. 聲明一個static類型的類變量

3. 聲明一個隻執行一次的任務

4. 調用dispatch_once執行該任務指定的代碼塊,在該代碼塊中執行個體化上文聲明的類變量

5. 傳回在整個應用的生命周期中隻會被執行個體化一次的變量

OK,這就是iOS開發中單例模式的機制,下面我們就看看如何在實際開發中使用此模式?(PS:為了盡可能的突出核心内容,我們會對設計中的其他模式或内容一筆帶過)

假如我們需要在iOS應用中實作分層的架構設計,即我們需要把資料的持久層,展示層,和邏輯層分開。為了突出重點,我們直接把目光轉到持久層,而根據MVC的設計模式,我們又可以把持久層細分為DAO層(放置通路資料對象的四類方法)和Domain層(各種實體類,比如學生),這樣就可以使用DAO層中的方法,配合實體類Domain層對資料進行清晰的增删改查。那麼我們如何設計呢?

從使用者的角度看,我們期望獲得DAO層的類執行個體,然後調用它的增删改查四大方法。可是這個類執行個體,我們似乎隻需要一個就足夠了,再多的話不利于管理且浪費記憶體。OK,我們可以使用單例模式了,代碼如下:

.h檔案:

<code>//StudentDAO.h</code>

<code>@interface StudentDAO:NSObject</code>

<code>@property (nonatomic,strong) NSMutaleArray *StudentsInfo;</code>

<code>+ (StudentDAO *)sharedStudentDAO;</code>

<code>-(</code><code>int</code><code>) create:(Student*)student;</code>

<code>-(</code><code>int</code><code>) </code><code>remove</code><code>:(Student*)student;</code>

<code>-(</code><code>int</code><code>) modify:(Student*)student;</code>

<code>-(NSMutaleArray) findAll;</code>

.m檔案:

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

<code>//StudentDAO.m</code>

<code>#import "StudentDAO.h"</code>

<code>#import "Student.h"</code>

<code>@implementation StudentDAO</code>

<code>static</code> <code>StudentDAO *studentDao = nil;</code>

<code>+ (StudentDAO)sharedStudentDAO{</code>

<code>    </code><code>static</code> <code>dispatch_once_t once;</code>

<code>        </code><code>Student  *student1 = [[Student alloc]init];</code>

<code>        </code><code>student1.name = </code><code>"MexiQQ"</code><code>;</code>

<code>        </code><code>student1.studentNum = </code><code>"201200301101"</code><code>;</code>

<code>        </code><code>Student  *student2 = [[Student alloc]init];</code>

<code>        </code><code>student2.name = </code><code>"Ricardo_LI"</code><code>;</code>

<code>        </code><code>student2.studentNum = </code><code>"201200301102"</code><code>;</code>

<code>        </code><code>studentDao = [[self alloc] init];</code>

<code>        </code><code>studentDao._StudentsInfo = [[NSMutaleArray alloc]init];</code>

<code>        </code><code>[studentDao._StudentsInfo addObject:student1];</code>

<code>        </code><code>[studentDao._StudentsInfo addObject:student2];</code>

<code>    </code><code>return</code> <code>studentDao;</code>

<code>}   </code>

<code>//插入的方法</code>

<code>-(</code><code>int</code><code>)create:(Student*)stu{</code>

<code>    </code><code>[self._StudentsInfo addObject:stu];</code>

<code>    </code><code>return</code> <code>0;</code>

<code>//删除的方法</code>

<code>-(</code><code>int</code><code>)</code><code>remove</code><code>:(Student*)stu{</code>

<code>    </code><code>for</code><code>(Student* s in self._StudentsInfo){</code>

<code>        </code><code>if</code><code>([stu.studentNum isEqual:s.studentNum]){</code>

<code>            </code><code>[self._StudentsInfo removeObject:s]</code>

<code>            </code><code>break</code><code>;</code>

<code>        </code><code>}</code>

<code>    </code><code>}</code>

<code>-(</code><code>int</code><code>)modify...... </code><code>//省略不寫</code>

<code>-(NSMutaleArray)findAll...... </code><code>//省略不寫</code>

上述例子不難了解,其中用到的Student類我這裡就不給出了,隻是一個含有姓名和學号屬性的實體類。

觀察者模式

概念:一個對象狀态改變,通知正在對他進行觀察的對象,這些對象根據各自要求做出相應的改變。

圖例:

IOS開發中常用的設計模式

如圖所示:操作對象向被觀察者對象投送消息,使得被觀察者的狀态得以改變,在此之前已經有觀察者向被觀察對象注冊,訂閱它的廣播,現在被觀察對象将自己狀态發生改變的消息廣播出來,觀察者接收到消息各自做出應變。

OK,我們先來看看在蘋果的Cocoa Touch架構中有誰使用了觀察者模式:

通知(notification)機制

原理圖如下:

IOS開發中常用的設計模式

如圖所示,在通知機制中對某個通知感興趣的所有對象都可以成為接受者。首先,這些對象需要向通知中心(NSNotificationCenter)發出addObserver:selector:name:object:消息進行注冊,在投送對象投送通知送給通知中心時,通知中心就會把通知廣播給注冊過的接受者。所有的接受者不知道通知是誰投送的,不去關心它的細節。投送對象和接受者是一對多的關系。接受者如果對通知不再關注,會給通知中心發送removeObserver:name:Object:消息解除注冊,以後不再接受通知。

(ps:這段話内容摘抄自關東升先生的文章)

OK,我們試着去使用一下通知機制:

建立一個Single view Project,對項目中的檔案做以下修改:

AppDelegate.m

<code>- (</code><code>void</code><code>)applicationDidEnterBackground:(UIApplication *)application {</code>

<code>    </code><code>[[NSNotificationCenter defaultCenter]postNotificationName:@</code><code>"APPTerminate"</code> <code>object:self];</code>

ViewController.m

<code>//</code>

<code>//  ViewController.m</code>

<code>//  TestNotification</code>

<code>//  Created by liwenqian on 14-10-18.</code>

<code>//  Copyright (c) 2014年 liwenqian. All rights reserved.</code>

<code>#import "ViewController.h"</code>

<code>@interface ViewController ()</code>

<code>@implementation ViewController</code>

<code>- (</code><code>void</code><code>)viewDidLoad {</code>

<code>    </code><code>[super viewDidLoad];</code>

<code>    </code><code>//注意此處的selector有參數,要加冒号</code>

<code>    </code><code>[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(doSomething:) name:@</code><code>"APPTerminate"</code> <code>object:nil];</code>

<code>    </code><code>// Do any additional setup after loading the view, typically from a nib.</code>

<code>- (</code><code>void</code><code>)didReceiveMemoryWarning {</code>

<code>    </code><code>[super didReceiveMemoryWarning];</code>

<code>    </code><code>[[NSNotificationCenter defaultCenter]removeObserver:self];</code>

<code>    </code><code>// Dispose of any resources that can be recreated.</code>

<code>#pragma mark -處理通知</code>

<code>-(</code><code>void</code><code>)doSomething:(NSNotification*)notification{</code>

<code>    </code><code>NSLog(@</code><code>"收到通知"</code><code>);</code>

如上所示,對模版項目的兩個檔案的方法或整個檔案做出修改,Command+R運作你的APP,再按下Home鍵(Command+H),會發現列印出一行收到通知的文字,如下:

IOS開發中常用的設計模式

在APP退到背景時,發出廣播,而viewController因為時觀察者,收到廣播,執行doSomething方法,列印出收到廣播的文字。

KVO(Key-Value-Observing)機制

IOS開發中常用的設計模式

如圖所示:該機制下觀察者的注冊是在被觀察者的内部進行的,不同于通知機制(由觀察者自己注冊),需要被觀察者和觀察者同時實作一個協定:NSKeyValueObserving,被觀察者通過addObserver:forKeypath:options:context方法注冊觀察者,以及要被觀察的屬性。

建立一個single view project,同時建立一個繼承自NSObject的TestWatche類,檔案結構如下圖:

IOS開發中常用的設計模式

對檔案進行如下修改:

AppDelegate.h

<code>//  AppDelegate.h</code>

<code>#import &lt;UIKit/UIKit.h&gt;</code>

<code>#import &lt;CoreData/CoreData.h&gt;</code>

<code>#import "TestWatche.h"</code>

<code>@interface AppDelegate : UIResponder &lt;UIApplicationDelegate&gt;</code>

<code>@property (strong, nonatomic) UIWindow *window;</code>

<code>@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;</code>

<code>@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;</code>

<code>@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;</code>

<code>@property (strong,nonatomic) NSString *state;</code>

<code>@property (strong,nonatomic) TestWatche *watcher;</code>

<code>- (</code><code>void</code><code>)saveContext;</code>

<code>- (NSURL *)applicationDocumentsDirectory;</code>

AppDelegate.m 對如下方法做出修改

<code>- (</code><code>BOOL</code><code>)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    </code><code>// Override point for customization after application launch.</code>

<code>    </code><code>self.watcher = [TestWatche alloc];</code>

<code>    </code><code>[self addObserver:self.watcher forKeyPath:@</code><code>"state"</code> <code>options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@</code><code>"pass content"</code><code>];    self.state = @</code><code>"launch"</code><code>;    </code><code>return</code> <code>YES;</code>

<code>- (</code><code>void</code><code>)applicationDidEnterBackground:(UIApplication *)application {    self.state = @</code><code>"backgroud"</code><code>;</code>

TestWatche.m(由于繼承自NSObject,而NSObject已實作了NSKeyValueObserving協定,是以不需要做聲明)

<code>//  TestWatche.m</code>

<code>@implementation TestWatche</code>

<code>-(</code><code>void</code><code>)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(</code><code>void</code> <code>*)context{</code>

<code>    </code><code>NSLog(@</code><code>"state change:%@"</code><code>,change);</code>

OK,Command+B Command+R Command+H看看你的應用輸出了什麼,如果你的操作無誤的話,會顯示如下内容:

IOS開發中常用的設計模式

委托模式

個人認為委托模式大多數人解釋的複雜了,其實就像是java中的接口,類可以實作或不實作協定(接口)中的方法。通過此種方式,達到最大的解耦目的,友善項目的擴充。不過你需要設定應用的委托對象,以确定協定中的方法為誰服務。

拿最常用的UITableViewDelegate UITableViewDataSource來舉例:

實作一個頁面有一個UItableView,UItableView的每一欄(cell)的資料由我們指定,那麼我們該如何做呢?蘋果也自然想到了這一點,于是定義了一個接口,這個接口有許多的方法,隻需要我們把要服務的對象傳進去,就可以使用這些方法了,這個接口就是委托和協定。而UITableViewDelegate 和 UITableViewDataSource 就是專為UITableView而寫的委托和協定。用法如下:

ViewController.h

<code>//  ViewController.h</code>

<code>//  RecipeBookMe</code>

<code>//  Created by liwenqian on 14-9-10.</code>

<code>@interface ViewController : UIViewController &lt;UITableViewDelegate, UITableViewDataSource&gt;</code>

<code>@property (nonatomic, strong) IBOutlet UITableView *mytableView;</code>

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

<code>#import "DetailViewController.h"</code>

<code>#import "Recipe.h"</code>

<code>#import "RecipeTableCellTableViewCell.h"</code>

<code>@implementation ViewController{</code>

<code>     </code><code>NSArray *recipes;      </code>

<code>@synthesize mytableView;</code>

<code>    </code><code>// Initialize the recipes array</code>

<code>    </code><code>Recipe *recipe1 = [Recipe </code><code>new</code><code>];</code>

<code>    </code><code>recipe1.name = @</code><code>"Egg Benedict"</code><code>;</code>

<code>    </code><code>recipe1.prepTime = @</code><code>"30 min"</code><code>;</code>

<code>    </code><code>recipe1.image = @</code><code>"egg_benedict.jpg"</code><code>;</code>

<code>    </code><code>recipe1.ingredients = [NSArray arrayWithObjects:@</code><code>"2 fresh English muffins"</code><code>, @</code><code>"4 eggs"</code><code>, @</code><code>"4 rashers of back bacon"</code><code>, @</code><code>"2 egg yolks"</code><code>, @</code><code>"1 tbsp of lemon juice"</code><code>, @</code><code>"125 g of butter"</code><code>, @</code><code>"salt and pepper"</code><code>, nil];</code>

<code>    </code><code>Recipe *recipe2 = [Recipe </code><code>new</code><code>];</code>

<code>    </code><code>recipe2.name = @</code><code>"Mushroom Risotto"</code><code>;</code>

<code>    </code><code>recipe2.prepTime = @</code><code>"30 min"</code><code>;</code>

<code>    </code><code>recipe2.image = @</code><code>"mushroom_risotto.jpg"</code><code>;</code>

<code>    </code><code>recipe2.ingredients = [NSArray arrayWithObjects:@</code><code>"1 tbsp dried porcini mushrooms"</code><code>, @</code><code>"2 tbsp olive oil"</code><code>, @</code><code>"1 onion, chopped"</code><code>, @</code><code>"2 garlic cloves"</code><code>, @</code><code>"350g/12oz arborio rice"</code><code>, @</code><code>"1.2 litres/2 pints hot vegetable stock"</code><code>, @</code><code>"salt and pepper"</code><code>, @</code><code>"25g/1oz butter"</code><code>, nil]; </code>

<code>    </code><code>recipes = [NSArray arrayWithObjects:recipe1, recipe2, nil];</code>

<code>/*--------------------------------------------------------------------*/</code>

<code>//定義UITableview的欄目數量</code>

<code>- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section</code>

<code>{</code>

<code>     </code><code>return</code> <code>[recipes count];</code>

<code>//定義UITableviewCell的顯示内容</code>

<code>- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath</code>

<code>    </code><code>static</code> <code>NSString *CoustomerTableIdentifier = @</code><code>"RecipeTableCellTableViewCell"</code><code>;</code>

<code>    </code><code>RecipeTableCellTableViewCell *cell =(RecipeTableCellTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CoustomerTableIdentifier];</code>

<code>    </code><code>if</code> <code>(cell == nil) {</code>

<code>       </code><code>cell = [[RecipeTableCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CoustomerTableIdentifier];</code>

<code>    </code><code>recipe = [recipes objectAtIndex:indexPath.row];</code>

<code>    </code><code>cell.nameLabel.text =  recipe.name;</code>

<code>    </code><code>cell.prepTimeLabel.text= recipe.prepTime;</code>

<code>    </code><code>cell.thumbnailImageView.image = [UIImage imageNamed:recipe.image];</code>

<code>    </code><code>return</code> <code>cell;</code>

<code>//點選每一欄執行跳轉時的處理</code>

<code>- (</code><code>void</code><code>)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {</code>

<code>    </code><code>if</code> <code>([segue.identifier isEqualToString:@</code><code>"showRecipeDetail"</code><code>]) {</code>

<code>        </code><code>NSIndexPath *indexPath = nil;</code>

<code>        </code><code>Recipe *recipe = nil;</code>

<code>        </code><code>indexPath = [self.mytableView indexPathForSelectedRow];</code>

<code>        </code><code>recipe = [recipes objectAtIndex:indexPath.row];</code>

<code>        </code><code>DetailViewController *destViewController = segue.destinationViewController;</code>

<code>        </code><code>destViewController.recipe = [recipes objectAtIndex:indexPath.row];</code>

<code>//定義cell的高度</code>

<code>- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath</code>

<code>    </code><code>return</code> <code>71;</code>

如上所示,兩條/------/注釋間的方法全部來自于委托和協定。利用委托和協定,你可以把主要精力放到邏輯業務上,将資料綁定和事件處理交給委托和協定去完成。

繼續閱讀