iOS開發——設計模式那點事

iOS開發——設計模式那點事 原文地址

題外話:提及設計模式,感受本身把握不了筆頭,因此單拿出iOS開發中的幾種經常使用設計模式談一下java

單例模式(Singleton)

概念:整個應用或系統只能有該類的一個實例設計模式

在iOS開發咱們常常碰到只須要某類一個實例的狀況,最多見的莫過於對硬件參數的訪問類,好比UIAccelerometer.這個類能夠幫助咱們得到硬件在各個方向軸上的加速度,可是咱們僅僅須要它的一個實例就夠了,再多,只會浪費內存。架構

因此蘋果提供了一個UIAccelerometer的實例化方法+sharedAccelerometer,從名字上咱們也能看出此方法讓整個應用共享一個UIAccelerometer實例(PS:iOS 的開放中,咱們每每能從方法名中就瞭解這個方法的做用),它內部的如何實現咱們暫且不談,先來看看還有哪些類一樣使用了單例模式。app

  • UIApplication類提供了 +sharedAPplication方法建立和獲取UIApplication單例
  • NSBundle類提供了 +mainBunle方法獲取NSBundle單例
  • NSFileManager類提供了 +defaultManager方法建立和得到NSFileManager單例。(PS:有些時候咱們得放棄使用單例模式,使用-init方法去實現一個新的實例,好比使用委託時)
  • NSNotificationCenter提供了 +defaultCenter方法建立和獲取NSNotificationCenter單例(PS:該類還遵循了另外一個重要的設計模式:觀察者模式)
  • NSUserDefaults類提供了 +defaultUserDefaults方法去建立和獲取NSUserDefaults單例

等等,蘋果的SDK中大量的遵循此設計模式,那麼它的內部是如何實現的呢?框架

首先給你們介紹一下GCD技術,是蘋果針對於多核CPU的多任務解決方案。你不須要了解更多,只須要知道是一組基於C語言開發的API(詳細內容能夠看一下唐巧前輩的這篇博文:http://blog.devtang.com/blog/2012/02/22/use-gcd/ )。GCD提供了一個dispatch_once函數,這個函數的做用就是保證block(代碼塊:暫時理解爲一個跟函數相近的東西,具體能夠參照這篇文章:http://blog.csdn.net/enuola/article/details/8674063 )裏的語句在整個應用的生命週期裏只執行一次。ide

OK,接下來就給出一個使用了單例模式新建和獲取實例的類模版,代碼以下:函數

//Singleton.h
@interface Singleton : NSObject
+ (Singleton *)sharedSingleton; <1>
@end

/***************************************************************/

//Singleton.m
#import "Singleton.h"
@implementation Singleton   
static Singleton *sharedSingleton = nil;<2>

+ (Singleton *)sharedSingleton{
    static dispatch_once_t once;<3>
    dispatch_once(&once,^{
        sharedSingleton = [[self alloc] init];<4>
        //dosometing
    });
    return sharedSingleton;<5>
}

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

  • 聲明一個能夠新建和獲取單個實例對象的方法
  • 聲明一個static類型的類變量
  • 聲明一個只執行一次的任務
  • 調用dispatch_once執行該任務指定的代碼塊,在該代碼塊中實例化上文聲明的類變量
  • 返回在整個應用的生命週期中只會被實例化一次的變量

OK,這就是iOS開發中單例模式的機制,下面咱們就看看如何在實際開發中使用此模式?(PS:爲了儘量的突出核心內容,咱們會對設計中的其餘模式或內容一筆帶過)ui

假如咱們須要在iOS應用中實現分層的架構設計,即咱們須要把數據的持久層,展現層,和邏輯層分開。爲了突出重點,咱們直接把目光轉到持久層,而根據MVC的設計模式,咱們又能夠把持久層細分爲DAO層(放置訪問數據對象的四類方法)和Domain層(各類實體類,好比學生),這樣就可使用DAO層中的方法,配合實體類Domain層對數據進行清晰的增刪改查。那麼咱們如何設計呢?atom

從使用者的角度看,咱們指望得到DAO層的類實例,而後調用它的增刪改查四大方法。但是這個類實例,咱們彷佛只須要一個就足夠了,再多的話不利於管理且浪費內存。OK,咱們可使用單例模式了,代碼以下:
.h文件:

//StudentDAO.h
@interface StudentDAO: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"
@implementation StudentDAO

static StudentDAO *studentDao = nil;
+ (StudentDAO)sharedStudentDAO{
    static dispatch_once_t once;
    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 = [[self alloc] init];
        studentDao._StudentsInfo = [[NSMutaleArray alloc]init];
        [studentDao._StudentsInfo addObject:student1];
        [studentDao._StudentsInfo addObject:student2];
    });
    return studentDao;
}   
//插入的方法
-(int)create:(Student*)stu{
    [self._StudentsInfo addObject:stu];
    return 0;
}   
//刪除的方法
-(int)remove:(Student*)stu{
    for(Student* s in self._StudentsInfo){
        if([stu.studentNum isEqual:s.studentNum]){
            [self._StudentsInfo removeObject:s]
            break;
        }
    }
}
-(int)modify...... //省略不寫
-(NSMutaleArray)findAll...... //省略不寫

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

