IOS基礎(chǔ)使用:Delegate、Protocol

原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章
創(chuàng)作不易,請(qǐng)珍惜,之后會(huì)持續(xù)更新,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié),畢竟好記性不如爛筆頭哈哈,這些文章記錄了我的IOS成長(zhǎng)歷程,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書不支持目錄跳轉(zhuǎn),大家可通過command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容

目錄

  • 一、比較
    • 1、代理和block的選擇
    • 2、Delegate、Notification、KVO的優(yōu)缺點(diǎn)比較
  • 二、跨層傳值
    • 1、屬性正向傳值
    • 2、KVC正向傳值
    • 3、Delegate逆向傳值
    • 4、Block逆向傳值
    • 5、KVO逆向傳值
    • 6、Notification反向傳值
    • 7、NSUserDefaults傳值
  • 三、Delegate與Protocol的用法
    • 1、簡(jiǎn)介
    • 2、計(jì)算人數(shù)工具類
    • 3、員工
    • 4、技術(shù)總監(jiān)
    • 5、調(diào)用方式
  • Demo
  • 參考文獻(xiàn)

一、比較

1、代理和block的選擇

多個(gè)消息傳遞,應(yīng)該使用delegate,這個(gè)時(shí)候block反而不便于維護(hù),而且看起來非常臃腫,很別扭。如果UITableView中很多代理都換成block實(shí)現(xiàn),我們腦海里想一下這個(gè)場(chǎng)景是很可怕的。

一個(gè)委托對(duì)象的代理屬性只能有一個(gè)代理對(duì)象,如果想要委托對(duì)象調(diào)用多個(gè)代理對(duì)象的回調(diào)應(yīng)該用block,因?yàn)?code>delegate只是一個(gè)保存某個(gè)代理對(duì)象的地址,如果設(shè)置多個(gè)代理相當(dāng)于重新賦值,只有最后一個(gè)設(shè)置的代理才會(huì)被真正賦值。

單例對(duì)象最好不要用delegate。單例對(duì)象由于始終都只是同一個(gè)對(duì)象,如果使用delegate,就會(huì)造成我們上面說的delegate屬性被重新賦值的問題,最終只能有一個(gè)對(duì)象可以正常響應(yīng)代理方法。

代理是可選的,而block在方法調(diào)用的時(shí)候只能通過將某個(gè)參數(shù)傳遞一個(gè)nil進(jìn)去實(shí)現(xiàn)同樣的效果,只不過這并不是什么大問題,沒有代碼潔癖的可以忽略。

從設(shè)計(jì)模式的角度來說,代理更佳面向過程,而block更佳面向結(jié)果。例如我們使用NSXMLParserDelegate代理進(jìn)行XML解析,NSXMLParserDelegate中有很多代理方法,NSXMLParser會(huì)不間斷調(diào)用這些方法將一些轉(zhuǎn)換的參數(shù)傳遞出來,這就是NSXMLParser解析流程,這些通過代理來展現(xiàn)比較合適。而例如一個(gè)網(wǎng)絡(luò)請(qǐng)求回來,就通過success、failure代碼塊來展示就比較好。

從性能上來說,block的性能消耗要略大于delegate,因?yàn)?code>block會(huì)涉及到棧區(qū)向堆區(qū)拷貝等操作,時(shí)間和空間上的消耗都大于代理。而代理只是定義了一個(gè)方法列表,在運(yùn)行時(shí)向遵守協(xié)議的對(duì)象發(fā)送消息即可。


2、Delegate、Notification、KVO的優(yōu)缺點(diǎn)比較

Delegate
優(yōu)勢(shì)
  • 如果delegate中的一個(gè)方法沒有實(shí)現(xiàn)那么就會(huì)出現(xiàn)編譯警告/錯(cuò)誤
  • 一個(gè)控制器中可以實(shí)現(xiàn)多個(gè)不同的協(xié)議
  • 能夠接收返回值
  • 一對(duì)一的通信
缺點(diǎn): 需要定義很多代碼
  • 協(xié)議定義
  • controllerdelegate屬性
  • 實(shí)現(xiàn)delegate方法
Notification
優(yōu)勢(shì)
  • 代碼量少,實(shí)現(xiàn)簡(jiǎn)單
  • 1對(duì)多的通信
  • 可以攜帶自定義消息
缺點(diǎn)
  • 需要在不用的時(shí)候注銷通知
  • 調(diào)試難以追蹤
  • 通知發(fā)送后,不能從觀察者得到任何反饋信息
  • 代碼可讀性不強(qiáng)
  • notifacationName必須相同,否則無法接受消息
