iOS中常見的設計模式——單例模式\委託模式\觀察者模式\MVC模式

 1、單例模式

1. 什麼是單例模式?php

在iOS應用的生命週期中,某個類只有一個實例。設計模式

 

2. 單例模式解決了什麼問題?api

想象一下,若是咱們要讀取文件配置信息,那麼每次要讀取,咱們就要建立一個文件實例,而後才能獲取到裏面的相關配置信息,這樣若是,咱們若是要屢次讀取這個文件的配置信息,那就要建立多個實例,這樣嚴重浪費了內存資源。而實際應用中,當咱們要用到的類多是要反覆用到的,通常能夠考慮使用單例模式。這樣能夠大大下降建立新實例帶來的內存浪費。數組

 

3. 單例模式的實現原理瀏覽器

通常會封裝一個靜態屬性,並提供靜態實例的建立方法(該方法使用GCD技術保證了整個程序生命週期只運行一次:用了dispath_once()函數)。網絡

 

4. 應用實例架構

  • UIApplication:提供應用程序的集中控制點來保持應用的狀態。
  • NSUserDefaults:讀取應用設置項目。
  • NSNotificationCenter:提供信息廣播通知。
  • NSFileManager:訪問文件系統的通用操做。
  • NSBundle:動態加載或卸載可執行代碼,定位資源文件,資源本地化和訪問文件系統等。

 

 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"www.baidu.com"]];
 NSFileManager *FileManager = [NSFileManager defaultManager];

註解:APP單例應用的建立,並在瀏覽器中打開URL地址。第二則是獲取文件管理者,整個程序運行週期內,只有一個文件管理者,第一次建立後,之後要用到就直接用不會再建立了。app

2、委託模式

1. 什麼是委託模式? 框架

  • 基本框架類+協議+委託對象
  • 把看似功能很強且很難維護的類,按照職責功能將它抽取出來成爲協議,委託其餘對象幫本身實現協議中的方法

 

 2. 委託模式解決了什麼問題?ide

委託時爲了下降一個對象的複雜度和耦合度,使其主要框架類可以具備通用性,其餘旁枝末節的方法留給委託對象去實現。

 

3. 委託模式的實現原理

  • 框架類經過delegate屬性保持對委託對象的引用,並在特定時刻向委託對象發送消息,通知其作一些事情。
  • 委託對象需符合兩個條件:1. 遵循協議 2. 設置爲框架類的委託

 

4. 應用實例

UITextFieldDelegate

#import "ViewController.h"

@interface ViewController () <UITextFieldDelegate>

@property (strong, nonatomic) UITextField *textField;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.textField.delegate = self;
}

@end  

註解:控制器遵循協議<UITextFieldDelegate>,而且成爲了textField的代理。這樣控制器就擁有了協議中的方法,textField也就能讓代理爲本身作一些事。

3、觀察者模式

1. 什麼是觀察者模式?

觀察者模式也叫發佈/訂閱模式。好比訂閱天氣預報,其中有以下三個角色:

  • 氣象局
  • 中國移動短信中心
  • 手機用戶 

第一步:手機用戶訂閱中國移動短信中心的天氣預報業務。

第二步:下雨時,氣象局發佈通告信息給中國移動短信中心:「有雨」。

第三步:手機用戶就會收到中國移動短信中心的信息:「有雨」,接着用戶就會知道應該採起什麼的動做:「出門帶傘」。

第四步:當不須要此項功能服務時,取消訂閱。

氣象局與用戶之間的通訊是匿名的,用戶只知道是中國移動發的短息,不知道氣象局的存在。

 

2. 觀察者模式解決了什麼問題?

  • 消除具備不一樣行爲的對象之間的耦合,經過這一模式,不一樣對象能夠協同工做,同時它們也能夠被複用於其餘地方。
  • 消息發送者和消息接受者二者能夠互相一無所知,徹底解耦。

 

3. 觀察者模式的實現原理

有4個角色:

  • 抽象主題(Subject):它把全部觀察者對象的引用保存到一個彙集裏,每一個主題均可以有任何數量的觀察者。抽象主題提供一個接口,能夠增長和刪除觀察者對象。
  • 具體主題(ConcreteSubject):將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給全部登記過的觀察者發出通知。
  • 抽象觀察者(Observer):爲全部的具體觀察者定義一個接口,在獲得主題通知時更新本身。
  • 具體觀察者(ConcreteObserver):實現抽象觀察者角色所要求的更新接口,以便使自己的狀態與主題狀態協調。

 

4. 觀察者模式的應用

