說起設計模式,感覺自己把握不了筆頭,是以單拿出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; <1></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;<2></code>
<code>+ (Singleton *)sharedSingleton{</code>
<code> </code><code>static</code> <code>dispatch_once_t once;<3></code>
<code> </code><code>dispatch_once(&once,^{</code>
<code> </code><code>sharedSingleton = [[self alloc] init];<4></code>
<code> </code><code>//dosometing</code>
<code> </code><code>});</code>
<code> </code><code>return</code> <code>sharedSingleton;<5></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類我這裡就不給出了,隻是一個含有姓名和學号屬性的實體類。
觀察者模式
概念:一個對象狀态改變,通知正在對他進行觀察的對象,這些對象根據各自要求做出相應的改變。
圖例:
如圖所示:操作對象向被觀察者對象投送消息,使得被觀察者的狀态得以改變,在此之前已經有觀察者向被觀察對象注冊,訂閱它的廣播,現在被觀察對象将自己狀态發生改變的消息廣播出來,觀察者接收到消息各自做出應變。
OK,我們先來看看在蘋果的Cocoa Touch架構中有誰使用了觀察者模式:
通知(notification)機制
原理圖如下:
如圖所示,在通知機制中對某個通知感興趣的所有對象都可以成為接受者。首先,這些對象需要向通知中心(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),會發現列印出一行收到通知的文字,如下:
在APP退到背景時,發出廣播,而viewController因為時觀察者,收到廣播,執行doSomething方法,列印出收到廣播的文字。
KVO(Key-Value-Observing)機制
如圖所示:該機制下觀察者的注冊是在被觀察者的内部進行的,不同于通知機制(由觀察者自己注冊),需要被觀察者和觀察者同時實作一個協定:NSKeyValueObserving,被觀察者通過addObserver:forKeypath:options:context方法注冊觀察者,以及要被觀察的屬性。
建立一個single view project,同時建立一個繼承自NSObject的TestWatche類,檔案結構如下圖:
對檔案進行如下修改:
AppDelegate.h
<code>// AppDelegate.h</code>
<code>#import <UIKit/UIKit.h></code>
<code>#import <CoreData/CoreData.h></code>
<code>#import "TestWatche.h"</code>
<code>@interface AppDelegate : UIResponder <UIApplicationDelegate></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看看你的應用輸出了什麼,如果你的操作無誤的話,會顯示如下内容:
委托模式
個人認為委托模式大多數人解釋的複雜了,其實就像是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 <UITableViewDelegate, UITableViewDataSource></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>
如上所示,兩條/------/注釋間的方法全部來自于委托和協定。利用委托和協定,你可以把主要精力放到邏輯業務上,将資料綁定和事件處理交給委托和協定去完成。