在iOS開發(fā)我們經(jīng)常碰到只需要某類一個(gè)實(shí)例的情況,最常見的莫過于對硬件參數(shù)的訪問類弯汰,比如UIAccelerometer.這個(gè)類可以幫助我們獲得硬件在各個(gè)方向軸上的加速度,但是我們僅僅需要它的一個(gè)實(shí)例就夠了锌历,再多,只會(huì)浪費(fèi)內(nèi)存树绩。
所以蘋果提供了一個(gè)UIAccelerometer的實(shí)例化方法+sharedAccelerometer,從名字上我們也能看出此方法讓整個(gè)應(yīng)用共享一個(gè)UIAccelerometer實(shí)例(PS:iOS 的開放中,我們往往能從方法名中就了解這個(gè)方法的作用)隐轩,它內(nèi)部的如何實(shí)現(xiàn)我們暫且不談饺饭,先來看看還有哪些類同樣使用了單例模式。
UIApplication類提供了 +sharedAPplication方法創(chuàng)建和獲取UIApplication單例
NSBundle類提供了 +mainBunle方法獲取NSBundle單例
NSFileManager類提供了 +defaultManager方法創(chuàng)建和獲得NSFileManager單例职车。(PS:有些時(shí)候我們得放棄使用單例模式瘫俊,使用-init方法去實(shí)現(xiàn)一個(gè)新的實(shí)例,比如使用委托時(shí))
NSNotificationCenter提供了 +defaultCenter方法創(chuàng)建和獲取NSNotificationCenter單例(PS:該類還遵循了另一個(gè)重要的設(shè)計(jì)模式:觀察者模式)
NSUserDefaults類提供了 +defaultUserDefaults方法去創(chuàng)建和獲取NSUserDefaults單例
等等悴灵,蘋果的SDK中大量的遵循此設(shè)計(jì)模式扛芽,那么它的內(nèi)部是如何實(shí)現(xiàn)的呢?
首先給大家介紹一下GCD技術(shù)称勋,是蘋果針對于多核CPU的多任務(wù)解決方案胸哥。你不需要了解更多,只需要知道是一組基于C語言開發(fā)的API赡鲜。GCD提供了一個(gè)dispatch_once函數(shù)空厌,這個(gè)函數(shù)的作用就是保證block里的語句在整個(gè)應(yīng)用的生命周期里只執(zhí)行一次。
OK银酬,接下來就給出一個(gè)使用了單例模式新建和獲取實(shí)例的類模版嘲更,代碼如下:
//Singleton.h@interfaceSingleton:NSObject+ (Singleton *)sharedSingleton; <1>@end/***************************************************************///Singleton.m#import"Singleton.h"@implementationSingletonstaticSingleton *sharedSingleton = nil;<2>+ (Singleton *)sharedSingleton{staticdispatch_once_tonce;<3>dispatch_once(&once,^{? ? ? ? sharedSingleton = [[selfalloc] init];<4>//dosometing});returnsharedSingleton;<5>}
上述代碼中有5小步,解釋如下:
聲明一個(gè)可以新建和獲取單個(gè)實(shí)例對象的方法
聲明一個(gè)static類型的類變量
聲明一個(gè)只執(zhí)行一次的任務(wù)
調(diào)用dispatch_once執(zhí)行該任務(wù)指定的代碼塊揩瞪,在該代碼塊中實(shí)例化上文聲明的類變量
返回在整個(gè)應(yīng)用的生命周期中只會(huì)被實(shí)例化一次的變量
OK赋朦,這就是iOS開發(fā)中單例模式的機(jī)制,下面我們就看看如何在實(shí)際開發(fā)中使用此模式李破?(PS:為了盡可能的突出核心內(nèi)容宠哄,我們會(huì)對設(shè)計(jì)中的其他模式或內(nèi)容一筆帶過)
假如我們需要在iOS應(yīng)用中實(shí)現(xiàn)分層的架構(gòu)設(shè)計(jì),即我們需要把數(shù)據(jù)的持久層嗤攻,展示層毛嫉,和邏輯層分開。為了突出重點(diǎn)妇菱,我們直接把目光轉(zhuǎn)到持久層承粤,而根據(jù)MVC的設(shè)計(jì)模式,我們又可以把持久層細(xì)分為DAO層(放置訪問數(shù)據(jù)對象的四類方法)和Domain層(各種實(shí)體類闯团,比如學(xué)生)辛臊,這樣就可以使用DAO層中的方法,配合實(shí)體類Domain層對數(shù)據(jù)進(jìn)行清晰的增刪改查房交。那么我們?nèi)绾卧O(shè)計(jì)呢彻舰?
從使用者的角度看,我們期望獲得DAO層的類實(shí)例候味,然后調(diào)用它的增刪改查四大方法刃唤】诓拢可是這個(gè)類實(shí)例,我們似乎只需要一個(gè)就足夠了透揣,再多的話不利于管理且浪費(fèi)內(nèi)存。OK川抡,我們可以使用單例模式了辐真,代碼如下:
.h文件:
//StudentDAO.h@interfaceStudentDAO:NSObject@property(nonatomic,strong) NSMutaleArray *StudentsInfo;+ (StudentDAO *)sharedStudentDAO;-(int) create:(Student*)student;-(int) remove:(Student*)student;-(int) modify:(Student*)student;-(NSMutaleArray) findAll;@end
.m文件:
//StudentDAO.m#import"StudentDAO.h"#import"Student.h"@implementationStudentDAOstaticStudentDAO *studentDao = nil;+ (StudentDAO)sharedStudentDAO{staticdispatch_once_tonce;dispatch_once(&once,^{? ? ? ? Student? *student1 = [[Student alloc]init];? ? ? ? student1.name="MexiQQ";? ? ? ? student1.studentNum="201200301101";? ? ? ? Student? *student2 = [[Student alloc]init];? ? ? ? student2.name="Ricardo_LI";? ? ? ? student2.studentNum="201200301102";? ? ? ? studentDao = [[selfalloc] init];? ? ? ? studentDao._StudentsInfo= [[NSMutaleArray alloc]init];? ? ? ? [studentDao._StudentsInfoaddObject:student1];? ? ? ? [studentDao._StudentsInfoaddObject:student2];? ? });returnstudentDao;}//插入的方法-(int)create:(Student*)stu{? ? [self._StudentsInfoaddObject:stu];return0;}//刪除的方法-(int)remove:(Student*)stu{for(Student* sinself._StudentsInfo){if([stu.studentNumisEqual:s.studentNum]){? ? ? ? ? ? [self._StudentsInforemoveObject:s]break;? ? ? ? }? ? }}-(int)modify......//省略不寫-(NSMutaleArray)findAll......//省略不寫
上述例子不難理解,其中用到的Student類我這里就不給出了崖堤,只是一個(gè)含有姓名和學(xué)號(hào)屬性的實(shí)體類侍咱。
觀察者模式
概念:一個(gè)對象狀態(tài)改變,通知正在對他進(jìn)行觀察的對象密幔,這些對象根據(jù)各自要求做出相應(yīng)的改變
圖例:
如圖所示:操作對象向被觀察者對象投送消息楔脯,使得被觀察者的狀態(tài)得以改變,在此之前已經(jīng)有觀察者向被觀察對象注冊胯甩,訂閱它的廣播昧廷,現(xiàn)在被觀察對象將自己狀態(tài)發(fā)生改變的消息廣播出來,觀察者接收到消息各自做出應(yīng)變偎箫。
OK木柬,我們先來看看在蘋果的Cocoa Touch框架中有誰使用了觀察者模式:
通知(notification)機(jī)制
原理圖如下:
如圖所示,在通知機(jī)制中對某個(gè)通知感興趣的所有對象都可以成為接受者淹办。首先眉枕,這些對象需要向通知中心(NSNotificationCenter)發(fā)出addObserver:selector:name:object:消息進(jìn)行注冊,在投送對象投送通知送給通知中心時(shí)怜森,通知中心就會(huì)把通知廣播給注冊過的接受者速挑。所有的接受者不知道通知是誰投送的,不去關(guān)心它的細(xì)節(jié)副硅。投送對象和接受者是一對多的關(guān)系姥宝。接受者如果對通知不再關(guān)注,會(huì)給通知中心發(fā)送removeObserver:name:Object:消息解除注冊想许,以后不再接受通知伶授。
(ps:這段話內(nèi)容摘抄自關(guān)東升先生的文章)
OK,我們試著去使用一下通知機(jī)制:
新建一個(gè)Single view Project,對項(xiàng)目中的文件做以下修改:
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication*)application {? ? [[NSNotificationCenterdefaultCenter]postNotificationName:@"APPTerminate"object:self];}
ViewController.m
////? ViewController.m//? TestNotification////? Created by liwenqian on 14-10-18.//? Copyright (c) 2014年 liwenqian. All rights reserved.//#import"ViewController.h"@interfaceViewController()@end@implementationViewController- (void)viewDidLoad {? ? [superviewDidLoad];//注意此處的selector有參數(shù)流纹,要加冒號(hào)[[NSNotificationCenterdefaultCenter]addObserver:selfselector:@selector(doSomething:) name:@"APPTerminate"object:nil];// Do any additional setup after loading the view, typically from a nib.}- (void)didReceiveMemoryWarning {? ? [superdidReceiveMemoryWarning];? ? [[NSNotificationCenterdefaultCenter]removeObserver:self];// Dispose of any resources that can be recreated.}#pragma mark -處理通知-(void)doSomething:(NSNotification*)notification{NSLog(@"收到通知");}@end
如上所示糜烹,對模版項(xiàng)目的兩個(gè)文件的方法或整個(gè)文件做出修改,Command+R運(yùn)行你的APP漱凝,再按下Home鍵(Command+H)疮蹦,會(huì)發(fā)現(xiàn)打印出一行收到通知的文字,如下:
在APP退到后臺(tái)時(shí)茸炒,發(fā)出廣播愕乎,而viewController因?yàn)闀r(shí)觀察者阵苇,收到廣播,執(zhí)行doSomething方法感论,打印出收到廣播的文字绅项。
KVO(Key-Value-Observing)機(jī)制
原理圖如下:
如圖所示:
該機(jī)制下觀察者的注冊是在被觀察者的內(nèi)部進(jìn)行的,不同于通知機(jī)制(由觀察者自己注冊)比肄,需要被觀察者和觀察者同時(shí)實(shí)現(xiàn)一個(gè)協(xié)議:NSKeyValueObserving快耿,被觀察者通過addObserver:forKeypath:options:context方法注冊觀察者,以及要被觀察的屬性芳绩。
新建一個(gè)single view project掀亥,同時(shí)新建一個(gè)繼承自NSObject的TestWatche類:文件結(jié)構(gòu)如下圖:
對文件進(jìn)行如下修改:AppDelegate.h
////? AppDelegate.h//? TestNotification////? Created by liwenqian on 14-10-18.//? Copyright (c) 2014年 liwenqian. All rights reserved.//#import#import#import"TestWatche.h"@interfaceAppDelegate:UIResponder@property(strong,nonatomic)UIWindow*window;@property(readonly,strong,nonatomic) NSManagedObjectContext *managedObjectContext;@property(readonly,strong,nonatomic) NSManagedObjectModel *managedObjectModel;@property(readonly,strong,nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;@property(strong,nonatomic)NSString*state;@property(strong,nonatomic) TestWatche *watcher;- (void)saveContext;- (NSURL*)applicationDocumentsDirectory;@end
AppDelegate.m 對如下方法做出修改
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {// Override point for customization after application launch.self.watcher= [TestWatche alloc];? ? [selfaddObserver:self.watcherforKeyPath:@"state"options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"pass content"];self.state=@"launch";returnYES;}- (void)applicationDidEnterBackground:(UIApplication*)application {self.state=@"backgroud";}
TestWatche.m(由于繼承自NSObject,而NSObject已實(shí)現(xiàn)了NSKeyValueObserving協(xié)議,所以不需要做聲明)
////? TestWatche.m//? TestNotification////? Created by liwenqian on 14-10-18.//? Copyright (c) 2014年 liwenqian. All rights reserved.//#import"TestWatche.h"@implementationTestWatche-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context{NSLog(@"state change:%@",change);}@end
OK妥色,Command+B Command+R Command+H看看你的應(yīng)用輸出了什么搪花,如果你的操作無誤的話,會(huì)顯示如下內(nèi)容:
委托模式
個(gè)人認(rèn)為委托模式大多數(shù)人解釋的復(fù)雜了嘹害,其實(shí)就像是java中的接口撮竿,類可以實(shí)現(xiàn)或不實(shí)現(xiàn)協(xié)議(接口)中的方法。通過此種方式吼拥,達(dá)到最大的解耦目的倚聚,方便項(xiàng)目的擴(kuò)展。不過你需要設(shè)置應(yīng)用的委托對象凿可,以確定協(xié)議中的方法為誰服務(wù)惑折。
拿最常用的UITableViewDelegate UITableViewDataSource來舉例:
實(shí)現(xiàn)一個(gè)頁面有一個(gè)UItableView,UItableView的每一欄(cell)的數(shù)據(jù)由我們指定枯跑,那么我們該如何做呢惨驶?蘋果也自然想到了這一點(diǎn),于是定義了一個(gè)接口敛助,這個(gè)接口有許多的方法粗卜,只需要我們把要服務(wù)的對象傳進(jìn)去,就可以使用這些方法了纳击,這個(gè)接口就是委托和協(xié)議续扔。而UITableViewDelegate 和 UITableViewDataSource 就是專為UITableView而寫的委托和協(xié)議。用法如下:
ViewController.h
////? ViewController.h//? RecipeBookMe////? Created by liwenqian on 14-9-10.//? Copyright (c) 2014年 liwenqian. All rights reserved.//#import@interfaceViewController:UIViewController@property(nonatomic,strong)IBOutletUITableView*mytableView;@end
ViewController.m
////? ViewController.m//? RecipeBookMe////? Created by liwenqian on 14-9-10.//? Copyright (c) 2014年 liwenqian. All rights reserved.//#import"ViewController.h"#import"DetailViewController.h"#import"Recipe.h"#import"RecipeTableCellTableViewCell.h"@interfaceViewController()@end@implementationViewController{NSArray*recipes;? ? ? }@synthesizemytableView;- (void)viewDidLoad {? ? [superviewDidLoad];// Initialize the recipes arrayRecipe *recipe1 = [Recipe new];? ? recipe1.name=@"Egg Benedict";? ? recipe1.prepTime=@"30 min";? ? recipe1.image=@"egg_benedict.jpg";? ? recipe1.ingredients= [NSArrayarrayWithObjects:@"2 fresh English muffins",@"4 eggs",@"4 rashers of back bacon",@"2 egg yolks",@"1 tbsp of lemon juice",@"125 g of butter",@"salt and pepper", nil];? ? Recipe *recipe2 = [Recipe new];? ? recipe2.name=@"Mushroom Risotto";? ? recipe2.prepTime=@"30 min";? ? recipe2.image=@"mushroom_risotto.jpg";? ? recipe2.ingredients= [NSArrayarrayWithObjects:@"1 tbsp dried porcini mushrooms",@"2 tbsp olive oil",@"1 onion, chopped",@"2 garlic cloves",@"350g/12oz arborio rice",@"1.2 litres/2 pints hot vegetable stock",@"salt and pepper",@"25g/1oz butter", nil];? ? recipes = [NSArrayarrayWithObjects:recipe1, recipe2, nil];}- (void)didReceiveMemoryWarning {? ? [superdidReceiveMemoryWarning];}/*--------------------------------------------------------------------*///定義UITableview的欄目數(shù)量- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section{return[recipes count];}//定義UITableviewCell的顯示內(nèi)容- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{staticNSString*CoustomerTableIdentifier =@"RecipeTableCellTableViewCell";? ? RecipeTableCellTableViewCell *cell =(RecipeTableCellTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CoustomerTableIdentifier];if(cell == nil) {? ? ? cell = [[RecipeTableCellTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CoustomerTableIdentifier];? ? }? ? recipe = [recipes objectAtIndex:indexPath.row];? ? cell.nameLabel.text=? recipe.name;? ? cell.prepTimeLabel.text= recipe.prepTime;? ? cell.thumbnailImageView.image= [UIImageimageNamed:recipe.image];returncell;}//點(diǎn)擊每一欄執(zhí)行跳轉(zhuǎn)時(shí)的處理- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {if([segue.identifierisEqualToString:@"showRecipeDetail"]) {NSIndexPath*indexPath = nil;? ? ? ? Recipe *recipe = nil;? ? ? ? indexPath = [self.mytableViewindexPathForSelectedRow];? ? ? ? recipe = [recipes objectAtIndex:indexPath.row];? ? ? ? DetailViewController *destViewController = segue.destinationViewController;? ? ? ? destViewController.recipe= [recipes objectAtIndex:indexPath.row];? ? }}//定義cell的高度- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath{return71;}/*--------------------------------------------------------------------*/@end
如上所示焕数,兩條/------/注釋間的方法全部來自于委托和協(xié)議纱昧。利用委托和協(xié)議,你可以把主要精力放到邏輯業(yè)務(wù)上堡赔,將數(shù)據(jù)綁定和事件處理交給委托和協(xié)議去完成识脆。
藍(lán)鷗iOS開發(fā),國內(nèi)口碑最好的iOS培訓(xùn)學(xué)院,詳情請咨詢官方網(wǎng)站http://www.lanou3g.com我們是 一群熱愛IT的年輕人灼捂,如果你也愛IT离例、愛iOS開發(fā),歡迎前來藍(lán)鷗iPhone培訓(xùn)中心參觀學(xué)習(xí)悉稠,讓我們共同為夢想發(fā)聲宫蛆。