KVO
優(yōu)勢(shì)
  • key paths來觀察屬性,因此可以觀察嵌套對(duì)象
  • 能夠提供一種簡(jiǎn)單的方法實(shí)現(xiàn)兩個(gè)對(duì)象間的同步
  • 能夠?qū)Ψ俏覀儎?chuàng)建的對(duì)象,即內(nèi)部對(duì)象的狀態(tài)改變做出響應(yīng),而且不需要改變內(nèi)部對(duì)象的實(shí)現(xiàn)
缺點(diǎn)
  • 觀察的屬性必須使用 string 來定義,因此編譯器不會(huì)出現(xiàn)警告
  • 對(duì)屬性重構(gòu)將導(dǎo)致我們的觀察代碼不再可用

二、跨層傳值

1、屬性正向傳值

當(dāng)從第一個(gè)頁面push到第二個(gè)頁面時(shí),第二個(gè)頁面需要使用到第一個(gè)頁面的數(shù)據(jù),這時(shí)就可以使用正向傳值。

界面一

這樣傳遞是有問題的,因?yàn)樽禹撁嬷械?code>textfield是在viewDidLoad中進(jìn)行初始化和布局的,但在這時(shí)候textfield還沒有初始化,為nil,所以賦值是失效的。

- (void)proprety
{
    SecondViewController *postVC = [[SecondViewController alloc] init];
    postVC.content = @"劉盈池";
    postVC.contentTextField.text = @"謝佳培";
    
    [self.navigationController pushViewController:postVC animated:YES];
}
界面二
- (void)proprety
{
    NSLog(@"屬性正向傳值,content內(nèi)容為:%@",self.content);
    NSLog(@"屬性正向傳值,contentTextField內(nèi)容為:%@",self.contentTextField.text);
}

輸出結(jié)果為:

2020-09-23 17:03:02.553646+0800 Demo[92767:17573694] 屬性正向傳值,content內(nèi)容為:劉盈池
2020-09-23 17:03:02.553825+0800 Demo[92767:17573694] 屬性正向傳值,contentTextField內(nèi)容為:

2、KVC正向傳值

通過Key名給對(duì)象的屬性賦值,而不需要調(diào)用明確的存取方法,這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)地訪問和修改對(duì)象的屬性。

界面一
- (void)useKVC
{
    SecondViewController *postVC = [[SecondViewController alloc] init];
    [postVC setValue:@"劉盈池" forKey:@"content"];
    
    [self.navigationController pushViewController:postVC animated:YES];
}
界面二
- (void)useKVC
{
    NSLog(@"KVC正向傳值,content內(nèi)容為:%@",self.content);
}

輸出結(jié)果為:

2020-09-23 17:57:13.984068+0800 Demo[93502:17615940] KVC正向傳值,content內(nèi)容為:劉盈池

3、Delegate逆向傳值

在從第二個(gè)頁面返回第一個(gè)頁面的時(shí)候,第二個(gè)頁面會(huì)釋放掉內(nèi)存,如果需要使用子頁面中的數(shù)據(jù)就用到了逆向傳值。

界面一

在第一個(gè)頁面中遵從該代理。

@interface FirstViewController ()<contentDelegate>

第二個(gè)頁面的代理是第一個(gè)頁面自身self

- (void)useDelegate
{
    SecondViewController *postVC = [[SecondViewController alloc] init];
    postVC.delegate = self;
    [self.navigationController pushViewController:postVC animated:YES];
}

實(shí)現(xiàn)代理中定義的方法,第二個(gè)頁面調(diào)用的時(shí)候會(huì)回調(diào)該方法。在方法的實(shí)現(xiàn)代碼中將參數(shù)傳遞給第一個(gè)頁面的屬性

- (void)transferString:(NSString *)content
{
    self.title = content;
    NSLog(@"Delegate反向傳值,第一個(gè)頁面接收到的content為:%@",content);
}
界面二

聲明代理

@protocol contentDelegate <NSObject>

/** 代理方法 */
- (void)transferString:(NSString *)content;

@end

聲明代理屬性

@interface SecondViewController : UIViewController

@property(nonatomic, weak) id<contentDelegate> delegate;

@end

返回第一個(gè)頁面之前調(diào)用代理中定義的數(shù)據(jù)傳遞方法,方法參數(shù)就是要傳遞的數(shù)據(jù)。如果當(dāng)前的代理存在,并且實(shí)現(xiàn)了代理方法,則調(diào)用代理方法進(jìn)行傳遞數(shù)據(jù)。

