一、代理
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、代理和通知的區別:代理是一對一,通知是一對多;通知沒有返回值,一般沒有級聯關係的用通知,代理有返回值。