主要有兩種,通知機制和KVO機制。

  • 通知機制:一個對象能夠與一個或多個對象進行通訊已完成同步協做,而且他們之間的通訊是匿名的,是經過第三方單例類NSNotificationCenter完成通訊的。

氣象臺:

//  observatory.h
#import <Foundation/Foundation.h>

@interface Observatory : NSObject

@property (getter=isRain) BOOL isRain;

@end
//  observatory.m
#import "Observatory.h"

@implementation Observatory


@end

 手機用戶:

//  phoneUser.h
#import <Foundation/Foundation.h>

@interface PhoneUser : NSObject

- (void)takeUmbrella;

@end
//  phoneUser.m
#import "PhoneUser.h"

@implementation PhoneUser

- (void)takeUmbrella {
    NSLog(@"Take Umbrella");
}


@end 

測試用例:

//  main.m
#import <Foundation/Foundation.h>
#import "Observatory.h"
#import "PhoneUser.h"

static NSString * const weatherForcast = @"weatherForecast";

int main(int argc, const char * argv[]) {
    
    // 用戶訂閱天氣預報
    PhoneUser *pUser = [[PhoneUser alloc] init];
    [[NSNotificationCenter defaultCenter] addObserver:pUser selector:@selector(takeUmbrella) name:weatherForcast object:nil];
    
    // 氣象臺發佈下雨天氣預報
    Observatory *observatory = [[Observatory alloc] init];
    observatory.isRain = YES;
    if (observatory.isRain) {
       [[NSNotificationCenter defaultCenter] postNotificationName:weatherForcast object:observatory userInfo:nil];
    }
    
    // 取消訂閱
    [[NSNotificationCenter defaultCenter] removeObserver:pUser name:weatherForcast object:observatory];
    
    return 0;
}

 運行結果:

2016-10-25 03:12:36.860998 02-通知機制[5172:2936788] Take Umbrella
Program ended with exit code: 0

 

註解:手機用戶向服務中心預訂天氣預報服務,當下雨時,氣象局就會發送通告給服務中心,服務中心收到通告就會發送短信給全部已訂閱的用戶,全部已訂閱用戶收到短信後則能夠做出相應的動做。

 

  • KVO機制:KVO,即:Key-Value Observing,它提供一種機制,當指定的對象的屬性被修改後,則對象就會接受到通知。簡單的說就是每次指定的被觀察的對象的屬性被修改後,KVO就會自動通知相應的觀察者了。這種模式有利於兩個類間的解耦合,尤爲是對於 業務邏輯與視圖控制 這兩個功能的解耦合。在MVC設計架構下的項目,KVO機制很適合實現mode模型和view視圖之間的通信。

 股票後臺數據:

//  BackgroundData.h
#import <Foundation/Foundation.h>

@interface BackgroundData : NSObject
/** 股價漲 */
@property (getter=isShareRise) BOOL isShareRise;

@end
//  BackgroundData.m
#import "BackgroundData.h"

@implementation BackgroundData

@end

前臺展現股票數據:

//  foregroundDisplay.h
#import <Foundation/Foundation.h>

@interface ForegroundDisplay : NSObject

@end
//  foregroundDisplay.m
#import "ForegroundDisplay.h"

@implementation ForegroundDisplay

#pragma mark - 當被觀察者的屬性值發送變化時調用改方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"The shares rise");
}

@end

測試用例:

//  main.m
#import <Foundation/Foundation.h>
#import "BackgroundData.h"
#import "ForegroundDisplay.h"

