和我一起來學iOS(二)iOS中的一些約定、模式與三種回調機制


第一節里,我們了解了ObjectC的語法,在第二節里,在正式動手之前,先要了解一些iOS中的基本約定與模式。

 

Foundation.h

我們之所以能夠方便的使用ObjectC中的諸如NSString、NSNumber等類型,是因為在Foundation這個框架中對C語言基本類型進行了封裝,並以對象的形式公開給我們使用。

所以我們在使用前都要#import <Fonduation/Foundation.h>,事實上,XCode會幫我們這么做。

 

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結尾,例如CALayoutManagerCTFontManager。這些通常都是單例。

 

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不是在堆上創建的,而是在棧上(ARC會自動copy,但是block還是在棧上創建的)。主要原因有兩 個,首先是因為性能——在棧上分配空間幾乎總是比在堆上快;其次是出於訪問其他局部變量的需要。

但是,當函數的作用域結束時,棧會被銷毀。如果Block被傳遞給一個方法,此方法會在定義Block的作用域銷毀后才調用到這個Block,所以應該用copy 方法把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 是copy而不是retain

這樣還可以避免循環保留導致對象無法被釋放的問題。

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

    __block VideoPlayerController *vpc = self;
_timeObserver
= [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC) queue:NULL usingBlock:                     ^(CMTime time)                     {                     [vpc doSth];                     }];

 

 

 

 

 

 最后,如果你覺得這篇文章有幫助,請點右下角一下推薦,這是我繼續下去的動力,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM