iOS中代理、通知、block的使用

一、代理

1、代理設計模式
代理也稱“委託”,就是一件事情發生後,自己不處理,讓別人去處理。其目的爲了在程序直接解藕,讓程序關係不是很緊密。
代理是一對一的關係

2、代理的應用場景
監聽思想:當A對象想監聽B對象的變化的時候,就可以讓A對象成爲B對象的代理
通知思想:當B對象發生了變化的時候想通知A對象,就可以讓A對象成爲B對象的代理

3、代理的命名規範
協議名稱:控件名稱+delegate
協議方法名稱:控件的名稱去掉前綴 + 含義
在協議方法中將自己(觸發方法的)控件傳出去的目的是方便用於區分那個控件出發了該方法


4、代理的代碼實現

#import <UIKit/UIKit.h>
@class MyView;

//1、指定代理的協議方法
@protocol MyViewDelegate <NSObject>

@required
//注:代理方法的返回值爲視圖控制器向控件發送的數據(即通知控件接收數據),控制器看做代理,視圖看做委託。
- (void)myViewChangeColor:(MyView *)myView;
@end

@interface MyView : UIView
/** 2、設置代理屬性 * 設置代理屬性 * weak修飾符:是防止循環引用 */
@property (nonatomic, weak)id <MyViewDelegate>delegate;

@end


@implementation MyView

- (void)click{
    
    //直接這樣調用代理的方法,會導致程序崩潰
//    [self.delegate myViewChangeColor:self];
    //3、當發生了事情,讓代理對象去做
    //判斷代理是否實現了某個方法
    if([self.delegate respondsToSelector:@selector(myViewChangeColor:)]){
        [self.delegate myViewChangeColor:self];
    }

}

- (void)viewDidLoad {
    [super viewDidLoad];
    MyView *myView = [[MyView alloc]initWithFrame:CGRectMake(0, 60, 375, 500)];
    
    myView.backgroundColor = [UIColor purpleColor];
    //4、設置代理
    myView.delegate = self;
    [self.view addSubview:myView];
    
}

//5、實現代理的協議方法
- (void)myViewChangeColor:(MyView *)myView{
    
    myView.backgroundColor = [UIColor redColor];
    
}

5、代理的weak修飾符,爲什麼防止循環引用




二、通知

1、通知的簡介

每一個應用程序都有一個通知中心(NSNotificationCenter)實例,專門負責協助不同對象之間的消息通信;
任何一個對象都可以向通知中心發佈通知(NSNotification),描述自己在做什麼。其他感興趣的對象(Observer)可以申請在某個特定通知發佈時(或在某個特定的對象發佈通知時)收到這個通知。

2、通知的簡單使用

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    //1、監聽通知,通知的順序:先監聽,後發送
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reciveNote) name:@"note" object:nil];
}

// 一個對象即將銷燬就會調用
- (void)dealloc
{
    //3、移除通知
    [[NSNotificationCenter defaultCenter] removeObserver:_observe];
}
@end

@implementation ZMViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //2、發送通知
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@-----", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"note" object:nil userInfo:nil];
    });
}

@end

3、通知在多線程的使用

接收通知代碼 由 發出通知線程決定

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 監聽通知:異步
    // 在異步線程,可以監聽通知
    // 2.異步任務,執行順序不確定
    // 異步:監聽通知 主線程:發出通知
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       // 異步任務
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reciveNote) name:@"note" object:nil];
    });
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // 主線程發出通知
    [[NSNotificationCenter defaultCenter] postNotificationName:@"note" object:nil];
}

// 一個對象即將銷燬就會調用
- (void)dealloc
{
    // 移除通知
    [[NSNotificationCenter defaultCenter] removeObserver:_observe];
}


// 總結:接收通知代碼 由 發出通知線程決定
- (void)reciveNote
{
    // 注意:在接收通知代碼中 可以加上主隊列任務
    // 更新UI
    dispatch_sync(dispatch_get_main_queue(), ^{
       // 更新UI
        
    });
}


三、block

1、block的聲明和定義
block也稱爲代碼塊,主要用來保存一段代碼,讓它在合適的時候執行

