delegate
委托是協議的一種,顧名思義,就是委托他人幫自己去做事。委托是給一個對象提供機會對另一個對象中的變化做出反應或者影響另一個對象的行為。其基本思想是:兩個對象協同解決問題,并且打算在廣泛的情形中重用。委托指向另一個對象(即它的委托)的引用,并在關鍵時刻給委托發消息。消息可能只是通知委托發生了某件事情,給委托提供機會執行額外的處理,或者消息可能要求委托提供一些關鍵的信息以控制所發生的事情。委托的作用主要有兩個,一個是傳值,一個是傳事件。
傳值常用在B類要把自己的一個數據或者對象傳給A類,讓A類去展示或者處理(切分緊耦合,和代碼分塊時常用)。傳事件是A類發生了什么事,把這件事告訴關注自己的類,也就是委托的對象,由委托的對象去考慮發生這個事件后應該做出什么反映(例如在異步請求中,界面事件觸發數據層改變等)。利用委托賦值,這種方法是為了不暴露自己的屬性就可以給自己賦值,這樣方便了類的管理,只有在你想要讓別人給你賦值的時候才調用,這樣的賦值更可控一些。(如tableView中的委托dateSource等)。
在iOS中委托通過一種@protocol的方式實現,所以又稱為協議。協議是多個類共享的一個方法列表,在協議中所列出的方法沒有響應的實現,由其它類來實現。delegate是“一對一”的關系,對同一個協議,一個對象只能設置一個代理delegate,所以單例對象就不能用代理。代理更注重過程信息的傳輸:如發起一個網絡請求,是否此時請求已經開始、是否收到了數據、數據是否已經接受完成、數據接收失敗等。
從委托類的定義可以看出,委托與協議有一定的相似性。都采用protocol關鍵字來聲明,并且其中的方法都有optional和required,都需要對required方法和調用的optional方法進行實現。而不同的是在委托對象所在的類中需要定義一個delegate對象,并且為id類型。但是delegate與protocol本質上是不同的。Delegate本身應該稱為一種設計模式,是把一個類自己需要做的一部分事情,讓另一個類(也可以就是自己本身)來完成,而實際做事的類為delegate。而protocol是一種語法,它的主要目標是提供接口給遵守協議的類使用,而這種方式提供了一個很方便的、實現delegate模式的機會。
委托模式的實現思路:
1、通常是在對象主體包含一個委托對象的弱引用:
@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
2、委托對象的實現有兩種方式:一種是必須實現,一種是可選實現,即@required和@optional的區別。
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
@optional
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
3、判斷觸發委托方法。
//確定委托是否存在Entered方法
if([delegate respondsToSelector:@selector(method:)])
{
//發送委托方法,方法的參數為用戶的輸入
[delegate method:xxx];
}
4、連接對象主體和委托,通過setDelegate:(id)obj來實現。
_test.delegate = self;
5、如果實現委托的類有委托需要的方法就執行方法。大概就是這樣。
Block
Block是Apple Inc.為C、C++以及Objective-C添加的特性,使得這些語言可以用類lambda表達式的語法來創建閉包,是iOS4.0以后和Mac OS X 10.6以后引進的對C語言的擴展,用來實現匿名函數的特性。 Block能夠讀取其它函數內部變量的函數,在一段請求連續代碼中可以看到調用參數(如發送請求)和響應結果。采用Block技術能夠抽象出很多共用函數,提高了代碼的可讀性,可維護性,封裝性。
block寫法更簡練,不需要寫protocol、函數等等;block注重結果的傳輸:比如對于一個事件,只想知道成功或者失敗,并不需要知道進行了多少或者額外的一些信息;block需要注意防止循環引用。
block的寫法大概就是這樣:
void (^blockTest)(void) = ^{
NSLog(@"block");
};
blockTest();
如果用block進行兩個類之間的互動,需要這樣:
BLOCK首先你在.h文件中聲明BLOCK對象,當然返回的參數你可以自己定義:
typedef void (^blockTest)();
@property (nonatomic,copy) blockTest myblock;
在.m文件中執行時:
if(_myblock){
_myblock();
}
其他類執行block時是這樣的:
_testClass.myblock = ^{
NSLog(@"block");
}
需要注意的是:
1.循環引用問題,如果block里用了self,需要替換為
__weak typeof(self) weakSelf = self;
2.block所在函數中的,捕獲自動變量,但是不能修改它,不然就是編譯錯誤。但是可以改變全局變量、靜態變量、全局靜態變量。為何不讓修改變量:這個是編譯器決定的。理論上當然可以修改變量了,只不過block捕獲的是自動變量的副本,名字一樣。為了不給開發者迷惑,干脆不讓賦值。靜態變量屬于類的,不是某一個變量。所以block內部不用調用self指針,所以block可以調用。解決block不能保存值這一問題的另外一個辦法是使用__block修飾符。
block和delegate的區別
block不像代理聲明了一個代理函數,在調用的類內部還要實現該函數,若一個頁面能發送多個請求,并且用多點觸控同時觸發發送多個請求,那個這個頁面的代理函數很難區分是那個請求的結果,只有你的響應消息中帶有消息類型可能會分出來,若服務器做的不夠強大,當出現異常時,找不發送請求,對于開發來說是個問題。這樣多個消息在一個函數里解析也不利于封裝。 Block比代理更清晰, Block可以在創建事件的時候區分開來。這也是為什么現在蘋果 API 中越來越多地使用 Block而不是 Delegate。
block有三個存儲區域_NSConcretStackBlock ,_NSConcretGlobalBlock和_NSConcretMallocBlock。正如它們名字說的那樣,說明了block的三種存儲方式:棧、全局、堆。如果是定義在函數外面的block是global的,另外如果函數內部的block但是沒有捕獲任何自動變量,那么它也是全局的。
typedef int (^blk_test)( int );
for(...){
blk_test blk = ^(int count) {return count;};
}
雖然,這個block在循環內,但是blk的地址總是不變的。說明這個block在全局段。在棧上block調用copy那么復制到堆上,在全局block調用copy什么也不做,在堆上調用block引用計數增加。
測試方法如下:
typedef int (^blkt1)(void) ;
-(void) stackOrHeap{
__block int val =10;
int *valPtr = &val;//使用int的指針,來檢測block到底在棧上,還是堆上
blkt1 s= ^{
NSLog(@"val_block = %d",++val);
return val;};
s();
NSLog(@"valPointer = %d",*valPtr);
}
int val肯定是在棧上的,我保存了val的地址,看看block調用前后是否變化。輸出一致說明是棧上,不一致說明是堆上。有興趣的可以手動試一下ARC和MRC下的結果。
由此我們可以看到delegate運行成本低,block成本很高。block出棧需要將使用的數據從棧內存拷貝到堆內存,當然對象的話就是加計數,使用完或者block置nil后才消除;delegate只是保存了一個對象指針,直接回調,沒有額外消耗。相對C的函數指針,只多做了一個查表動作 。
一般來說公共接口,方法也較多用delegate進行解耦 ,iOS有很多例子如最常用tableViewDelegate,textViewDelegate等。異步和簡單的回調用block更好 ,iOS有很多例子如常用的網絡庫AFNetwork等。