原創(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é)議定義
-
controller的delegate屬性 - 實(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與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