//1.block的聲明:返回值(^block變量名)(參數)
    void(^block)();
    
    //2.block定義:三種定義方式
    //方式一:
    void(^block1)() = ^(){
        NSLog(@"調用了block1");
    };
    //方式二:block如果沒有參數,可以省略
    void(^block2)() = ^{
        
    };
    //方式三:block的返回值可以省略,不管有沒有返回值,都可以省略
    int (^block3)() = ^ int {
        return 3;
    };
    
    //3.block的類型:int(^)(NSString *)
    int (^block4)(NSString *) = ^(NSString *name) {
        return 2;
    };
    
    //4.block的調用
    block1();
    
    //5.block的快捷方式
    //直接敲:inlineblock
    <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
        <#statements#>
    };

2、block的內存管理

block是不是一個對象?
是,官方文檔
 
如何判斷文件是MRC或者ARC?
在dealloc方法中能否用super;能否使用retain,release
 
ARC管理原則:只要一個對象沒有被一個強指針修飾,就會被銷燬。默認的局部變量對象都是強指針,存放到堆裏面。在ARC中基本數據類型存放在棧裏面進行管理的。

 MRC開發的常識:
MRC沒有strong和weak,局部變量對象相當於基本數據類型;MRC給成員屬性賦值,一定要使用set方法,不能直接訪問下劃線成員屬性賦值。


堆、棧、全局區的瞭解

棧區(stack):由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。
     
堆區(heap):一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。
     
全局區(靜態區)(static):全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域,程序結束後有系統釋放。
     
文字常量區:常量字符串就是放在這裏的,程序結束後由系統釋放。
     
程序代碼區:存放函數體的二進制代碼。


2.1、MRC環境下

如果block沒有引用外部的局部變量,block放在全局區
如果block引用了外部的局部變量,block就放在棧裏面
在MRC中,block只能使用copy,不能使用retain。使用retain,則block存放在棧裏面

2.2、ARC環境下

在ARC中,block用strong,最好不要用copy
在ARC環境下,block沒有引用外部局部變量,block存放在全局區
在ARC環境下,block引用了外部的局部變量,block存放在堆裏面,但是還是不能要weak修飾來引用block


3、block在開發中的使用

3.1、作爲對象的屬性,在恰當時後調用

@interface CellItem : NSObject

//設計模型:控件需要展示什麼內容,就定義什麼屬性
@property (nonatomic, strong)NSString *title;

//保存點擊每個cell需要做的事情
@property (nonatomic, strong)void (^blockName)();

+ (instancetype)itemWithTitle:(NSString *)title;

@end
- (void)viewDidLoad {
    [super viewDidLoad];
    //創建cell模型
    CellItem *item1 = [CellItem itemWithTitle:@"打電話"];
    item1.blockName = ^{
        NSLog(@"打電話");
    };
    CellItem *item2 = [CellItem itemWithTitle:@"發短信"];
    item2.blockName = ^{
        NSLog(@"發短信");
    };

    CellItem *item3 = [CellItem itemWithTitle:@"喫飯啦"];
    item3.blockName = ^{
        NSLog(@"喫飯啦");
    };

    _dataArr = @[item1, item2, item3];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    //把要做的事情保存到模型中的block中
    CellItem *item = self.dataArr[indexPath.row];
    
    item.blockName();
}

3.2、作爲方法的參數

@interface CaculorManager : NSObject

@property (nonatomic, assign)NSInteger result;

//計算
- (void)caculator: (NSInteger (^)(NSInteger num))caculatorBlock;

@end

@implementation CaculorManager

- (void)caculator: (NSInteger (^)(NSInteger num))caculatorBlock{
    if(caculatorBlock){
        _result = caculatorBlock(6);
    }
}

@end

#import "CaculorManager.h"

//什麼時候需要把block當作參數去使用呢?做的事情由外界決定,但什麼時候做由內部決定。
// 把block當做參數,並不是馬上就調用Block,什麼時候調用,由方法內部決定

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CaculorManager *mag = [[CaculorManager alloc] init];
    [mag caculator:^NSInteger(NSInteger num) {
        num += 5;
        num *= 4;
        return num;
    }];
    NSLog(@"%ld", mag.result);
}
@end