int main(int argc, const char * argv[]) {
    // 前臺展現股票跌漲
    ForegroundDisplay *fgDisplay = [[ForegroundDisplay alloc] init];

    // 後臺數據
    BackgroundData *bgData = [[BackgroundData alloc] init];
    bgData.isShareRise = NO;
    
    // 爲後臺數據註冊觀察者
    [bgData addObserver:fgDisplay forKeyPath:@"isShareRise" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    
    // 後臺數據屬性值改變
    bgData.isShareRise = YES;
    
    // 移除觀察者
    [bgData removeObserver:fgDisplay forKeyPath:@"isShareRise" context:nil];
    
    
    return 0;
}

 運行結果:

2016-10-25 02:51:35.931473 03-KVO機制[5059:2820557] The shares rise
Program ended with exit code: 0

 

 註解:註冊一個觀察者觀察後臺股票數據的變更,一旦有變更,則會觸發觀察者的特定方法,在改方法中能夠配置後臺數據與前臺展現同步。最後應當移除觀察者,若是沒有移除,則至關於後臺數據類銷燬了,但觀察者還保存着對它的引用,這就會奔潰。

 

4、 MVC模式

1. 什麼是MVC模式

所謂MVC模式,即model,view,controller。模型、視圖、控制器,其中各自有其職能所在:

  • Model負責存儲、定義、操做數據
  • View用來展現數據給用戶,和用戶進行操做交互
  • Controller是Model和View的協調者,Controller把Model中的數據拿過來給View用

Controller能夠直接與Model和View進行通訊,而View不能和Controller直接通訊。View與Controller通訊須要利用代理協議的方式,當有數據更新時,Model也要與Controller進行通訊,這個時候就要用Notification和KVO,這個方式就像一個廣播同樣,Model發信號,Controller設置監聽接受信號,當有數據更新時就發信號給Controller,Model和View不能直接進行通訊,這樣會違背MVC設計模式。

 

2. MVC模式解決了什麼問題?

MVC模式可以完成各司其職的任務模式,因爲下降了各個環節的耦合性,大大優化Controller的代碼量,便於調試與維護,並且還利於程序的可複用性 。有利於團隊合做。

 

3. MVC的實現原理

引用一張MVC通訊示例圖:

其中虛實線就比如交通規則路線,虛線表示可穿越,實線表示不可穿越。從圖中能夠看出:

  • 控制器能夠經過outlet與模型,視圖直接通訊
  • 模型和視圖不能直接通訊
  • 視圖與控制器通訊有三種途徑:數據源、代理、Action-Target 監聽模式
  • 模型與控制器通訊的途徑是觀察者模式:通知機制和KVO機制

 

4. MVC模式的應用 

用MVC模式構建以下圖片所展現的demo,其中的數據都是經過網絡請求加載的,cell是自定義的。

 

1. 以MVC劃分文件

 

2. 控制器

1 //  LKSubTableViewController.h
2 #import <UIKit/UIKit.h>
3 
4 @interface LKSubscriptionTableViewController : UITableViewController
5 
6 @end
  1 //  LKSubscriptionTableViewController.m
  2 #import "LKSubscriptionTableViewController.h"
  3 #import "LKSubscriptionItem.h"
  4 #import "LKSubscriptionTableViewCell.h"
  5 
  6 #import <AFNetworking/AFNetworking.h>
  7 #import <MJExtension/MJExtension.h>
  8 #import <UIImageView+WebCache.h>
  9 #import <SVProgressHUD-0.8.1/SVProgressHUD.h>
 10 
 11 /** 可重用單元標識 */
 12 static NSString *cellIdentifier = @"Cell";
 13 
 14 @interface LKSubscriptionTableViewController ()
 15 /** 會話管理者 */
 16 @property (strong, nonatomic) AFHTTPSessionManager *mgr;
 17 /** 網絡數據 */
 18 @property (strong, nonatomic) NSArray *data;
 19 
 20 @end
 21 
 22 @implementation LKSubscriptionTableViewController
 23 
 24 - (void)viewDidLoad {
 25     [super viewDidLoad];
 26     
 27     // 註冊
 28     [self.tableView registerNib:[UINib nibWithNibName:@"LKSubscriptionTableViewCell" bundle:nil] forCellReuseIdentifier:cellIdentifier];
 29   
 30     
 31     // 加載網絡數據
 32     [self loadData];
 33     
 34     [SVProgressHUD showWithStatus:@"加載中..."];
 35     
 36 }
 37 
 38 #pragma mark - 1. 數據源方法
 39 
 40 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 41 
 42     return 1;
 43 }
 44 
 45 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 46     
 47     return self.data.count;
 48 }
 49 
 50 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 51     // 加載自定義的表單元
 52     LKSubscriptionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
 53     
 54     // 獲取模型
 55     LKSubscriptionItem *item = self.data[indexPath.row];
 56     
 57     // 控制器經過接口傳遞模型數據給視圖
 58     cell.item = item;
 59     
 60     return cell;
 61 }
 62 
 63 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
 64     return 80;
 65 }
 66 
 67 
 68 #pragma mark - 2. 加載網絡數據
 69 - (void)loadData {
 70     // 建立會話管理者
 71     self.mgr = [AFHTTPSessionManager manager];
 72     
 73     // 封裝參數
 74     NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
 75     parameters[@"a"] = @"tag_recommend";
 76     parameters[@"c"] = @"topic";
 77     parameters[@"action"] = @"sub";
 78 
 79     // 發送請求
 80     [self.mgr GET:@"http://api.budejie.com/api/api_open.php" parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
 81         
 82         [SVProgressHUD dismiss];
 83         // 經過字典數組來建立一個模型數組
 84         self.data = [LKSubscriptionItem mj_objectArrayWithKeyValuesArray:responseObject];
 85         
 86         // 刷新列表顯示到界面
 87         [self.tableView reloadData];
 88         
 89     } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
 90         NSLog(@"加載推薦標籤出錯:%@", error);
 91         [SVProgressHUD showErrorWithStatus:@"加載失敗,請檢查網絡是否正常"];
 92         [SVProgressHUD dismiss];
 93     }];
 94 }
 95 
 96 
 97 - (void)viewWillDisappear:(BOOL)animated {
 98     [super viewWillDisappear:animated];
 99     [SVProgressHUD dismiss];
100     [self.mgr.tasks makeObjectsPerformSelector:@selector(cancel)];
101 }
102 
103 
104 @end
LKSubscriptionTableViewController.m

