1. 程式人生 > >iOS 代理和block的理解

iOS 代理和block的理解

首先兩者作用是一樣的,都是進行單一回調。
不通的是,delegate是個物件,然後用過一個物件自己呼叫代理協議函式來完成整個流程。
block是傳遞一個函式指標,利用函式指標執行來進行回撥。
還有在記憶體管理上需要注意,delegate不需要儲存引用。block對引用資料有copy的處理。

1.block型別-儲存程式碼塊的型別

在非同步程式設計時常需要進行函式回撥,在C#中會用匿名委託或者lambda表示式講一個操作作為引數進行傳遞.
ObjC中是使用對於閉包的實現,在塊狀中我們可以持有或引用區域性變數. 同時利用Block可以將一個操作作為引數進行傳遞;

blcok用法:

  • 定義:返回值型別 ( ^變數名 ) ( 形參型別 );
  • 賦值:變數名=^(形參){
    程式碼塊+形參變數
    };
  • 使用:變數(實參); 

例: 

    int (^myBlcok)(int ,int)=^(int m,int n){
      return  m+n;
      }; //無引數時大括號前()可省略

      myBlock(10,5);    //呼叫塊,省略了接受塊返回值;

總結:經過簡單瞭解C與OC;發現從最小的一個變數到表示式再到一個函式,其實只起兩點作用: 值(返回值) 與 功能(行為,方法,作用).
所以說一行程式碼,按它是使用了值 還是 功能來解讀比較容易理解. 

Block做使用場景:

  • 如果回撥方法比較少,1~2,最好不要超過3個,這個時候使用block比較合適
  • 如果回撥方法非常多,同時又不用每一個方法都必須實現,這個時候用delegate會比較方便!

block傳值的迴圈引用問題:

只有當block直接或間接的被self持有時,在block使用self時才需要替換為weak self。如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。

   __weak __typeof__(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        __strong __typeof(self
) strongSelf = weakSelf; [strongSelf doSomething]; [strongSelf doOtherThing]; });

typedef block格式

類似函式指標,直接在定義格式之前加 typedef關鍵字,之後變數名就是型別的別名了.
typedef viod (^別名)(形參);
一般以後需要使用block作為函式方法的引數時,為方便最好用別名.而在block作用返回值時,一定需要別名,因為編譯器不能識別做此時型別做何種解釋.
延伸:經過測試,block在編譯時按程式碼順序,而執行時按呼叫順序(變數作用域)

使用例子:
KCButton.h

#import <Foundation/Foundation.h>
@class KCButton;
typedef void(^KCButtonClick)(KCButton *);

@interface KCButton : NSObject

#pragma mark - 屬性
@property (nonatomic,copy) KCButtonClick onClick;

#pragma mark 點選方法
-(void)click;
@end

KCButton.m

#import "KCButton.h"


@implementation KCButton

-(void)click{
    NSLog(@"Invoke KCButton's click method.");
    if (_onClick) {
        _onClick(self);
    }
}

@end

main.m

   KCButton *button=[[KCButton alloc]init];
    button.onClick=^(KCButton *btn){
        NSLog(@"Invoke onClick method.The button is:%@.",btn);
    };
    [button click];
    /*結果:
     Invoke KCButton's click method.
     Invoke onClick method.The button is:<KCButton: 0x1006011f0>.
     */

block訪問外部變數

  • block內部可以訪問外部區域性變數,但是此時是const copy方式,地址不同,相當於值傳遞,只讀的.如果外部定義時加字首__block時,內部可改變外部局變值.
  • block內部如果建立了和外部同名的變數,會遮蔽外部作用域.此時內部的變數也存在棧區;
    原因:block本質是程式碼塊,ARC下建立的時候在堆區,此時程式碼只是單純儲存,沒有功能;當呼叫的時候,相當於程式碼增加到main中,這樣程式碼塊中建立的變數就跟正常的一樣; (block呼叫完成內部變數即釋放,而堆區的只在釋放block時一起釋放).
  • 如果是靜態變數(static修飾局變,生命週期延長,儲存在資料區(同初始化的全域性))和全域性變數.地址傳遞.此時block儲存在全域性區.
  • 常量字串@"abc",加__block會引用常量變數(如:a變數,a = @"abc",內部可以任意修改a 指向的內容)的地址。不加block就是@"abc"本身地址,不可變;

三種類型block

根據block在記憶體中的位置
"NSGlobaBlock"類似函式,存於程式碼區--全域性block
"NNStackBlock"棧區,函式返回後的Block--棧
"NSMallocBlock"block--堆
  1. block內沒有使用外部變數或是隻使用了全域性/靜態變數時.存於全域性程式碼區,為全域性block;---(ARC和MRC下一致)
  2. 當使用外部變數時
    • MRC下,block程式碼存於棧區;如果此外部變數A存於區,那麼A會被copy到block分配的區;如果A是存於區,那麼A在block塊內與快外相同.
    • ARC下,block程式碼存於堆區.如果此外部變數A存於區,那麼A會被copy到block分配的區;如果A是存於區,那麼A在block塊內與快外相同.
  3. 如果需要修改外部變數,需要在變數前面宣告__Block;
    當使用下劃線Block修飾外部變數時:
    • MRC下,無論變數A存於還是區,A在block塊內與快外相同;
    • ARC下,如果此外部變數A存於區,那麼A會被轉移而不是複製區;如果A是存於區,那麼A在block塊內與快外相同.

