關于Blcok,我們經常使用,在Swift中,閉包更是無處不在。那么,關于Block,我們到底了解多少呢?這篇文章旨在對Block做一次全面的總結,從簡單應用,到案例講解再到易錯點,以及原理和內存機制,爭取一文寫清楚Block。
拒絕搬磚,手寫代碼,全部測試。
知識點:
小小擴展:關于值的類型
1.Block的幾種用法
(1)作為局部變量;
(2)作為全局變量;
(3)Block作為函數參數;
(4)Block作為函數返回值;
(5)Block中使用局部變量和全局變量;
2.Block在項目中常用案例
(1)頁面傳值;
(2)代替代理使用;
(3)作為參數傳值;
3.Block的常見問題
(1)循環引用問題產生以及解決方法;
(2)崩潰問題以及解決方法;
4.Block內存機制
(1)iOS五大內存區
(2)Block的存放
(2.1)全局區Block
(2.2)棧區Block
(2.3)堆區Block
(3)為什么加了_ _block,就可以訪問外部的變量了?
5.Block原理
小小擴展:關于值的類型
我們知道,無論是變量、常量還是參數,都會有它的類型,我們一開始接觸一門語言的時候,都會詳細的介紹這門語言的各種類型。比如C的int, float, double, char, const char, char *, *, 等等類型,比如OC的NSString, NSArray, NSSet, NSValue等等類型。我們也知道,這些類型用來標志一個值在內存中的存儲方式。把某個類型放在值的前面,用于聲明這個值的類型。比如:
int a;
char b;
NSString * name;
那么,Block作為匿名函數,它也是有類型的,不過它的類型比較復雜一些,是由簡單類型組合起來的類型。比如:
void (^block) (void)
類型是: void (^) (void)
NSString * (^block1) (void)
類型是:NSString * (^) (void)
NSInteger (^block3) (NSInteger, NSInteger)
類型是: NSInteger (^) (NSInteger, NSInteger)
明白了Block的類型,我們就可以把Block跟其他簡單類型一樣作為參數,作為變量進行使用了。
1.Block的幾種用法
(1)作為局部變量;
//block作為局部變量
-(void)test1 {
/**
返回值類型 (^Block名稱)(參數列表) = ^ 返回值類型 (參數列表){
實現代碼
}
*/
void (^block) (void) = ^ {
NSLog(@"返回值為空,參數為空的block");
};
//使用block
block();
NSString * (^block1) (void) = ^{
NSString * temp = @"返回值為字符串,參數為空的block";
return temp;
};
//使用
NSLog(@"%@", block1());
NSInteger (^block2) (NSInteger) = ^ NSInteger (NSInteger a){
NSInteger t = a * 10;
return t;
};
NSInteger test2 = block2(3);
NSLog(@"test2: %ld", (long)test2);
//求兩個數的和
NSInteger (^block3) (NSInteger, NSInteger) = ^ NSInteger (NSInteger a, NSInteger b) {
return a + b;
};
NSInteger test3 = block3(2, 7);
NSLog(@"test3: %ld", test3);
//求兩個數的乘積
double (^block4) (double, double) = ^double (double a, double b) {
return a * b;
};
NSLog(@"test4: %.2f", block4(2.3, 4.7));
//求兩個數的加減乘除運算
double (^block5) (double, double, NSString*) = ^ double (double a, double b, NSString* mark){
if ([mark isEqualToString:@"+"]) {
return a + b;
} else if ([mark isEqualToString:@"-"]) {
return a - b;
} else if ([mark isEqualToString:@"*"]) {
return a * b;
} else if ([mark isEqualToString:@"/"]) {
if (b == 0) {
return -1;
}
return a / b;
} else {
return -1;
}
};
NSLog(@"test5: %f", block5(4.0, 6.5, @"+"));
NSLog(@"test6: %f", block5(7.8, 6.5, @"-"));
NSLog(@"test7: %f", block5(2.4, 6.5, @"*"));
NSLog(@"test8: %f", block5(4.8, 2.4, @"/"));
//block賦值
double (^block6) (double, double) = block4;
//調用
NSLog(@"test9: %.2f", block6(2.1, 3.8));
}
打印結果:
(2)作為全局變量;
//給block起別名
typedef NSInteger (^SumBlock) (NSInteger, NSInteger);
@interface BlockTestViewController ()
@property(nonatomic, copy)SumBlock block7;
@end
//block作為全局變量
-(void)test2 {
self.block7 = ^ NSInteger (NSInteger a, NSInteger b) {
NSInteger sum = 0;
for (NSInteger i = a; i < b + 1; i++) {
sum = sum + I;
}
NSLog(@"從 %ld 到 %ld 的和為 %ld", a, b, sum);
return sum;
};
if (self.block7) {
//調用
self.block7(0, 100);
self.block7(0, 50);
}
}
打印結果:
(3)Block作為函數參數;
//請求成功
typedef void (^SuccessBlock)(NSDictionary* json, int code);
//請求失敗
typedef void (^FailBlock)(NSString* msg);
//調用test3系列:block作為參數
-(void)test4 {
//有參數,有返回值的block作為參數
[self test3:^NSString *(NSString *name) {
NSLog(@"name = %@", name);
return @"哇哈哈哈哈";
}];
//有參數,無返回值的block作為參數
[self test3_1:^(NSString *name) {
NSLog(@"我是block的參數,name = %@", name);
}];
//無參數,有返回值的block作為參數
[self test3_2:^int{
return 10;
}];
//函數也有返回值,block也有返回值
NSArray * arr = [self test3_3:^NSArray *(NSArray *array) {
//過濾重元素
NSMutableArray * tempArray = [NSMutableArray array];
BOOL flag = NO;
for (NSNumber * num in array) {
for (NSNumber * temp in tempArray) {
if (num == temp) {
flag = YES;
}
}
if (flag == NO) {
[tempArray addObject:num];
}
flag = NO;
}
return tempArray;
}];
NSLog(@"測試的array: %@", arr);
//應用實例
[self loadDataSuccess:^(NSDictionary *json, int code) {
NSLog(@"請求成功,%@", json);
} fail:^(NSString *msg) {
NSLog(@"%@", msg);
}];
}
//block作為函數參數-1
//有參數,有返回值的block作為參數
-(void)test3:(NSString* (^)(NSString * name))block {
NSString * newName = NSStringFromSelector(_cmd);
NSLog(@"%@", block(newName));
}
//block作為函數參數-2
//有參數,無返回值的block作為參數
-(void)test3_1: (void (^)(NSString * name))block {
NSString * myName = @"丹頂鶴";
block(myName);
}
/**
block作為函數參數-3
無參數,有返回值的block作為參數
*/
-(void)test3_2: (int (^)(void))blcok {
NSLog(@"我是test3_2");
int a = blcok();
NSLog(@"我是block的返回值喲, a = %d", a);
}
/**
block作為函數參數-5
函數也有返回值
*/
-(NSArray *)test3_3: (NSArray* (^)(NSArray* array))block {
NSArray * myArray = @[@1,@2,@5,@8,@5,@7,@9,@7,@2,@1];
return block(myArray);
}
/**
block作為函數參數-4
實際應用:模擬網絡請求
*/
-(void)loadDataSuccess: (SuccessBlock)success fail:(FailBlock)fail {
NSDictionary * json = @{@"num":@1,
@"name":@"小明",
@"age":@18,
@"phone":@"12345678922",
@"email":@"6666666@163.com"};
int code = 200;
if (code == 200) {
success(json, code);
} else {
fail(@"請求失敗了");
}
}
打印結果:
(4)Block作為函數返回值;
//調用test5: block作為函數返回值
-(void)test6 {
//有返回值,無參數的block作為函數返回值
NSString* (^block)(void) = [self test5_1];
NSLog(@"%@", block());
//有返回值,有參數的block作為函數返回值
NSString * (^block2)(NSString * name) = [self test5_2:@"小花花"];
NSString * bReturn = block2(@"小點點");
NSLog(@"最終的值: %@", bReturn);
}
//有返回值,無參數的block作為函數返回值
-(NSString* (^)(void))test5_1 {
NSString * (^block)(void) = ^ NSString* {
return @"我們都是好孩子";
};
return block;
}
//有返回值,有參數的block作為函數返回值
-(NSString* (^)(NSString* name))test5_2: (NSString *)testName {
NSString * (^block)(NSString * name) = ^ NSString* (NSString* name) {
return [NSString stringWithFormat:@"block的參數:%@",name];
};
NSLog(@"函數的參數: %@",testName);
return block;
}
打印結果:
(5)Block中使用局部變量和全局變量;
@interface BlockTestViewController ()
@property(nonatomic, copy)SumBlock block7;
@property(nonatomic, assign)int num;
@end
-(void)test7 {
//1.使用局部變量
//要想在block內部使用局部變量,需要在局部變量前面加上__block
__block int a = 100;
int b = 20;
void (^block)(void) = ^{
NSLog(@"a = %d, b = %d", a,b);
a = 200;
};
block();
NSLog(@"after a = %d, b = %d", a,b);
//2.使用全局變量
self.num = 10;
double (^block1) (void) = ^{
self.num = 20;
double t = self.num * 3.14;
return t;
};
//使用block1
double r = block1();
NSLog(@"self.num = %d, r = %.2f", self.num, r);
}
- (void)dealloc
{
NSLog(@"%@銷毀了",NSStringFromClass(self.class));
}
打印結果:
2.Block在項目中常用案例
(1)頁面傳值;
新建兩個VC,一個是BlockAController,一個是BlockBController,如下圖:
BlockAController代碼如下:
//
// BlockAController.h
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface BlockAController : BaseViewController
@end
NS_ASSUME_NONNULL_END
//
// BlockAController.m
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import "BlockAController.h"
#import "BlockBController.h"
@interface BlockAController ()<BlockBControllerDelegate>
@property (weak, nonatomic) IBOutlet UITextField *delegateValue;
@property (weak, nonatomic) IBOutlet UITextField *blockValue;
@end
@implementation BlockAController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)gotoB:(id)sender {
BlockBController * bVC = [BlockBController instance];
bVC.delegate = self;
__weak BlockAController * weakSelf = self;
bVC.block1 = ^(NSString * name) {
self.blockValue.text = name;
};
[self.navigationController pushViewController:bVC animated:YES];
}
#pragma mark - BlockBControllerDelegate
- (void)delegateB:(NSString *)name {
self.delegateValue.text = name;
}
#pragma mark -
- (void)dealloc {
NSLog(@"%@銷毀了",NSStringFromClass([self class]));
}
@end
BlockBController.h
//
// BlockBController.h
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@protocol BlockBControllerDelegate <NSObject>
//代理傳值
-(void)delegateB: (NSString *)name;
@end
typedef void (^BlockType)(NSString * name);
@interface BlockBController : BaseViewController
@property(nonatomic, weak)id<BlockBControllerDelegate>delegate;
@property(nonatomic, copy)BlockType block1;
@end
NS_ASSUME_NONNULL_END
BlockBController.m
//
// BlockBController.m
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import "BlockBController.h"
@interface BlockBController ()
@property (weak, nonatomic) IBOutlet UITextField *input;
@end
@implementation BlockBController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)OKBtnClick:(id)sender {
if (_input.text.length > 0) {
if ([self.delegate respondsToSelector:@selector(delegateB:)]) {
[self.delegate delegateB:_input.text];
}
if (self.block1) {
self.block1(_input.text);
}
[self.navigationController popViewControllerAnimated:YES];
} else {
NSLog(@"沒有值");
}
}
#pragma mark -
- (void)dealloc {
NSLog(@"%@銷毀了",NSStringFromClass([self class]));
}
@end
為了少些重復代碼,新建一個baseVC,如下:
//
// BaseViewController.h
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface BaseViewController : UIViewController
+(instancetype)instance;
@end
NS_ASSUME_NONNULL_END
//
// BaseViewController.m
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import "BaseViewController.h"
@interface BaseViewController ()
@end
@implementation BaseViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
+(instancetype)instance {
UIStoryboard * storyB = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
BaseViewController * vc = [storyB instantiateViewControllerWithIdentifier:NSStringFromClass([self class])];
return vc;
}
@end
A,B在Storyboard中的頁面設計如下圖
運行結果:
(2)代替代理使用;
我們在使用TableView的時候,往往會在cell中進行一些點擊操作,然后把值傳回給VC,進行一系列操作。這種需求,使用代理和Block都是可以實現的。我們使用Block實現以下看看。
我們新建一個VC: BlockCController
//
// BlockCController.h
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface BlockCController : BaseViewController
@end
NS_ASSUME_NONNULL_END
//
// BlockCController.m
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import "BlockCController.h"
#import "BlockCCell.h"
@interface BlockCController ()<UITableViewDataSource, UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, strong)NSArray * datas;
@end
@implementation BlockCController
- (void)viewDidLoad {
[super viewDidLoad];
[self setup];
[self setupData];
}
-(void)setup {
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
-(void)setupData {
NSArray * contents = @[@"床前明月光", @"疑是地上霜", @"舉頭望明月", @"低頭思故鄉"];
NSMutableArray * temp = [NSMutableArray array];
for (int i = 0; i < 20; i++) {
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
dict[@"num"] = [NSString stringWithFormat:@"%d", I];
NSString * str = contents[i%4];
dict[@"content"] = str;
[temp addObject:dict];
}
self.datas = temp;
}
#pragma mark - UITableViewDataSource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.datas.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BlockCCell * cell = [BlockCCell cellWithTableView:tableView];
NSDictionary * dict = self.datas[indexPath.row];
[cell set:dict];
cell.clickBlock = ^ (NSDictionary * dict) {
NSLog(@"VC中,block實現,第%@行, 內容:%@", dict[@"num"], dict[@"content"]);
};
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100;
}
#pragma mark - UITableViewDelegate
#pragma mark - lazy
-(NSArray *)datas {
if (!_datas) {
_datas = [NSArray array];
}
return _datas;
}
@end
cell:
//
// BlockCCell.h
// Test
//
// Created by wenhuanhuan on 2020/6/29.
// Copyright ? 2020 weiman. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^BlockAction)(NSDictionary *);
@interface BlockCCell : UITableViewCell
@property(nonatomic, copy)BlockAction clickBlock;
+(instancetype)cellWithTableView:(UITableView *)tableView;
-(void)set: (NSDictionary *)dict;
@end
NS_ASSUME_NONNULL_END
//
// BlockCCell.m
// Test
//
// Created by wenhuanhuan on 2020/6/29.
// Copyright ? 2020 weiman. All rights reserved.
//
#import "BlockCCell.h"
@interface BlockCCell()
@property (weak, nonatomic) IBOutlet UILabel *numLabel;
@property (weak, nonatomic) IBOutlet UILabel *contentLabel;
@property (weak, nonatomic) IBOutlet UIButton *clickBtn;
@property (nonatomic, strong)NSDictionary * dict;
@end
@implementation BlockCCell
+(instancetype)cellWithTableView:(UITableView *)tableView {
NSString * identifier = @"BlockCCell";
BlockCCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];
return cell;
}
-(void)awakeFromNib {
[super awakeFromNib];
self.clickBtn.layer.masksToBounds = YES;
self.clickBtn.layer.cornerRadius = 5;
}
-(void)set: (NSDictionary *)dict {
self.numLabel.text = dict[@"num"];
self.contentLabel.text = dict[@"content"];
self.dict = dict;
}
- (IBAction)clickAction:(id)sender {
NSLog(@"cell, 點擊了 %@ 行", self.numLabel.text);
if (self.clickBlock) {
self.clickBlock(self.dict);
}
}
@end
運行結果:
(3)作為參數傳值;
要說Block作為參數進行傳值,應用最多的應該是在網絡請求的時候啦。下面簡單模擬一下:
//請求成功
typedef void (^SuccessBlock)(NSDictionary* json, int code);
//請求失敗
typedef void (^FailBlock)(NSString* msg);
-(void)loadDataSuccess: (SuccessBlock)success fail:(FailBlock)fail {
NSDictionary * json = @{@"num":@1,
@"name":@"小明",
@"age":@18,
@"phone":@"12345678922",
@"email":@"6666666@163.com"};
int code = 200;
if (code == 200) {
success(json, code);
} else {
fail(@"請求失敗了");
}
}
使用:
[self loadDataSuccess:^(NSDictionary *json, int code) {
NSLog(@"請求成功,%@", json);
} fail:^(NSString *msg) {
NSLog(@"%@", msg);
}];
3.Block的常見問題
(1)循環引用問題產生以及解決方法;
我們再新建一個VC:LastPageViewController,代碼如下:
// Copyright ? 2020 weiman. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface LastPageViewController : BaseViewController
@end
NS_ASSUME_NONNULL_END
//
// LastPageViewController.m
// Test
//
// Created by wenhuanhuan on 2020/6/28.
// Copyright ? 2020 weiman. All rights reserved.
//
#import "LastPageViewController.h"
#import "LastCell.h"
#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height
@interface LastPageViewController ()<UITableViewDataSource, UITableViewDelegate>
@property(nonatomic,strong)UITableView *TAB;
@end
@implementation LastPageViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.TAB];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
__weak LastPageViewController * weakSelf = self;
LastCell *cell = [LastCell loadLastWithTableView:tableView];
[cell set:[NSString stringWithFormat:@"%ld", indexPath.row]];
cell.loadCell = ^(NSString * _Nonnull name) {
NSLog(@"點擊row = %@, 退出了", name);
[weakSelf.navigationController popViewControllerAnimated:YES];
};
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 60;
}
-(UITableView *)TAB{
if (!_TAB) {
_TAB = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight-10) style:(UITableViewStylePlain)];
_TAB.delegate = self;
_TAB.dataSource = self;
}
return _TAB;
}
-(void)dealloc {
NSLog(@"%@銷毀了",NSStringFromClass([self class]));
}
@end
cell:
LastCell.h
// Copyright ? 2020 weiman. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LastCell : UITableViewCell
@property(nonatomic,copy)void(^loadCell)(NSString *name);
+ (instancetype)loadLastWithTableView:(UITableView *)tableView;
-(void)set:(NSString *)name;
@end
NS_ASSUME_NONNULL_END
LastCell.m
//
// LastCell.m
// Copyright ? 2020 weiman. All rights reserved.
//
#import "LastCell.h"
@interface LastCell()
@property(nonatomic,strong)UIButton *btnClick;
@end
@implementation LastCell
+(instancetype)loadLastWithTableView:(UITableView *)tableView{
static NSString *ID = @"LastCell";
LastCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[LastCell alloc]initWithStyle:(UITableViewCellStyleDefault) reuseIdentifier:ID];
}
return cell;
}
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
[self.contentView addSubview:self.btnClick];
self.btnClick.frame = CGRectMake(20, 10, 100, 40);
}
return self;
}
-(void)set:(NSString *)name {
[self.btnClick setTitle:name forState:UIControlStateNormal];
}
- (void)btnClickAction{
if (self.loadCell) {
self.loadCell(self.btnClick.titleLabel.text);
}
}
-(UIButton *)btnClick{
if (!_btnClick) {
_btnClick = [UIButton buttonWithType:UIButtonTypeCustom];
_btnClick.backgroundColor = [UIColor redColor];
[_btnClick addTarget:self action:@selector(btnClickAction) forControlEvents:(UIControlEventTouchUpInside)];
}
return _btnClick;
}
@end
運行:
我們先把block修飾符改成Strong,看看會不會有問題
運行結果:
我們發現沒有問題,VC依然是可以銷毀的。
我們再改成weak試試:
運行結果:
崩潰了。
為什么會這樣呢?我們來分析一下。
首先看看使用Strong修飾Block,我們的引用情況如下圖:
從圖中可以看出,并不會引起循環引用的情況,沒有閉環。
再來看看使用weak為什么崩潰?
一下就明白了,我們在使用Block的時候,可能已經釋放掉了,當然會引起崩潰了。
再看看copy,是把Block從棧上copy到堆上,由開發者自己管理釋放,所以也不會有問題,這個我們后面詳細說。
什么時候會引起循環引用呢?
在此例當中,當我們在VC中,沒有使用weakSelf的時候,就會引起循環引用,我們先看圖。
代碼中試試:
運行:
我們發現,LastPageViewController沒有被釋放,引起了內存泄露。我們在使用Block的時候,一定要注意。
(2)崩潰問題;
block和代理一樣,如果沒有實現方法,就會出現崩潰。
如下圖,我們注釋掉block的實現:
再次運行,出現了崩潰
所以,使用block的時候和代理一樣,都要判斷是否實現了,如下圖:
這一點一定要注意。
4.Block內存機制
(1)iOS五大內存區
說到內存,就要先了解下內存的五大區:
分別簡單介紹下。
- 堆區:程序員管理,優點:靈活方便,缺點:效率略低。
- 棧區:系統管理,優點:快速高效,缺點:有限制,數據不靈活(先進后出)。通常存放:形參、局部變量。
- 方法區:存放方法(函數)的二進制代碼。
- 靜態區(全局區):系統管理,通常存放:全局變量和靜態變量。
注:全局區又可分為未初始化全局區: .bss段和初始化全局區:data段。 舉例:int a;未初始化的。int a = 10;已初始化的。
-
常量區:系統管理,通常存放:常量字符串。
借用網上一小段C代碼來看看程序是如何存放的。
image.png
了解了iOS的內存分布,那么Block是如何存放的呢?
(2)Block的存放
Block根據存放的區域,分為三種:棧區Block,堆區Block,全局區Block。
注意:在ARC和MRC下,同樣的block代碼,可能會在不同的區域,因為ARC下,系統會為我們把棧上的block拷貝到堆上一份。下面我們分別試一試。
MRC下:
我們首先把當前的VC的ARC關閉,開啟MRC模式,如下圖:
- 全局區Block
什么都不訪問的Block,在全局區。
- (IBAction)test1:(id)sender {
void (^block) (void) = ^{
NSLog(@"這是一個block,什么都沒調用");
};
NSLog(@"%@", block);
//調用
block();
printf("\n\n");
}
打印結果:
訪問靜態全局變量的Block在全局區
static NSString * name = @"Xcode";
//訪問靜態全局變量的Block:全局區
- (IBAction)test4:(id)sender {
void (^block) (void) = ^{
NSLog(@"block 靜態全局變量 name = %@", name);
};
NSLog(@"%@", block);
block();
printf("\n\n");
}
打印:
本身是全局變量的Block, 被賦值的block如果是全局block,全局變量block也在全局區。
/*本身是全局變量的Block, 被賦值的block如果是全局block,
全局變量block也在全局區
*/
- (IBAction)test5:(id)sender {
//全局區block
void (^block)(void) = ^{
NSLog(@"哈哈h哈哈");
};
NSLog(@"myBlock賦值之前:myBlock = %@, block = %@", self.myBlock, block);
self.myBlock = block;
NSLog(@"myBlock賦值之后:myBlock = %@, block = %@", self.myBlock, block);
block();
self.myBlock();
NSLog(@"調用之后:myBlock = %@, block = %@", self.myBlock, block);
}
打印:
- 棧區Block
如果block內部訪問了外部局部變量,那么在MRC
下,這個block存儲在棧區。
//訪問外部局部變量的Block:MRC棧區,ARC堆區
- (IBAction)test2:(id)sender {
int a = 10;
void (^block)(void) = ^{
NSLog(@"block 外部局部變量,a = %d", a);
};
NSLog(@"%@", block);
block();
__block int b = a;
void (^block2)(void) = ^{
b = 20;
NSLog(@"block2 改變外部局部變量,b = %d", b);
};
NSLog(@"%@", block2);
block2();
printf("\n\n");
}
打印結果:
如果block內部訪問了外部的全局變量,在MRC
下,這個block存儲于棧區。
//訪問外部全局變量的Block:MRC棧區,ARC堆區
- (IBAction)test3:(id)sender {
void (^block) (void) = ^{
self.num = 300;
NSLog(@"block 全局變量 num = %d", self.num);
};
NSLog(@"%@", block);
block();
printf("\n\n");
}
打印結果:
- 堆區Block
如果被賦值的block是在棧區,全局變量block使用copy修飾,會在堆區拷貝一份,成為堆區block
//全局block使用copy修飾,對于在棧區的block,賦值給全局copy修飾的block之后,全局block在堆區拷貝了一份。
- (IBAction)test6:(id)sender {
int a = 100000;
void (^block)(void) = ^{
NSLog(@"block 外部局部變量,a = %d", a);
};
NSLog(@"賦值之前:blockTest = %@, block = %@", self.blockTest, block);
self.blockTest = block;
NSLog(@"賦值之后:blockTest = %@, block = %@", self.blockTest, block);
block();
}
打印結果:
ARC下
看完了MRC下的block存儲,我們再看看ARC下的Block存儲吧。
先把MRC開啟去掉,如下圖:
我們再來看看上面的例子的打印結果。
代碼不再粘貼,就看看打印結果。
1.什么都不訪問的block:全局區,與MRC同。
2.訪問靜態全局變量:全局區,與MRC同。
3.訪問局部變量, MRC:棧區, ARC:堆區
4.訪問全局變量,MRC:棧區,ARC:堆區
5.對于全局變量的Block,與被賦值的block在什么區域有關。例如:
self.myBlock = blockTest;
如果blockTest是全局區block,那么賦值以后,self.myBlock也是全局區block。
如果blockTest是堆區block,那么copy修飾的self.myBlock在堆區拷貝一份,成為堆區block。
如果blockTest是棧區block,那么copy修飾的self.myBlock在堆區拷貝一份,成為堆區block。
不知道大家有沒有注意,在MRC下的棧區Block,到了ARC就變成了堆區Block,為什么呢?
因為在ARC上,當系統捕獲到自動變量的時候,就自動的進行一次copy操作,把棧上的Block拷貝到了堆上,就變成了堆區Block。
ARC下沒有棧區Block嗎?不,有的,當Block作為參數的時候,訪問外部局部變量,這個Block就是棧區Block,如
//block作為參數
-(void)test8:(void (^)(void))block {
NSLog(@"---------%@--------", NSStringFromSelector(_cmd));
NSLog(@"函數中,block作為參數");
NSLog(@"%@",block);
block();
printf("\n\n");
}
NSLog(@"--訪問局部變量的弱引用block");
__block int static_k = 3;
__weak void (^block2)(void) = ^{
static_k++;
};
block2();
NSLog(@"block2 = %@",block2);
printf("\n\n");
- (void)viewDidLoad {
[super viewDidLoad];
int age = 18;
[self test8:^{
NSLog(@"調用時,block作為參數,訪問局部變量,age = %d", age);
}];
}
打印結果:
前面內容比較多,也很零散,關于Block的內存機制,這里也只是選取了幾種常見的例子,我們簡單做個總結吧。
Block為什么要用copy修飾?
在ARC中,Block不一定要用copy修飾,使用strong也是沒問題的。
原因:
通過上面的分析我們知道了,在MRC下,Block中訪問局部或者全局變量,這個Block存放于棧區,棧區是系統管理的,超過作用于就會被銷毀了,所以我們要使用copy修飾,把棧區的Block拷貝到堆區一份,由程序員自己管理,全局共享。所以,在MRC時代,我們使用copy修飾Block。
在ARC中,訪問局部變量或者全局變量的Block存儲于堆區,系統已經給我們做了一次copy,我們就不需要自己copy一次了。所以,ARC中,使用strong修飾Block也是沒問題的。
之所以大家還是使用copy,是因為MRC遺留下來的習慣。
在ARC下,我們把棧區Block賦值給全局變量Block,看看會不會是棧區Block呢?
@property(nonatomic, strong)void (^test2)(void);
//block作為參數
-(void)test8:(void (^)(void))block {
NSLog(@"---------%@--------", NSStringFromSelector(_cmd));
NSLog(@"函數中,block作為參數");
NSLog(@"%@",block);
block();
printf("\n\n");
//把參數block賦值給全局變量test2試試
self.test2 = block;
self.test2();
NSLog(@"參數block賦值給全局變量, block: %@, test2: %@", block, self.test2);
printf("\n\n");
}
打印結果:
再次印證了我們的結論,ARC下,即使用strong修飾Block,把棧區Block賦值給變量,得到的也是堆區Block。
(3)為什么加了_ _block,就可以訪問外部的變量了?
我看看一段熟悉的代碼:
void blockTest(void);
int main(int argc, const char * argv[]) {
blockTest();
return 0;
}
void blockTest(void) {
int age = 100;
void (^myBlock)(void) = ^{
printf("age = %d\n", age);
};
myBlock();
}
我們使用終端clang命令,看看生成的c++文件。我們cd到main.mm所在的目錄,然后使用
clang -rewrite-objc main.mm
命令,在當前目錄下生成main.cpp文件。
打開文件,由于內容太多,我們搜索我們需要的內容:
這時,我們嘗試修改age的值:
會報錯。我們使用_ _block修飾變量,不在報錯。再次clang看看有何不同。
第三個參數變成了一個指針,_ block修飾的int類型的局部變量也變成了一個指針。我們知道,Block就是一個匿名函數,使用 _block修飾的變量,在block使用的時候,相當于一個引用傳遞,參數是一個指針。
關于值傳遞,引用傳遞,地址傳遞的擴展知識,篇幅有限,請看我的另一篇文章:http://www.lxweimin.com/p/560197c17e48
如果局部變量不用_ _block修飾,相當于值傳遞,即使在Block中進行了更改,也是對外部無效的。只有加了
_ _block修飾,才是引用傳遞,在Block內部做的更改才會對外部變量起作用。