iOS中的一些約定、模式與三種回調機制

iOS中的各類模式
1.MVC

先說說 一般你們所見的MVC模式
在MVC的教科書定義中,Model採用的是觀察者模式,也就是Model是被觀察者,View是觀察者,Model有任何改變的狀況下,View都會接受到通知。
可是在典型Web環境中,View不須要實時的改變,只有客戶端發送request時,View纔可能須要改變。
換句話說,只有當咱們須要生成一個頁面做爲響應返回給客戶端的時候,建立一個View並使用Model纔有意義。
因此View就再也不直接觀察Model,而是經過Controller來做爲中間人。

一個Asp.NET mvc的大概流程圖以下,能夠看出Controller是相應客戶端請求的入口,當一個請求由客戶端發過來的時候,router選擇合適的Controller實例化,再把請求交給相應的action處理。




可是在iOS中,事情又變得不同了些,這是由於View類(一般指UIView和它的之類)負責與用戶交互,它們提供信息而且接受用戶事件。
View的邏輯一般委託給Controller處理,但View不該該直接引用Controller。除直接父View和子View外,它們也不該引用其餘View。View能夠引用Model類,但通常只引用它們正在顯示的特定Model對象。例如,StudentView對象可能顯示一個Student對象。

View負責從用戶接受事件,但不處理它們。當用戶觸碰View時,該View可能提醒一個委託說明它已被觸碰,但它不能執行邏輯或者修改其餘View。例如,按下刪除鍵這一事件發生時應該只提示一個委託說明一個刪除鍵已被按下。View不能直接讓Model類刪除數據或者本身從屏幕上刪除數據。這些功能應該由Controller來負責,也就是說View和Model的聯繫應該由Controller來創建。

Controller實現了大部分應用程序特定的邏輯。大多數Controller在「Model」類和「View「類之間起協調做用。例如,UITableViewController協調數據Model和UITableView。有些控制器在Model對象或者View對象之間進行協調。這些控制器的名稱有時以Manager結尾,例如CALayoutManager和CTFontManager。這些一般都是單例。

 

2.iOS中的委託,使用組合代替繼承
使用委託配置對象是策略模式的一種形式。策略模式封裝了一個算法而且容許你經過附加不一樣的策略(算法)對象改變該對象的行爲。

委託是一種策略對象,它封裝了決定另外一 個對象行爲的算法。例如,一個UITableView的委託實現了一個算法,該算法決定UITableView的行高。

結合在MVC中討論的View和Controller的關係,能夠由下圖來表現二者關係,實線是直接引用,虛線是間接引用。間接引用能夠經過id來實現






舉個自定義協議的例子來講,在LWRequest.h中,咱們定義了一個協議LWRequestDelegate,目的是爲了把協議ASIHTTPRequestDelegate的實現轉交給不一樣的委託:

複製代碼
@class LWRequest;
@class ASIHTTPRequest;

@protocol LWRequestDelegate <NSObject>
@optional
- (void) requestDidFinish:(LWRequest*)request;
@end

@interface LWRequest : NSObject <ASIHTTPRequestDelegate>
{
@protected
    
    ASIHTTPRequest* _serverRequest;   
    id<LWRequestDelegate> _delegate;
}

@property (nonatomic, weak) id<LWRequestDelegate> delegate;
複製代碼
 
在LWRequest.m中,咱們定義

複製代碼
@interface LWRequest ()

@property (nonatomic, strong) ASIHTTPRequest* serverRequest;

//自定義了get方法,set方法交由@synthesize生成
- (ASIHTTPRequest*) serverRequest;

- (void) requestDidFinish:(ASIHTTPRequest*)request;


@end

@implementation LWRequest

@synthesize serverRequest = _serverRequest;

//交由子類來實現,實現向不一樣的地址發送請求
- (ASIHTTPRequest*) serverRequest 
{
    return nil;
}

//這個方法是協議ASIHTTPRequestDelegate的方法
- (void) requestDidFinish:(ASIHTTPRequest*)request 
{
    if (request.responseStatusCode == 200 || request.responseStatusCode == 500)
    {
        //轉交給咱們的委託去處理
        if ([delegate_ respondsToSelector:@selector(requestDidFinish:)])
            [delegate_ requestDidFinish:self];
    }
    else
        ...
}
複製代碼
任何實現了咱們定義的協議  @protocol LWRequestDelegate <NSObject> LWRequestDelegate的類,能夠做爲LWRequest的delegate來使用

@class LWRequest;

@interface SomeClass : NSObject <LWRequestDelegate>
{
    - (void) requestDidFinish:(LWRequest*)request;
}
 

在把配置屬性直接添加到類以前,應先考慮改成添加一個委託,這樣能夠得到更好的靈活性。

 

 
3.單例模式
單例模式在Cocoa中很是常見。按照習慣,你能夠經過一個以shared開頭的類方法識別它。

單例每每用於業務層對象,就如同前面所說的CALayoutManager類同樣。

單例每每會伴隨着線程安全問題,能夠在+sharedSingleton中添加一個@synchronize以達到線程安全的目的,但這樣就會使用到同步對象,性能會產生問題。

建議經過GCD內置的dispatch_once方法、速度快,並且線程安全。

+ (MYSingleton *)sharedSingleton {
  static dispatch_once_t pred;
  static MYSingleton *instance = nil;
  dispatch_once(&pred, ^{instance = [[self alloc] init];});
  return instance;
}
 

