怎么利用云盘建设网站,网页设计与制作项目,怎样做网络销售网站,wordpress 添加phpmyadmin文章目录 Block底层实现全局变量捕获block类型总结__NSGlobalBlock____NSStackBlock__NSMallocBlock__ 四种自动将栈上操作复制到堆上的操作1.Block做函数或方法的返回值2.将Block需要强引用时3.当Block作为参数传给Cocoa API时4.当Block作为参数传给GCD的API时Block属性在MRC与…文章目录Block底层实现全局变量捕获block类型总结__NSGlobalBlock____NSStackBlock__NSMallocBlock__四种自动将栈上操作复制到堆上的操作1.Block做函数或方法的返回值2.将Block需要强引用时3.当Block作为参数传给Cocoa API时4.当Block作为参数传给GCD的API时Block属性在MRC与ARC下的写法区别MRC环境下ARC环境下示例Block存储转换__block修饰符作用_block_object_assign_Block_object_dispose__forwarding指针循环引用NSProxy虚拟类三层拷贝详细讲解Block底层实现在main函数中书写如下代码:intmain(intargc,constchar*argv[]){autoreleasepool{void(^block)(void)^{printf(block~~~\n);};block();}return0;}接下来我们在终端cd进入文件目录执行clang -rewrite-objc main.m -o main.cpp将OC文件编译为C文件。main.cpp文件内容删除掉其他系统代码之后核心部分如下struct__main_block_impl_0{struct__block_impl impl;struct__main_block_desc_0*Desc;__main_block_impl_0(void*fp,struct__main_block_desc_0*desc,intflags0){impl.isa_NSConcreteStackBlock;impl.Flagsflags;impl.FuncPtrfp;Descdesc;}};staticvoid__main_block_func_0(struct__main_block_impl_0*__cself){printf(block~~~\n);}staticstruct__main_block_desc_0{size_t reserved;size_t Block_size;}__main_block_desc_0_DATA{0,sizeof(struct__main_block_impl_0)};intmain(intargc,constchar*argv[]){void(*block)(void)((void(*)())__main_block_impl_0((void*)__main_block_func_0,__main_block_desc_0_DATA));((void(*)(__block_impl*))((__block_impl*)block)-FuncPtr)((__block_impl*)block);return0;}观察上面代码可以看到Block本质是一个结构体对象。核心结构如下struct__block_impl{//不block基础结构体可以理解为所有block的父结构体void*isa;//指向不Block类型intFlags;//block标识intReserved;//预留字段void*FuncPtr;//block执行函数};struct__main_block_impl_0//block结构体void(^block)(void)staticvoid__main_block_func_0(...)//block被执行函数^{printf(block~~~\n);}struct__main_block_desc_0//block描述结构记录block大小复制函数销毁函数block执行过程block();//实际编译如下((__block_impl*)block)-FuncPtr(...)block本质就是结构体加函数指针我们看一下核心部分struct__main_block_impl_0 tmp__main_block_impl_0(__main_block_func_0,__main_block_desc_0_DATA);//创建一个Block结构体对象可以理解为 Block tmp;struct__main_block_impl_0*blktmp;Tmp:block对象__main_block_func_0:block中的代码__main_block_desc_0_DATA:block描述信息假设一个block中捕获了一个int类型的变量那么其C语言实现如下__main_block_func_0//clang自动生成的函数名staticvoid__main_block_func_0(struct__main_block_impl_0*__scelf){printf(block~~~\n);}__文件名_block_func_编号全局变量捕获#importFoundation/Foundation.hintc1000;staticintd10000;intmain(intargc,constchar*argv[]){autoreleasepool{inta10;staticintb100;void(^block)(void)^{NSLog(a %d,a);NSLog(b %d,b);NSLog(c %d,c);NSLog(d %d,d);};a20;b200;c2000;d20000;block();}return0;}输出结果如下通过终端转换为C代码如下struct__main_block_impl_0{struct__block_impl impl;struct__main_block_desc_0*Desc;inta;int*b;__main_block_impl_0(void*fp,struct__main_block_desc_0*desc,int_a,int*_b,intflags0):a(_a),b(_b){impl.isa_NSConcreteStackBlock;impl.Flagsflags;impl.FuncPtrfp;Descdesc;}};我们可以看见block中只捕获了两个变量原因如下全局变量捕获因为是全局变量无论静态全局变量或者是全局普通变量在哪里都可以被直接访问所以在block内部即便是不进行捕获也可以直接访问所以我们打印这些变量时就是最新的值静态局部变量的捕获定义的静态全局变量b被block捕获之后在block结构体之中是以int* b的形式存储的也就是说block捕获的其实是变量b的地址在block内部是通过b的地址去获取修改b的值所以block的外更改b的值会影响block内部捕获的b的值block内部更改b的值也会影响block外面b的值。普通局部变量的捕获普通局部变量的捕获就是在一个函数或者代码块中定义类似的auto类型的变量和局部变量不同的是普通局部变量被block捕获后在block底层结构体中是以int a,形式存储的值捕获也就是内部重新创建了一个变量用来存储捕获的值此时block内部于外部其实是两个不同的变量存储着相同的值。为什么这里只是存储值不是存储地址呢原因就是auto类型的变量出了作用域就被自动释放了如果指针引用会造成悬空指针。我们看一下下面这个问题-(void)blockTest{// 第一种void(^block1)(void)^{NSLog(%p,self);};// 第二种void(^block2)(void)^{self.nameJack;};// 第三种void(^block3)(void)^{NSLog(%,_name);//访问实例变量时编译器会自动转换为self-_name};// 第四种void(^block4)(void)^{[selfname];};}上述代码哪些捕获了self答案是全部捕获了selfOC的调用[self blockTest]时底层都会被编译器转换成objc_msgSend(self, selector(blockTest));可以看出self实际是作为参数传给函数objc_msgSend的也就是说在方法执行时self的本质就是一个函数参数。在函数调用时参数会被压入栈帧中虽然self这个指针变量在方法结束后会被销毁但是销毁的是栈上的指针变量self而不是self指向的对象。block类型总结我们探索一下block的类intmain(intargc,constchar*argv[]){autoreleasepool{intnum100;void(^block1)(void)^{NSLog(----);};NSLog(block1的类 —— %,[block1 class]);NSLog(block2的类 —— %,[^{NSLog(----%d,num);}class]);NSLog(block3的类 —— %,[[^{NSLog(----%d,num);}copy]class]);}return0;}输出结果如下__NSGlobalBlock__如果一个block中没有访问任何变量那么该block就是__NSGlobalBlock__在内存中是存在数据区的即全局区或者静态区。__NSGlobalBlock__类型的block调用copy方法其实啥都没干。就相当于一个单例。-(void)test{void(^block)(void)^{NSLog(-----);};NSLog(--- %,[block class]);NSLog(--- %,[[block class]superclass]);NSLog(--- %,[[[block class]superclass]superclass]);NSLog(--- %,[[[[block class]superclass]superclass]superclass]);}输出结果如下__NSStackBlock__如果block捕获了局部变量那么它就是一个__NSStackBlock__他存储在栈区栈区的特点就是自动释放即他的内存不受开发者控制系统自动释放。NSMallocBlock__一个__NSStackBlock__类型的block调用copy方法那么就从栈上复制到堆上。如果对一个__NSMallocBlock__类型的变量做copy操作那么该block的引用计数➕1。完整复制。__NSStackBlock完整继承链__NSMallocBlock__↓__NSMallocBlock__//这一步其实在runtime内部存在是中间的私有类↓NSBlock↓NSObject四种自动将栈上操作复制到堆上的操作1.Block做函数或方法的返回值-(void(^)(void))createBlock{intnum101;return^{NSLog(zl);};}编译器会在返回前自动调用copy操作将其从栈上复制到堆上。2.将Block需要强引用时如果我们将一个栈上的Block赋值给一个使用strong修饰符修饰的变量编译器同样会自动复制到堆上。-(void)test{inta10;// 定义一个 block并赋值给一个强指针变量 myBlockvoid(^myBlock)(void)^{NSLog(a %d,a);};myBlock();}当block被赋值给一个strong类型的变量时ARC会确保block的内存安全y因此自动将其复制到堆上。3.当Block作为参数传给Cocoa API时许多Cocoa API都要求传入的block必须是堆上的Block如果传入栈上的系统会自动将其复制。[UIView animateWithDuration:1.0fanimations:^{// 这里传入的 block 会被自动复制到堆上}];Block在这里不是立即执行而是被保存起来如果block还是在栈上的话函数返回时栈空间释放block失效4.当Block作为参数传给GCD的API时GCD的API也要求block必须在堆上这样才能在异步执行中长期有效。dispatch_async(dispatch_get_main_queue(),^{//传递给GCD的block会自动从栈上复制到堆上便于在调度队列中安全的使用});Block属性在MRC与ARC下的写法区别MRC环境下建议使用copy栈上的block不会自动复制到堆上所以最好使用copy修饰property(nonatomic,copy)void(^block)(void);避免因为栈内存释放导致的崩溃或未定义行为ARC环境下copy和strong都可以编译器都会自动将其从栈上复制到堆上。property(strong,nonatomic)void(^block)(void);property(copy,nonatomic)void(^block)(void);示例typedefint(^mutiplierBlock)(int);mutiplierBlockcreateMutiplierBlock(intfactor);intmain(intargc,constchar*argv[]){autoreleasepool{//调用创建block捕获factor的值为5mutiplierBlock multipliercreateMutiplierBlock(5);intresultmultiplier(4);//调用block虽然创建函数中的factor局部变量早已释放但是值拷贝了复制本NSLog(result %d,result);//输出结果为5 * 4}return0;}//定义一个函数用于创建一个block并捕获局部变量factormutiplierBlockcreateMutiplierBlock(intfactor){mutiplierBlock block^(intnumber){//block执行变量捕获值拷贝副本returnfactor*number;};return[block copy];//返回前将栈上的block拷贝到堆上}Block存储转换默认情况下block在函数中创建时位于栈上生命周期与局部变量类似。调用copy将其复制到栈上如图所示被copy到堆上的block就是__NSConcreteMallocBlock类多次重复拷贝不会导致重复复制只是增加引用计数。__block修饰符作用常规的局部变量只能被block捕获初始值在修改之后不会更新已捕获的值使用静态局部变量的话会消耗不必不要的内存所以尽量不使用。使用__block关键字可以解决这个问题。如果在一个Block中使用该修饰词当该block从栈上复制到堆时使用的所有__block变量也将全部从栈上复制到堆上此时Block持有__block变量。我们看一个例子__blockinta10;void(^block)(void)^{NSLog(%d,a);};编译器不会简单复制而是生成一个byref结构体。类似于__Block_byref_astruct__Block_byref_a{void*__isa;__Block_byref_a*__forwarding;int__flags;int__size;inta;};//Block捕获的是__Block_byref_a *在多个Block中使用__block变量时由于最初所有的block都会被配置在栈上所以__block变量也会配置在栈上在copy时都会一并复制到堆上并被Block持有。这里第二个复制例子只复制了Block1却导致Block0也复制到了堆上这是因为两个Block共享同一个__block变量结构而该结构被迁移到堆时引用它的栈上Block也被必须一起迁移。__block不论修饰基础数据类型还是对象数据类型底层都是将们包装成一个对象这里我们暂时叫做__blockObjblock结构体中有一个指针指向该对象。当block在栈上时block内部不会对__blockObj产生强引用。当block被copy到堆上时__blockObj也会被拷贝到堆上并对__blockObj产生强引用。在OC中_Block_object_assign和_Block_object_dispose是与block内存管理相关的内部函数用于处理block在运行时的内存管理。_block_object_assign作用用于将一个对象赋值给一个block中的局部变量例如捕获的__block变量当block捕获一个对象时它需要确保对这个对象的引用在block执行期间有效。通过该函数即可实现block对捕获的对象进行管理。主要用途复制与引用计数管理会增加对象的引用计数确保不提前释放对象。引用传递_Block_object_assign确保block在内部维护正确的对象引用确保不会循环引用_Block_object_dispose作用是用来释放block内部捕获的对象引用的函数会在block被销毁时正确的减少捕获对象的引用计数。避免造成内存泄漏主要用途释放捕获对避免内存泄漏清理操作在block销毁时进行清理确保在block中使用的对象不在被引用时可以正确释放__forwarding指针__blockintval10;void(^blk)(void)^{val1;};我们使用__block修饰符修饰变量的时候该变量底层会变成一个结构体类型的变量,__block_byref_val_0结构体类型的自动变量即栈上生成的__Block_byref_val_0结构体实例struct__Block_byref_val_0{void*__isa;//用于兼容OC对象结构不咋用struct__Block_byref_val_0*__forwarding;//指向真正的数据位置int__flags;//标志位用于记录当前状态是否复制到堆是否需要释放是否包含对象类型int__size;//bref结构体大小intval;//原始变量}/* Stack │ └── byref_va l forwarding → Heap_byref_val Heap │ ├── HeapBlock │ └── Heap_byref_va; forwarding → 自己 Stack_byref.forwarding → Heap_byref //关键变化 */当block从栈上复制到堆上后局部__Block变量也必须复制到堆上。不然block执行时会访问无效内存。它确保了block内所有对__block变量的引用都指向堆上的有效拷贝而不会停留在即将销毁的栈内存中。无论block是在栈上执行还是在堆上执行所有对该__block变量的访问都通过__forwarding指针确保访问的是最新最有效的内存区域。循环引用示例如下-(void)test{self.namepop;self.myBlock^(void){NSLog(%,self.name);};}我们可以看到如下警告上面警告内容就是提示我们在block中对self的捕获是强引用。可能会导致循环引用retain cycle常用解决办法weak - strong___block修饰对象同时置为nil传递对象self作为block的参数提供给block内部使用使用NSProxy__weaktypeof(self)weakSelfself;self.viewModel.researchSong^{__strongtypeof(self)strongSelfweakSelf;}如果block内部没有嵌套block那么只用__weak就行如果嵌套了那么就需要再使用__strong,后面这个strongSelf只是临时变量内部block执行完成之后就释放strongSelf。这种方式依赖于中介者模式属于自动置为nil。__blocktypeof(obj)blockObjobj;obj.block^{NSLog(Inside block: %,blockObj);dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(2*NSEC_PER_SEC)),dispatch_get_main_queue(),^{NSLog(Inside delayed block: %,blockObj);blockObjnil;});//将对象复制给一个__block修饰的变量在block结束后对其手动释放}需要手动只为nil必须NSProxy虚拟类一个抽象根类与NSObject同级特性说明根类与 NSObject 同级不能直接实例化必须子类化专门用于消息转发不负责普通对象行为实现代理对象常用于 AOP、远程代理就是一个专门用于拦截并转发消息的对象。在OC的消息转发机制中有三个阶段动态方法解析resolveInstanceMethod:快速转发forwardingTargetForSelector:完整转发methodSignatureForSelector:forwardInvocation:NSProxy主要依赖于第三阶段-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel;//返回方法签名-(void)forwardInvocation:(NSInvocation*)invocation;//真正的消息转发三层拷贝详细讲解__block NSObject*obj[[NSObject alloc]init];void(^block)(void)^{NSLog(%,obj);};如果变量使用__block修饰就会触发三层拷贝第一层拷贝_Block_copy//将block从栈复制到堆上Stack├─ Block└─ __block struct拷贝后Heap├─ Block└─ __block struct (引用栈结构体)此时__NSStackBlock__ → __NSMallocBlock__第二层拷贝_Block_byref_copy//复制__block结构体Stack Heap ┌───────────────────────┐ ┌───────────────────────┐ │ byref_obj_stack │ │ byref_obj_heap │ │ obj │ │ obj │ └───────────────────────┘ └───────────────────────┘同时更新__forwarding指针让所有访问都堆结构体第三层拷贝_Block_object_assign//处理结构体中的对象变量retain对象objc_retainBlock//触发block copy_Block_copy//复制block本身如果没有捕获变量这一步结束_Block_object_assign//如果Block捕获变量Runtime会调用该方法判断变量类型并处理普通对象、__block变量、block_Block_byref_copy//如果捕获的是__block变量调用该方法复制__block结构体_Block_object_dispose//如果__block变量还有上一步copy内部还会调用_Block_object_assign_Block_byref_release//释放block捕获的变量