- (void)backDelegate
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(transferString:)])
    {
        [self.delegate transferString:@"劉盈池"];
        [self.navigationController popViewControllerAnimated:YES];
    }
}

輸出結(jié)果為:

2020-09-23 17:21:45.923769+0800 Demo[92974:17584680] Delegate反向傳值,第一個(gè)頁面接收到的content為:劉盈池

4、Block逆向傳值

界面一

通過子頁面的block回傳拿到數(shù)據(jù)后進(jìn)行處理,賦值給當(dāng)前頁面的textfield。

- (void)useBlock
{
    SecondViewController *postVC = [[SecondViewController alloc] init];
    postVC.transDataBlock = ^(NSString * _Nonnull content) {
        self.title = content;
        NSLog(@"Block逆向傳值,第一個(gè)頁面接收到的content為:%@",content);
    };
    
    [self.navigationController pushViewController:postVC animated:YES];
}
界面二

聲明block,用于回傳數(shù)據(jù)

typedef void(^TransDataBlock)(NSString *content);

定義一個(gè)block屬性,用于回傳數(shù)據(jù)

@property(nonatomic, copy) TransDataBlock transDataBlock;

@property (nonatomic, copy) void(^ TransDataBlock)(NSString * content);

調(diào)用block并將數(shù)據(jù)作為參數(shù)回傳。

- (void)backBlock
{
    if (self.transDataBlock)
    {
        self.transDataBlock(@"劉盈池");
    }
    [self.navigationController popViewControllerAnimated:YES];
}

輸出結(jié)果為:

2020-09-23 17:29:40.121002+0800 Demo[93133:17594231] Block逆向傳值,第一個(gè)頁面接收到的content為:劉盈池

5、KVO逆向傳值

界面一

在第一個(gè)頁面注冊(cè)觀察者。

- (void)useKVO
{
    self.secondVC = [[SecondViewController alloc] init];

    [self.secondVC addObserver:self forKeyPath:@"content" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
    
    [self.navigationController pushViewController:self.secondVC animated:YES];
}

實(shí)現(xiàn)KVO的回調(diào)方法,當(dāng)觀察者中的數(shù)據(jù)有變化時(shí)會(huì)回調(diào)該方法。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"content"])
    {
        self.title = self.secondVC.content;
        NSLog(@"KVO反向傳值,第一個(gè)頁面接收到的content為:%@",self.secondVC.content);
    }
}

在第一個(gè)頁面銷毀時(shí)移除KVO觀察者。

- (void)dealloc
{
    [self.secondVC removeObserver:self forKeyPath:@"content"];
}
界面二
- (void)backKVO
{
    // 修改屬性的內(nèi)容
    self.content = @"劉盈池";

    // 返回第一個(gè)界面回傳數(shù)據(jù)
    [self.navigationController popViewControllerAnimated:YES];
}

輸出結(jié)果為:

2020-09-23 17:46:10.744468+0800 Demo[93363:17607725] KVO反向傳值,第一個(gè)頁面接收到的content為:劉盈池

6、Notification反向傳值

界面一

點(diǎn)擊跳轉(zhuǎn)到第二個(gè)頁面

- (void)notificationClick
{
    SecondViewController *postVC = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:postVC animated:YES];
}

注冊(cè)通知

- (void)registerNotification
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(infoAction:) name:@"info" object:nil];
}

實(shí)現(xiàn)收到通知時(shí)觸發(fā)的方法

- (void)infoAction:(NSNotification *)notification
{
    NSLog(@"接收到通知,內(nèi)容為:%@",notification.userInfo);
    self.title = notification.userInfo[@"name"];
}

在注冊(cè)通知的頁面消毀時(shí)一定要移除已經(jīng)注冊(cè)的通知,否則會(huì)造成內(nèi)存泄漏。

- (void)dealloc
{
    // 移除所有通知
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
    // 移除某個(gè)通知
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"info" object:nil];
    
    // 在第一個(gè)頁面銷毀時(shí)移除KVO觀察者
    [self.secondVC removeObserver:self forKeyPath:@"content"];
}
界面二

如果發(fā)送的通知指定了object對(duì)象,那么觀察者接收的通知設(shè)置的object對(duì)象與其一樣,才會(huì)接收到通知,但是接收通知如果將這個(gè)參數(shù)設(shè)置為了nil,則會(huì)接收一切通知。