觀察者模式

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

圖例:

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

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

通知(notification)機制

原理圖以下:

如圖所示,在通知機制中對某個通知感興趣的全部對象均可以成爲接受者。首先,這些對象須要向通知中心(NSNotificationCenter)發出addObserver:selector:name:object:消息進行註冊,在投送對象投送通知送給通知中心時,通知中心就會把通知廣播給註冊過的接受者。全部的接受者不知道通知是誰投送的,不去關心它的細節。投送對象和接受者是一對多的關係。接受者若是對通知再也不關注,會給通知中心發送removeObserver:name:Object:消息解除註冊,之後再也不接受通知。
(ps:這段話內容摘抄自關東昇先生的文章)

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

新建一個Single view Project,對項目中的文件作如下修改:

AppDelegate.m

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[NSNotificationCenter defaultCenter]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"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //注意此處的selector有參數,要加冒號
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(doSomething:) name:@"APPTerminate" object:nil];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    [[NSNotificationCenter defaultCenter]removeObserver:self];
    // Dispose of any resources that can be recreated.
}

#pragma mark -處理通知
-(void)doSomething:(NSNotification*)notification{
    NSLog(@"收到通知");
}

@end

如上所示,對模版項目的兩個文件的方法或整個文件作出修改,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

//
//  AppDelegate.h
//  TestNotification
//
//  Created by liwenqian on 14-10-18.
//  Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "TestWatche.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@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];

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

- (void)applicationDidEnterBackground:(UIApplication *)application {
    self.state = @"backgroud";
}

TestWatche.m(因爲繼承自NSObject,而NSObject已實現了NSKeyValueObserving協議,因此不須要作聲明)

//
//  TestWatche.m
//  TestNotification
//
//  Created by liwenqian on 14-10-18.
//  Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import "TestWatche.h"

@implementation TestWatche

-(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看看你的應用輸出了什麼,若是你的操做無誤的話,會顯示以下內容:

委託模式

我的認爲委託模式大多數人解釋的複雜了,其實就像是java中的接口,類能夠實現或不實現協議(接口)中的方法。經過此種方式,達到最大的解耦目的,方便項目的擴展。不過你須要設置應用的委託對象,以肯定協議中的方法爲誰服務。

拿最經常使用的UITableViewDelegate UITableViewDataSource來舉例:

實現一個頁面有一個UItableView,UItableView的每一欄(cell)的數據由咱們指定,那麼咱們該如何作呢?蘋果也天然想到了這一點,因而定義了一個接口,這個接口有許多的方法,只須要咱們把要服務的對象傳進去,就可使用這些方法了,這個接口就是委託和協議。而UITableViewDelegate 和 UITableViewDataSource 就是專爲UITableView而寫的委託和協議。用法以下:

ViewController.h

//
//  ViewController.h
//  RecipeBookMe
//
//  Created by liwenqian on 14-9-10.
//  Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong) IBOutlet UITableView *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"

@interface ViewController ()

@end

@implementation ViewController{
     NSArray *recipes;      
}

@synthesize mytableView;

- (void)viewDidLoad {
    [super viewDidLoad];

    // Initialize the recipes array
    Recipe *recipe1 = [Recipe new];

    recipe1.name = @"Egg Benedict";
    recipe1.prepTime = @"30 min";
    recipe1.image = @"egg_benedict.jpg";
    recipe1.ingredients = [NSArray arrayWithObjects:@"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 = [NSArray arrayWithObjects:@"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 = [NSArray arrayWithObjects:recipe1, recipe2, nil];

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

/*--------------------------------------------------------------------*/
//定義UITableview的欄目數量
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
     return [recipes count];
}

//定義UITableviewCell的顯示內容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *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 = [UIImage imageNamed:recipe.image];

    return cell;
}

//點擊每一欄執行跳轉時的處理
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"showRecipeDetail"]) {

        NSIndexPath *indexPath = nil;
        Recipe *recipe = nil;

        indexPath = [self.mytableView indexPathForSelectedRow];
        recipe = [recipes objectAtIndex:indexPath.row];

        DetailViewController *destViewController = segue.destinationViewController;
        destViewController.recipe = [recipes objectAtIndex:indexPath.row];
    }
}

//定義cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 71;
}
/*--------------------------------------------------------------------*/
@end

如上所示,兩條/------/註釋間的方法所有來自於委託和協議。利用委託和協議,你能夠把主要精力放到邏輯業務上,將數據綁定和事件處理交給委託和協議去完成。

相關文章
相關標籤/搜索