iOS进阶-深度学习block原理
目录
- 基本声明使用总结
- block在ARC、MRC下内存方式
- 使用block遇到的坑
- 更深次的看block的本质
基本声明使用总结
声明方式总结:
声明格式:typeReturn(^blockName)(typeParameters)
声明+实现: <#returnType#>(^blockName)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
使用方式一:
void(^myblock)(void) = ^{
NSLog(@"没有参数没有返回值的block");
};
使用方式二:
NSString*(^myblock)(void) = ^{
return @"有返回值没有参数的block";
};
使用后方式三:
void(^myblock)(NSString*) = ^(NSString * a){
NSLog(@"%@",[NSString stringWithFormat:@"没有返回值 有参数:%@",a]) ;
};
总结:
无论block声明的是否有返回值,在实现中都可以不写出来,单是如果声明里有参数时,实现必须加入参数。
block在ARC、MRC下内存方式
ARC下:
/*
ARC只存在两种内存情况: 1. 获取局部变量的block都会复制到堆区 2. 获取全局、静态变量或者不获取变量都会加载搭配全局区
2019-05-17 09:14:17.186183+0800 test-block[44562:5593068] myblock 没有获取外部变量
2019-05-17 09:14:17.186344+0800 test-block[44562:5593068] myblock: <__NSGlobalBlock__: 0x1037881c8>
2019-05-17 09:14:17.186433+0800 test-block[44562:5593068] myblock2获取到局部变量:3
2019-05-17 09:14:17.186531+0800 test-block[44562:5593068] myblock2: <__NSMallocBlock__: 0x600001aec060>
2019-05-17 09:14:17.186611+0800 test-block[44562:5593068] myblock3获取到局部变量:3
2019-05-17 09:14:17.186681+0800 test-block[44562:5593068] myblock3: <__NSMallocBlock__: 0x600001aec0c0>
2019-05-17 09:14:17.186763+0800 test-block[44562:5593068] myblock4获取到全局变量:7
2019-05-17 09:14:17.186838+0800 test-block[44562:5593068] myblock4: <__NSGlobalBlock__: 0x1037881e8>
2019-05-17 09:14:17.186920+0800 test-block[44562:5593068] 我是用copys修饰的属性block 加载局部变量 4
2019-05-17 09:14:17.187008+0800 test-block[44562:5593068] myblockT1: <__NSMallocBlock__: 0x600001af5da0>
2019-05-17 09:14:17.187103+0800 test-block[44562:5593068] 我是用copys修饰的属性block 加载q全局变量7
2019-05-17 09:14:17.187326+0800 test-block[44562:5593068] myblockT2: <__NSGlobalBlock__: 0x103788208>
*/
- (void) storageAboutBlockInARC{
void(^myblock)(void) = ^{
NSLog(@"myblock 没有获取外部变量");
};
myblock();
NSLog(@"myblock: %@",myblock); // <__NSGlobalBlock__: 0x1037881c8>
int i=3;
void(^myblock2)(void) = ^{
NSLog(@"myblock2获取到局部变量:%d",i);
};
myblock2();
NSLog(@"myblock2: %@",myblock2); // <__NSMallocBlock__: 0x600001aec060>
__block int h=3;
void(^myblock3)(void) = ^{
NSLog(@"myblock3获取到局部变量:%d",h);
};
myblock3();
NSLog(@"myblock3: %@",myblock3); // <__NSMallocBlock__: 0x600001aec0c0>
void(^myblock4)(void) = ^{
NSLog(@"myblock4获取到全局变量:%d",m);
};
myblock4();
NSLog(@"myblock4: %@",myblock4); // <__NSGlobalBlock__: 0x1037881e8>
int l =4;
self.myblockT1 = ^{
NSLog(@"我是用copys修饰的属性block 加载局部变量 %d",l);
};
self.myblockT1();
NSLog(@"myblockT1: %@",self.myblockT1); // <__NSMallocBlock__: 0x600001af5da0>
self.myblockT2 = ^{
NSLog(@"我是用copys修饰的属性block 加载q全局变量%d",m);
};
self.myblockT2();
NSLog(@"myblockT2: %@",self.myblockT2); // <__NSGlobalBlock__: 0x103788208>
}
/*
声明方式:
*/
- (void) simpleUsage
{
// 使用方式一:
void(^myblock1)(void) = ^{
NSLog(@"没有参数没有返回值的block");
};
// 使用方式二:
NSString*(^myblock2)(void) = ^{
return @"有返回值没有参数的block";
};
// 使用后方式三:
void(^myblock3)(NSString*) = ^(NSString * a){
NSLog(@"%@",[NSString stringWithFormat:@"没有返回值 有参数:%@",a]) ;
};
}
MRC下:
/*
MRC情况下内存相关:1. 位于代码区的block访问局部变量的都会复制到栈区 2. 访问局部全局、静态变量会加载到全局区 3、被copy修饰的block访问局部变量会被加载到堆区
2019-05-17 09:08:01.133444+0800 test-block[44272:5574974] myblock 没有获取外部变量
2019-05-17 09:08:01.133628+0800 test-block[44272:5574974] myblock: <__NSGlobalBlock__: 0x1067ea100>
2019-05-17 09:08:01.133728+0800 test-block[44272:5574974] myblock2获取到局部变量:3
2019-05-17 09:08:01.133895+0800 test-block[44272:5574974] myblock2: <__NSStackBlock__: 0x7ffee94159c0>
2019-05-17 09:08:01.134000+0800 test-block[44272:5574974] myblock3获取到局部变量:3
2019-05-17 09:08:01.134188+0800 test-block[44272:5574974] myblock3: <__NSStackBlock__: 0x7ffee9415970>
2019-05-17 09:08:01.134353+0800 test-block[44272:5574974] myblock4获取到全局变量:7
2019-05-17 09:08:01.134427+0800 test-block[44272:5574974] myblock4: <__NSGlobalBlock__: 0x1067ea170>
2019-05-17 09:08:01.134529+0800 test-block[44272:5574974] 我是用copys修饰的属性block 加载局部变量 4
2019-05-17 09:08:01.134752+0800 test-block[44272:5574974] myblockT1: <__NSMallocBlock__: 0x600002f8a5e0>
2019-05-17 09:08:01.135106+0800 test-block[44272:5574974] 我是用copys修饰的属性block 加载q全局变量7
2019-05-17 09:08:01.135668+0800 test-block[44272:5574974] myblockT2: <__NSGlobalBlock__: 0x1067ea190>
*/
- (void) storageAboutBlockInMRC{
void(^myblock)(void) = ^{
NSLog(@"myblock 没有获取外部变量");
};
myblock();
NSLog(@"myblock: %@",myblock); // <__NSGlobalBlock__: 0x108d2d0f8>
int i=3;
void(^myblock2)(void) = ^{
NSLog(@"myblock2获取到局部变量:%d",i);
};
myblock2();
NSLog(@"myblock2: %@",myblock2); // <__NSStackBlock__: 0x7ffee52969c0>
__block int h=3;
void(^myblock3)(void) = ^{
NSLog(@"myblock3获取到局部变量:%d",h);
};
myblock3();
NSLog(@"myblock3: %@",myblock3); // <__NSStackBlock__: 0x7ffee5296970>
void(^myblock4)(void) = ^{
NSLog(@"myblock4获取到全局变量:%d",m);
};
myblock4();
NSLog(@"myblock4: %@",myblock4); // myblock: <__NSGlobalBlock__: 0x10535d178>
int l =4;
self.myblockT1 = ^{
NSLog(@"我是用copys修饰的属性block 加载局部变量 %d",l);
};
self.myblockT1();
NSLog(@"myblockT1: %@",self.myblockT1); // <__NSMallocBlock__: 0x60000107dce0>
self.myblockT2 = ^{
NSLog(@"我是用copys修饰的属性block 加载q全局变量%d",m);
};
self.myblockT2();
NSLog(@"myblockT2: %@",self.myblockT2); // myblock: <__NSGlobalBlock__: 0x10535d178>
}
总结:
一、 在ARC内存管理方式下: block 只存在两种存储方式: 1. 不关是什么block只要加载局部变量都会被加载到堆区 2. 不管是什么block获取全局、静态变量或者不获取外部变量,都会被加载到全局区;
二、在MRC内存房里方式下: 1. 被copy修饰的block 、局部block获取获取局部变量不同,前者被加载到堆区,后者被加载到栈区 2. 访问局部全局、静态变量会加载到全局区
使用block遇到的坑
block属性在ARC、MRC下修饰符
ARC用strong、copy都可以,因为编译器会在编译的时候自动加载到堆区。即便是除了对应的方法仍然可以引用到。
MRC用copy修饰符将block拷贝到堆区,因为栈区中的变量出了作用域之后就会被销毁,无法在全局使用,是在堆区的block不会被提前销毁,可以作为公共资源进行访问,开发者可以手动进行它的内存管理。
什么修饰符随时外部变量,block内部可以修改
通常我们使用 __block __weak ; __block 即可以修饰指针类型也可以修饰基本数据类型,__weak只能修饰指针类型;
循环引用问题
- (void)testBlockRetainCycle {
ClassB* objB = [[ClassB alloc] init];
__weak typeof(objB) weakObjB = objB;
self.myBlock = ^() {
[weakObjB doSomething];
};
objB.objA = self;
}
上图: 由于iOS的内存管理方式是引用计数,出现循环引用的原因如下:
image.png
解决方案:
主要循环节点任意一个节点不是强引用即可解决,通常在block外部将内部需要引用的对象转化为弱引用对象即可解决,但是如果block需要用对象处理以下耗时操作,弱引用对象也会提前释放,导致空指针报错,解决方法是用转化为强引用对象即可解决,强引用的生命周期跟随当前的block。即解决循环引用又解决耗时操作业务。
两种转换方式如下:
__weak typeof(self) weakobj = selfObj;
__strong typeof(weakobj) strongObj = weakobj;
更深次的看block的本质
总结:block 实质上是一个oc对象,内部也有一个isa指针,内部封装了函数调用和调用环境。
// # main.m 文件
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
void(^myblock1)(void) = ^{
NSLog(@"没有参数没有返回值的block");
};
对以上的代码执行以下指令将它转变为c++ 代码理解,由于#import <UIKit/UIKit.h>、#import "AppDelegate.h"会生成的库文件没有拷贝,我拷贝主要部分:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
struct __myblock1_block_impl_0 {
struct __block_impl impl;
struct __myblock1_block_desc_0* Desc;
__myblock1_block_impl_0(void *fp, struct __myblock1_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __myblock1_block_func_0(struct __myblock1_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_r1_y39rnll97blgw_jhbgdjkh9c0000gn_T_main_eab483_mi_0);
}
static struct __myblock1_block_desc_0 {
size_t reserved;
size_t Block_size;
} __myblock1_block_desc_0_DATA = { 0, sizeof(struct __myblock1_block_impl_0)};
static __myblock1_block_impl_0 __global_myblock1_block_impl_0((void *)__myblock1_block_func_0, &__myblock1_block_desc_0_DATA);
void(*myblock1)(void) = ((void (*)())&__global_myblock1_block_impl_0);
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
定义block
void(*myblock1)(void) = ((void (*)())&__global_myblock1_block_impl_0);
定义部分调用 __global_myblock1_block_impl_0 调用 block内部;
__myblock1_block_impl_0 函数 是block 的主体部分
struct __myblock1_block_impl_0 {
struct __block_impl impl;
struct __myblock1_block_desc_0* Desc;
__myblock1_block_impl_0(void *fp, struct __myblock1_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
1.__myblock1_block_impl_0内部定义了 isa 执行它所处于的类型,通常有6中,常用有static、globel、heap类型;
image.png
2、有__myblock1_block_impl_0的同名构造函数,穿入block实现、__main_block_desc_0对象;如果定义的block有参数的话也会在flags参数后面追加,
__main_block_func_0的实现部分,比如我纸打印了以下:
static void __myblock1_block_func_0(struct __myblock1_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_r1_y39rnll97blgw_jhbgdjkh9c0000gn_T_main_eab483_mi_0);
}
关于block的相关练习代码github地址:https://github.com/ge123/test-block-/blob/master/README.md