// 返回到上個(gè)界面
- (void)backNotification
{
    // 1.創(chuàng)建字典,將數(shù)據(jù)包裝到字典中
    NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:@"劉盈池",@"name",@"19",@"age", nil];
    
    // 2.創(chuàng)建通知
    NSNotification *notification = [NSNotification notificationWithName:@"info" object:nil userInfo:dict];
    
    // 3.通過通知中心發(fā)送通知
    [[NSNotificationCenter defaultCenter] postNotification:notification];
    
    // 4.回傳數(shù)據(jù)
    [self.navigationController popViewControllerAnimated:YES];
}

輸出結(jié)果為:

2020-09-23 18:08:28.526933+0800 Demo[93661:17626006] 接收到通知,內(nèi)容為:{
    age = 19;
    name = "\U5218\U76c8\U6c60";
}

7、NSUserDefaults傳值

頁面一

需要使用值時(shí)通過NSUserDefaults從沙盒目錄里面取值進(jìn)行處理。

- (void)useDefaults
{
    NSString *content = [[NSUserDefaults standardUserDefaults] valueForKey:@"Girlfriend"];
    NSLog(@"NSUserDefaults傳值,內(nèi)容為:%@",content);
}
頁面二

需要傳值時(shí)將數(shù)據(jù)通過NSUserDefaults保存到沙盒目錄里面,比如用戶名之類,當(dāng)用戶下次登錄或者使用app的時(shí)候,可以直接從本地讀取此值。

- (void)userDefaultsClick
{
    [[NSUserDefaults standardUserDefaults] setObject:@"劉盈池" forKey:@"Girlfriend"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    // 跳轉(zhuǎn)到第一個(gè)界面
    [self.navigationController popViewControllerAnimated:YES];
}

輸出結(jié)果為:

2020-09-23 18:19:10.065815+0800 Demo[93846:17636600] NSUserDefaults傳值,內(nèi)容為:劉盈池

三、Delegate與Protocol的用法

1、簡(jiǎn)介

Delegate
什么是Delegate與Protocol呢?

舉個(gè)簡(jiǎn)單的例子,外賣app就是我的代理,我就是委托方,我買了一瓶紅茶并付給外賣app錢,這就是購買協(xié)議。我只需要從外賣app上購買就可以,具體的操作都由外賣app去處理,我只需要最后接收這瓶紅茶就可以。我付的錢就是參數(shù),最后送過來的紅茶就是處理結(jié)果。

有哪些特性呢?

如果只是某個(gè)類使用,我們常做的就是寫在某個(gè)類中。如果多個(gè)類都是用同一個(gè)協(xié)議,建議創(chuàng)建一個(gè)Protocol文件,在這個(gè)文件中定義協(xié)議。遵循的協(xié)議可以被繼承,例如我們常用的UITableView,由于繼承自UIScrollView的緣故,所以也將UIScrollViewDelegate繼承了過來,我們可以通過代理方法獲取UITableView偏移量等狀態(tài)參數(shù)。

協(xié)議只能定義公用的一套接口,類似于一個(gè)約束代理雙方的作用。但不能提供具體的實(shí)現(xiàn)方法,實(shí)現(xiàn)方法需要代理對(duì)象去實(shí)現(xiàn)。協(xié)議可以繼承其他協(xié)議,并且可以繼承多個(gè)協(xié)議,在iOS中對(duì)象是不支持多繼承的,而協(xié)議可以多繼承。

默認(rèn)是@required狀態(tài)的,無論是@optional還是@required,在委托方調(diào)用代理方法時(shí)都需要做一個(gè)判斷,判斷代理是否實(shí)現(xiàn)當(dāng)前方法,否則會(huì)導(dǎo)致崩潰。

原理是什么呢?

其實(shí)委托方的代理屬性本質(zhì)上就是代理對(duì)象自身,設(shè)置委托代理就是代理屬性指針指向代理對(duì)象,相當(dāng)于代理對(duì)象只是在委托方中調(diào)用自己的方法,如果方法沒有實(shí)現(xiàn)就會(huì)導(dǎo)致崩潰。

為什么我們?cè)O(shè)置代理屬性都使用weak呢?

由于代理對(duì)象使用強(qiáng)引用指針,引用創(chuàng)建的委托方對(duì)象,并且成為委托方對(duì)象的代理。這就會(huì)導(dǎo)致委托方的delegate屬性強(qiáng)引用代理對(duì)象,導(dǎo)致循環(huán)引用的問題,最終兩個(gè)對(duì)象都無法正常釋放。設(shè)置為弱引用屬性。這樣在代理對(duì)象生命周期存在時(shí),可以正常為我們工作,如果代理對(duì)象被釋放,委托方和代理對(duì)象都不會(huì)因?yàn)閮?nèi)存釋放導(dǎo)致的Crash。