2. 視圖

 1 //  LKSubscriptionTableViewCell.h
 2 #import <UIKit/UIKit.h>
 3 
 4 @class LKSubscriptionItem;
 5 
 6 @interface LKSubscriptionTableViewCell : UITableViewCell
 7 /** 模型數據 */
 8 @property (strong, nonatomic) LKSubscriptionItem *item;
 9 
10 @end

 

 1 //  LKSubscriptionTableViewCell.m
 2 #import "LKSubscriptionTableViewCell.h"
 3 #import "LKSubscriptionItem.h"
 4 
 5 #import <UIImageView+WebCache.h>
 6 
 7 
 8 @interface LKSubscriptionTableViewCell ()
 9 /** 訂閱圖標 */
10 @property (weak, nonatomic) IBOutlet UIImageView *icon;
11 
12 /** 訂閱名稱 */
13 @property (weak, nonatomic) IBOutlet UILabel *name;
14 
15 /** 訂閱人數 */
16 @property (weak, nonatomic) IBOutlet UILabel *number;
17 
18 @end
19 
20 @implementation LKSubscriptionTableViewCell
21 
22 #pragma mark - 根據控制器傳遞過來的模型數據來顯示cell
23 - (void)setItem:(LKSubscriptionItem *)item {
24     _item = item;
25     
26     // 設置訂閱名稱
27     _name.text = item.theme_name;
28     
29     // 設置訂閱人數
30     [self resolveNum];
31     
32     // 設置訂閱圖標
33     [_icon sd_setImageWithURL:[NSURL URLWithString:self.item.image_list] placeholderImage:[UIImage imageNamed:@"defaultUserIcon"]];
34     
35 }
36 
37 #pragma mark - 處理訂閱數字
38 - (void)resolveNum {
39     NSString *numStr = [NSString stringWithFormat:@"有%@人訂閱",self.item.sub_number];
40     NSInteger num = self.item.sub_number.integerValue;
41     if (num > 10000) {
42         CGFloat numF = num / 10000.0;
43         numStr = [NSString stringWithFormat:@"%.1f萬人訂閱",numF];
44         numStr = [numStr stringByReplacingOccurrencesOfString:@".0" withString:@""];
45     }
46     
47     _number.text = numStr;
48 }
49 
50 #pragma mark - 處理cell間的分割線
51 - (void)setFrame:(CGRect)frame {
52     frame.size.height -= 1;
53     // 纔是真正去給cell賦值
54     [super setFrame:frame];
55 }
56 
57 #pragma mark - 處理切割圖片爲圓形頭像
58 - (void)awakeFromNib {
59     [super awakeFromNib];
60     
61     self.icon.layer.cornerRadius = 30;
62     self.icon.layer.masksToBounds = YES;
63 }
64 
65 
66 @end
LKSubscriptionTableViewCell.m

 

 3. 模型數據

 1 //  LKSubscriptionItem.h
 2 #import <Foundation/Foundation.h>
 3 
 4 @interface LKSubscriptionItem : NSObject
 5 /** 圖標 */
 6 @property (strong, nonatomic) NSString *image_list;
 7 
 8 /** 名稱 */
 9 @property (strong, nonatomic) NSString *theme_name;
10 
11 /** 訂閱人數 */
12 @property (assign, nonatomic) NSString *sub_number;
13 
14 @end

 

//  LKSubscriptionItem.m
#import "LKSubscriptionItem.h"

@implementation LKSubscriptionItem

@end

 

註解:經過上面的demo能夠知道:控制器主要負責將從網絡上請求數據獲得的數據經過接口存儲到數據模型之中,當view,也即便自定義的cell須要數據時,並協助view與model之間的通訊,也就是把model的數據傳給了cell,這樣cell就能及時顯示數據給用戶。上述demo中各角色職責分明:

  • 控制器:網絡請求數據,存儲到模型,並協做view與model之間的通訊
  • 視圖:負責將數據展現給用戶
  • 模型:存儲網絡數據
相關文章
相關標籤/搜索