3.3、作爲方法的返回值

@interface CaculatueManager : NSObject

@property (nonatomic, assign)int result;

- (CaculatueManager * (^)(int))add;

@end

@implementation CaculatueManager

- (CaculatueManager * (^)(int))add{
    return ^(int value) {
        
        _result += value;
        
        return self;
    };
}
@end

#import "CaculatueManager.h"

/** 寫的過程中不斷優化代碼,就會形成一種思想
 *  鏈式編程思想:把所有的語句用.號鏈接起來,好處:可讀性非常好,如:Masonry框架
 */

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.test();
    
    //實現鏈式編程的思想
    CaculatueManager *mag = [[CaculatueManager alloc] init];
    mag.add(5).add(4).add(2);
    
    NSLog(@"%d", mag.result);
}

- (void (^)())test{
    NSLog(@"%s", __func__);
    return ^{
      NSLog(@"調用了block");
    };
}

@end

4、block造成循環引用的問題

循環引用:你引用我,我引用你,就會造成循環引用。雙方都不會銷燬,導致內存泄漏

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    ModalViewController *modalVc = [[ModalViewController alloc] init];
    modalVc.view.backgroundColor = [UIColor redColor];
    
    //模態出來的控制器會被rootViewController的屬性強引用
//    self.presentedViewController
    
    [self presentViewController:modalVc animated:YES completion:nil];
}

@end

#import "ModalViewController.h"

@interface ModalViewController ()

@property (nonatomic, strong)void (^block)();

@end

@implementation ModalViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //block循環引用:block會對它裏面的所有外部變量進行強引用一次。
    //把self包裝成弱指針
    //typeof用來獲取self的類型
    __weak typeof(self) weakSelf = self;
    _block = ^{
        //strongSelf是一個局部變量,在這個_block棧裏面,
        //主要的作用是,防止控制器已經dismiss了,但是block裏面的事情沒有做
        __strong typeof(self) strongSelf = weakSelf;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
//            NSLog(@"%@", weakSelf);
            NSLog(@"%@", strongSelf);
        });
    };
    
    _block();

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //如果控制器被dismiss就會被銷燬
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)dealloc{
    NSLog(@"%s", __func__);
}

@end

5、block值傳遞

@implementation ViewController

int d = 9;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    int a = 3;
    static int b = 5;
    __block int c = 7;
    
    //如果是給block傳遞局部變量,block是值傳遞
    //如果給block傳遞的是靜態、全局的變量、__block修飾的局部,block是指針傳遞
    void (^block) () = ^{
        NSLog(@"a = %d, b = %d, c = %d, d = %d", a, b, c, d);
    };
    
    a = 5;
    b = 7;
    c = 8;
    d = 10;
    
    block();
}
@end

6、block在開發中使用的場景

block的聲明和定義,直接敲"inlineBlock"
* block的作用:用於保存一份代碼,等到恰當時機再去調用。
* block類型的屬性,在ARC中用strong,在MRC中用copy
 
block開發中的使用場景:
1.block保存到對象中的屬性,在恰當的時候使用
2.block作爲方法的參數使用,block做的事情由外界覺得,但是外界不調用,方法內部調用block
3.把block作爲方法的返回值,目的就是爲了代替方法,封裝方法內部的實現,在外界調用block.

block使用場景二:
傳值:1、只要能拿到對方就能傳值
 
順傳:給需要傳值的對象,直接定義它屬性,然後進行傳值
逆傳:用代理,block,在開發中就是利用block代替代理。在進行逆傳需要把前一個控制器保存起來,然後逆向跳轉回去時才能傳值。


總結:

1、能用代理的地方都能用block嗎?

2、代理和block的最大的區別:在於方法的數量上。

block的代碼都在一起,非常直觀;代理傳遞消息是爲了在控制器和view之間解藕,讓視圖能夠被多個控制器服用。如果視圖只是爲了封裝代碼,而且不被其它控制器使用,可以直接通過addTarget方式添加按鈕方法,這樣做的冗餘度高。

3、代理和通知的區別:代理是一對一,通知是一對多;通知沒有返回值,一般沒有級聯關係的用通知,代理有返回值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章