實(shí)際運(yùn)用場(chǎng)景舉個(gè)例子?

控制器瘦身:UITableView的數(shù)據(jù)處理、展示邏輯和簡(jiǎn)單的邏輯交互都由代理對(duì)象去處理,傳入一些參數(shù),和控制器相關(guān)的邏輯處理傳遞出來,交由控制器來處理,這樣控制器的工作少了很多,而且耦合度也大大降低了。


2、計(jì)算人數(shù)工具類

聲明委托方法
@protocol CountToolDelegate <NSObject>

- (void)willCountAllPerson;// 即將計(jì)算人數(shù)的委托方法
- (void)didCountedAllPerson;// 完成計(jì)算人數(shù)的委托方法

@end
聲明數(shù)據(jù)源方法
@protocol CountToolDataSource <NSObject>

- (NSArray *)personArray;// 返回包含所有人的數(shù)組

@end
聲明委托和數(shù)據(jù)源屬性
@property (nonatomic, weak) id<CountToolDelegate> delegate;// 委托
@property (nonatomic, weak) id<CountToolDataSource> dataSource;// 數(shù)據(jù)源
實(shí)現(xiàn)計(jì)數(shù)方法
- (void)count
{
    // 調(diào)用即將計(jì)數(shù)的委托方法
    if (self.delegate && [self.delegate conformsToProtocol:@protocol(CountToolDelegate)])
    {
        [self.delegate willCountAllPerson];
    }
    
    // 調(diào)用數(shù)據(jù)源進(jìn)行計(jì)數(shù)
    NSArray *persons = [self.dataSource personArray];
    NSLog(@"人數(shù):%@", @(persons.count));
    
    // 調(diào)用完成計(jì)數(shù)的委托方法
    if (self.delegate && [self.delegate respondsToSelector:@selector(didCountedAllPerson)])
    {
        [self.delegate didCountedAllPerson];
    }
}

3、員工

聲明工號(hào)和職位的協(xié)議
@protocol WorkProtocol <NSObject>

@property (nonatomic, strong) NSString *jobNumber;// 工號(hào)

@required
- (void)printJobNumber;// 打印工號(hào)

@optional
- (void)codingAsProgrammer;// 職位

@end
遵從協(xié)議并聲明協(xié)議屬性
@interface Person : NSObject <WorkProtocol>

@property (nonatomic, weak) id<WorkProtocol> delegate;

@end
實(shí)現(xiàn)協(xié)議方法
@implementation Person

- (void)printJobNumber
{
    NSLog(@"打印工號(hào)為:%@", self.jobNumber);
}

- (void)codingAsProgrammer
{
    NSLog(@"編程者");
}

@end

4、技術(shù)總監(jiān)

遵從工具類委托
@interface Administrator() <CountToolDelegate, CountToolDataSource>

_countTool.delegate = self;
_countTool.dataSource = self;
實(shí)現(xiàn)計(jì)算所有人的數(shù)目
- (void)countAllPerson
{
    [self.countTool count];
}
CountToolDelegate
- (void)willCountAllPerson
{
    NSLog(@"調(diào)用了即將計(jì)算人數(shù)的委托方法");
}

- (void)didCountedAllPerson
{
    NSLog(@"調(diào)用了完成計(jì)算人數(shù)的委托方法");
}
CountToolDataSource
- (NSArray *)personArray
{
    return self.allPersons;// 返回包含所有人的數(shù)組
}

5、調(diào)用方式

Person *aPerson = [[Person alloc] init];

// protocol
aPerson.jobNumber = @"10004847";
[aPerson printJobNumber];
[aPerson codingAsProgrammer];

// delegate, dataSource
Administrator *admin = [[Administrator alloc] init];
[admin countAllPerson];

輸出結(jié)果為:

2020-10-20 17:23:35.042241+0800 DelegateDemo[27449:4938470] 打印工號(hào)為:10004847
2020-10-20 17:23:35.042349+0800 DelegateDemo[27449:4938470] 編程者
2020-10-20 17:23:35.042450+0800 DelegateDemo[27449:4938470] 調(diào)用了即將計(jì)算人數(shù)的委托方法
2020-10-20 17:23:35.042539+0800 DelegateDemo[27449:4938470] 人數(shù):2
2020-10-20 17:23:35.042619+0800 DelegateDemo[27449:4938470] 調(diào)用了完成計(jì)算人數(shù)的委托方法

Demo

Demo在我的Github上,歡迎下載。
BasicsDemo

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容