4.iOS中的命令模式
主要用於UIControl,在指定控件的對應事件的時候用,其餘狀況下比較少用,因此就不說了。建議用Block來代替(相似於C#中的匿名委託),Block下面會說。

一樣有關聯的有proxy模式,不過用起來相對複雜一些,之後再說

 

5.iOS中的觀察者模式
 觀察者模式容許一個對象在它的狀態變化時通知多個觀察者,而被觀察的對象無需對觀察者有特定的認識。觀察者模式在 Cocoa 中以多種形式出現,包括 NSNotification、KVO。它促使對象之間產生弱耦合,以使組件更具可重用性而且更加健壯。

這裏介紹一下NSNotification,它看起來就像個全局的事件註冊對象。

比較常見的是UIDeviceNotification:

UIDeviceOrientationDidChangeNotification
UIDeviceBatteryStateDidChangeNotification
UIDeviceBatteryLevelDidChangeNotification
UIDeviceProximityStateDidChangeNotification
好比檢測設備方向是否變化的時候,就能夠相應這些事件

複製代碼
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    UIDevice *device = [UIDevice currentDevice];

    [device beginGeneratingDeviceOrientationNotifications];

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    
    [nc addObserver:self //把本身做爲事件觀察者
           selector:@selector(orientationChanged:) // 用來響應事件發生時的方法
               name:UIDeviceOrientationDidChangeNotification // 方向改變事件
             object:device]; //事件發出者

    [self.window makeKeyAndVisible];

    return YES;
}


//響應事件時的方法
- (void)orientationChanged:(NSNotification *)note
{
    NSLog(@"orientationChanged: %d", [[note object] orientation]);
}

複製代碼
使用起來很簡單,要注意的是dealloc前候要先取消註冊

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
 

6. Block
其實熟練使用C#匿名委託和JavaScript的人對這種對方法的抽象再熟悉不過了。

先看看語法, 用^符號來代表這是一個block變量,下面就聲明瞭一個MyBlock變量。左邊是返回類型,右邊是參數類型

複製代碼
typedef int (^myBlockA) (int p1, double p2);

UIColor * (^myBlockB)(Line *)

void (^myBlockC) ();

//這麼賦值,是否是很眼熟啊?
myBlockA = ^(int p1,double p2){
  return p1;
}
 //block也能夠是匿名的

- (void (^)()) getBlock
{
    return ^{ NSLog(@"  block 0:%d", 1);};
}

//使用的時候和lambda同樣,返回值和參數都不是必須的

^{}

相似於

()=>{}

複製代碼
若是使用typedef關鍵字進行代碼塊定義,有時會使代碼更易讀。這使得你沒必要從新敲入代碼塊的全部參數和返回類型就能夠複用這些定義。

typedef void (^myBlockA)(NSString *);
在C#中已經幫咱們預約義了Func<T,T>,Action<T>等,這樣就省得咱們本身再次定義,這是我以爲作得好的地方。
和C#中的匿名委託和Javascript中的函數同樣,Block也能捕獲變量。這是它與C中的函數指針有區別的地方。

好吧,關於Block有別於其餘語言下的特殊實現點來了!
Block是一種比較特殊的 Objective-C 對象。跟傳統對象不一樣的是,Block不是在堆上建立的,而是在棧上。主要緣由有兩 個,首先是由於性能——在棧上分配空間幾乎老是比在堆上快;其次是出於訪問其餘局部變量的須要。

可是,當函數的做用域結束時,棧會被銷燬。若是Block被傳遞給一個方法,此方法會在定義Block的做用域銷燬後才調用到這個Block,因此應該複製Block。能夠在傳遞時就copy
[[myBlockA copy] autorelease],
或者像這段代碼同樣,在調用的方法裏copy
 複製代碼
@interface Foo : NSObject
{
    void (^myBlock)(NSString *);
}
-(void)setMyBlock:(void (^)(NSString *))inBlock;
@end;

@implementation Foo

-(void)dealloc;
{
    [myBlock release];
    [super dealloc];
}

-(void)setMyBlock:(void (^)(NSString *))inBlock
{
    myBlock = [inBlock copy];
}
@end
複製代碼

當Block被複制時,它會從棧移動到堆上。在塊引用其做用域中定義的局部變量時,局部變量會隨着塊一塊兒移動。全部被引用的 NSObject子類對象都會被保留(retain)而不是複製(由於這些對象原本就已經在堆上 了,而保留的耗時要比複製少一些)。Objective-C 的運行時環境爲Block建立每一個局部變量的常量引用(const reference)。這也意味着默認狀況下塊不能修改上下文數據,以下所示的代碼會致使編譯錯誤。 
int statusCode = 1;

Myblock b = ^{
       statusCode = 4;
};
爲了能修改局部變量,你須要用__block(雙下劃線) 修飾符來 聲明變量。因此 statusCode 的聲明應該是這樣:

__block int statusCode = 1;
用到這個額外的修飾符是爲了命令編譯器當Block被複制時也把變量__block 複製過去。複製是比保留和傳遞常量引用更耗時的操做,因此係統的實現者決定把選擇權交給開發者。

總而言之,__block 是複製而不是保留變量

這樣還能夠避免循環保留致使對象沒法被釋放的問題。

舉個視頻播放的例子,假設咱們在VideoPlayerController裏持有一個AVPlayer類的成員變量_player,若是不加上__block,就會出現循環保留問題。

複製代碼
    __block VideoPlayerController *vpc = self;

    _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
                                                         queue:NULL
                                                    usingBlock:
                                         ^(CMTime time)
                                         {
                                             [vpc doSth];
                                         }];
複製代碼
相關文章
相關標籤/搜索