面試題:block的@property引數(記憶體管理引數)為什麼要用copy:如果不用copy,此時不論ARC還是MRC都是棧Bolck,棧block會提前釋放,導致無法繼續使用;可以copy到堆區手動管理記憶體.(而字串copy是防止字串如果是非常量的,外部可變,造成非預估的結果;)

block在MRC下得記憶體隱患(NNStacKBlock)

Block_copy將block及內部變數拷貝到堆區.
使用完畢用Blok_release(block變數)釋放此堆區空間;

block使用技巧

  1. block結構快速顯示:inlineBlock...(也可右下角自定義快速顯示其他格式)
  2. Block作為方法引數時,最好把引數列表部分加上,這樣後面呼叫方法時,會自動有格式;
  3. 做方法引數時,需要加上返回值型別;
  4. 做返回值時,先定義別名,最後別忘記執行返回值;
  5. 方法中,void(^)()表示block型別同int,做參和返回值;做例項變數
    @property (nonatomic, copy) void(^變數名)()
  6. get點語法獲取block型別例項變數時,自動執行,後面需加();

2.protocl協議的概念及使用

在ObjC中使用@protocol定義一組方法規範. 實現此協議的類 也必須實現對應的方法. 面向物件的語言介面本身是物件行為描述的 協議規範, 也就是說ObjC中@protocol和其他語言的介面定義是類似的, 只是ObjC的@interface關鍵字已經用於定義類了, 因此不會像C# 和Java中那樣使用interface來定義介面;

1.定義:

只宣告而不實現方法 ,而讓遵守此協議的類實現協議宣告的方法;
在.h 中

    @protocol 協議名 <系統協議>,<其他協議2>...  
    //方法宣告列表  
    @end
2.類遵守協議:

類宣告檔案中:

@interface 類名:父類<協議1,協議2>
@end
3.實現協議:

只需在此類的.m檔案中實現協議宣告方法即可(根據協議定義時@option的關鍵字,有些必須實現,有些選擇實現);

4.應用:
  1. 協議只宣告多個方法,不實現, 不能定義屬性;
  2. 只要遵守協議,就可以擁有協議的所有方法宣告;
  3. 一個類遵守協議而實現的方法,宣告和實現都會遺傳給子類,而沒有實現的方法宣告會遺傳給子類,即協議可以 繼承 ;
  4. 協議和類可以多對多,而一個協議又可以繼承其他一個或多個協議.
  5. 所有協議預設遵守NSObject的基協議,其中聲明瞭很多基本方法.
    • 協議中有兩個關鍵字:@required(預設)必須實現的. 
    • @optional 選擇實現;作用域類似@public等關鍵字作用域

兩大作用

1. 做型別限制:程式設計師間交流

格式:
id<協議名>obj;//表明 如果要給obj賦值,則賦值的物件必須遵守"協議"才可以賦值.不滿足會有警告;
即id<協議名> obj=[類1 new],必須類1要遵守此協議;id 可換成類名,記得加*;
引申的,在物件做例項變數時也可限制,例:@prperty Dog<協議> *dog

2.代理設計模式
使兩個不同意義的事物分離解耦.
一種常見程式設計思想:有些事自己不好做,找代理做;傳入的物件,代替當前類完成摸個功能;
原理:物件關聯關係的型別限制(A物件擁有遵守特定協議的物件B)白手套作為主子的例項變數;
應用場合:

  • 當A發生了一些行為(ˇˍˇ) 想~告訴B或B想監聽物件A的一些行為 
  • A無法處理某些行為的時候,讓B幫忙;

思路:

  1. 先定義一個協議.
  2. 定義代理類,遵守此協議.
  3. 主類定義限定此協議型別的屬性.
  4. 主類中要有行為 來觸發代理.
  5. 設定代理.

  6. 用父類還是id<協議>

  7. 不使用父類的原因:
  8. 如果抽象一個父類的話, 還是有侷限性, 因為很多時候, 不同類是無法抽象出共同的父類
    的. , 不能多繼承

3.Category

  1. 作用:在不修改原類的情況下擴充套件其他功能
  2. 目的:對類方法進行歸類,便於分模組開發大型類,團隊協作.
    宣告:@interface 類名(分類名)
    實現:@implementation 類名
    使用原類直接呼叫

注意:1)分類不能擴充套件任何新的例項變數; 

  • 2)分類可以訪問原類的成員變數;(相當於原類方法呼叫) 
  • 3)如果分類中存在和原類同名方法,優先使用分類(功能更新)
  • 4)如果多個分類都有同名方法,呼叫最後一個參與編譯的分類同名方法;在Compile Sources 檔案編譯可以看到的的順序; 
分類的非正式協議:(面試題)

就是NSObject(和系統類框架)的類別(分類)即系統類的分類,增加了所有類的功能;類別介面中指定的方法可能會或者可能不會被框架類實際地實現,而是被子類重寫. 

分類的延展(匿名分類)Extendsion

延展是分類一個特例,匿名.且新新增的方法一樣要予以實現

@interface MyClass () {
    float value;    //可以新增例項變數
 }
-(void)setValue:(float)newValue;
@end

使用:

  1. 可以在原類.h直接宣告,原類.m中實現;不需要自己的.m檔案;
  2. 主要用於新增類的私有方法和私有變數.()