日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS之Block总结以及内存管理

發(fā)布時間:2025/4/16 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS之Block总结以及内存管理 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

block定義

struct Block_descriptor {unsigned long int reserved;unsigned long int size;void (*copy)(void *dst, void *src);void (*dispose)(void *); }; struct Block_layout {void *isa;int flags;int reserved; void (*invoke)(void *, ...);struct Block_descriptor *descriptor;/* Imported variables. */ };

從上面代碼看出,Block_layout就是對block結(jié)構(gòu)體的定義:

isa指針:指向表明該block類型的類。

flags:按bit位表示一些block的附加信息,比如判斷block類型、判斷block引用計(jì)數(shù)、判斷block是否需要執(zhí)行輔助函數(shù)等。

reserved:保留變量,我的理解是表示block內(nèi)部的變量數(shù)。

invoke:函數(shù)指針,指向具體的block實(shí)現(xiàn)的函數(shù)調(diào)用地址。

descriptor:block的附加描述信息,比如保留變量數(shù)、block的大小、進(jìn)行copy或dispose的輔助函數(shù)指針。

variables:因?yàn)閎lock有閉包性,所以可以訪問block外部的局部變量。這些variables就是復(fù)制到結(jié)構(gòu)體中的外部局部變量或變量的地址。

舉例,定義一個最簡單block 打印hello world:

int main(int argc, const char * argv[]) {void (^block)()=^{printf("hello world");};block();return 0; }

使用clang指令

clang -rewrite-objc main.m

得到一個cpp文件,編譯后,你就會看到什么是block了

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, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("hello world"); }static struct __main_block_desc_0 {size_t reserved;size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(int argc, const char * argv[]) {void (*block)()=((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);return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; View Code

你定義完block之后,其實(shí)是創(chuàng)建了一個函數(shù),在創(chuàng)建結(jié)構(gòu)體的時候把函數(shù)的指針一起傳給了block,所以之后可以拿出來調(diào)用。

再看看值捕獲的問題

int main(int argc, const char * argv[]) {int a=10;//__block int a=10; //__block前綴void (^block)()=^{printf("打印a=%d",a);};block();return 0; }

定義block的時候,變量a的值就傳遞到了block結(jié)構(gòu)體中,僅僅是值傳遞,所以在block中修改a是不會影響到外面的a變量的。

而加了__block前綴,編譯后:

struct __Block_byref_a_0 {void *__isa; __Block_byref_a_0 *__forwarding;int __flags;int __size;int a; };struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_a_0 *a; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_a_0 *a = __cself->a; // bound by ref printf("打印a=%d",(a->__forwarding->a));} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) {__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; View Code

并不是直接傳遞a的值了,而是把a(bǔ)的地址(&a)傳過去了,所以在block內(nèi)部便可以修改到外面的變量了。

isa:isa指針,在Objective-C中,任何對象都有isa指針。block 有三種類型:
_NSConcreteGlobalBlock 全局靜態(tài),不會訪問任何外部變量,不會涉及到任何拷貝,比如一個空的block。例如:

#include int main() {^{ printf("Hello, World!\n"); } ();return 0; }

_NSConcreteStackBlock 保存在棧中,出函數(shù)作用域就銷毀,例如:

#include int main() {char a = 'A';^{ printf("%c\n",a); } ();return 0; }

_NSConcreteMallocBlock 保存在堆中,retainCount == 0銷毀

該類型的block都是由_NSConcreteStackBlock類型的block從棧中復(fù)制到堆中形成的。例如下面代碼中,在exampleB_addBlockToArray方法中的block還是_NSConcreteStackBlock類型的,在exampleB方法中就被復(fù)制到了堆中,成為_NSConcreteMallocBlock類型的block:

void exampleB_addBlockToArray(NSMutableArray *array) {char b = 'B';[array addObject:^{printf("%c\n", b);}]; } void exampleB() {NSMutableArray *array = [NSMutableArray array];exampleB_addBlockToArray(array);void (^block)() = [array objectAtIndex:0];block(); }

總結(jié)一下:

_NSConcreteGlobalBlock類型的block要么是空block,要么是不訪問任何外部變量的block。它既不在棧中,也不在堆中,我理解為它可能在內(nèi)存的全局區(qū)。

_NSConcreteStackBlock類型的block有閉包行為,也就是有訪問外部變量,并且該block只且只有有一次執(zhí)行,因?yàn)闂V械目臻g是可重復(fù)使用的,所以當(dāng)棧中的block執(zhí)行一次之后就被清除出棧了,所以無法多次使用。

_NSConcreteMallocBlock類型的block有閉包行為,并且該block需要被多次執(zhí)行。當(dāng)需要多次執(zhí)行時,就會把該block從棧中復(fù)制到堆中,供以多次執(zhí)行。

而ARC和MRC中,還略有不同

題目:下面代碼在按鈕點(diǎn)擊后,在ARC下會發(fā)生什么,MRC下呢?為什么?

@property(nonatomic, assign) void(^block)();- (void)viewDidLoad {[superviewDidLoad];int value = 10;void(^blockC)() = ^{NSLog(@"just a block === %d", value);};NSLog(@"%@", blockC);_block = blockC;}- (IBAction)action:(id)sender {NSLog(@"%@", _block); }

在ARC 打印:

mytest[25284:7473527] test:<__NSMallocBlock__: 0x60000005f3e0> mytest[25284:7473527] NSShadow {0, -1} color = {(null)}

雖然不會crash,第二個是野指針

MRC 會打印:test:<__NSStackBlock__: 0x7fff54941a38> 然后crash

例如:

NSArray *testArr = @[@"1", @"2"];NSLog(@"block is %@", ^{NSLog(@"test Arr :%@", testArr);});//結(jié)果:block is <__NSStackBlock__: 0x7fff54f3c808>void (^TestBlock)(void) = ^{NSLog(@"testArr :%@", testArr);};NSLog(@"block2 is %@", TestBlock);//block2 is <__NSMallocBlock__: 0x600000045e80>
//其實(shí)上面這句在非arc中打印是?NSStackBlock,?但是在arc中就是NSMallocBlock
//即在arc中默認(rèn)會將block從棧復(fù)制到堆上,而在非arc中,則需要手動copy.

?

循環(huán)引用

  Block的循環(huán)引用是比較容易被忽視,原本也是相對比較難檢查出來的問題。當(dāng)然現(xiàn)在蘋果在XCode編譯的層級就已經(jīng)做了循環(huán)引用的檢查,所以這個問題的檢查就突然變的沒有難度了。

  簡單說一下循環(huán)引用出現(xiàn)的原理:Block的擁有者在Block作用域內(nèi)部又引用了自己,因此導(dǎo)致了Block的擁有者永遠(yuǎn)無法釋放內(nèi)存,就出現(xiàn)了循環(huán)引用的內(nèi)存泄漏。下面舉個例子說明一下:

@interface ObjTest () {NSInteger testValue; } @property (copy, nonatomic) void (^block)(); @end@implement ObjTest - (void)function {self.block = ^() {self.testValue = 100;}; } @end

在這個例子中,ObjTest擁有了一個名字叫block的Block對象;然后在這個Block中,又對ObjTest的一個成員變量testValue進(jìn)行了賦值。于是就產(chǎn)生了循環(huán)引用:ObjTest->block->ObjTest。

  要避免循環(huán)引用的關(guān)鍵就在于破壞這個閉合的環(huán)。在目前只考慮ARC環(huán)境的情況下,筆者所知的只有一種方法可以破壞這個環(huán):在Block內(nèi)部對擁有者使用弱引用。

@interface ObjTest () {NSInteger testValue; } @property (copy, nonatomic) void (^block)(); @end@implement ObjTest - (void)function {__weak ObjTest* weakSelf = self;self.block = ^() {weakSelf.testValue = 100;}; } @end

在單例模式下 Block避免循環(huán)引用,如下:

@interface Singleton : NSObject @property (nonatomic, copy) void(^block)(); + (instancetype)share; @end@implementation Singleton + (instancetype)share {static Singleton *singleton;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{singleton = [[Singleton alloc] init];});return singleton; } @end//============分割線================= //控制器中代碼的實(shí)現(xiàn)@implementation NextViewController- (void)viewDidLoad {[super viewDidLoad];__weak typeof(self) weakSelf=self;void (^blockTest)()=^(){ // NSLog(@"print %@", self);//會內(nèi)存泄漏NSLog(@"print %@", weakSelf);};Singleton *singleton = [Singleton share];singleton.block = blockTest; } - (IBAction)btnClick:(UIButton *)sender {[Singleton share].block(); }- (void)dealloc {NSLog(@"%s", __FUNCTION__); } @end

?為什么iOS中系統(tǒng)的block方法可以使用self

因?yàn)?#xff1a;首先循環(huán)引用發(fā)生的條件就是持有這個block的對象,被block里邊加入的對象持有。當(dāng)然是強(qiáng)引用。
所以UIView的動畫block不會造成循環(huán)引用的原因就是,這是個類方法,當(dāng)前控制器不可能強(qiáng)引用一個類,所以循環(huán)無法形成。

ARC情況下:
1、如果用copy修飾Block,該Block就會存儲在堆空間。則會對Block的內(nèi)部對象進(jìn)行強(qiáng)引用,導(dǎo)致循環(huán)引用。內(nèi)存無法釋放。

解決方法:新建一個指針(__weak typeof(Target) weakTarget = Target )指向Block代碼塊里的對象,然后用weakTarget進(jìn)行操作。就可以解決循環(huán)引用問題。
2、如果用weak修飾Block,該Block就會存放在棧空間。不會出現(xiàn)循環(huán)引用問題。MRC情況下用copy修飾后,如果要在Block內(nèi)部使用對象,則需要進(jìn)行(__block typeof(Target) blockTarget = Target )處理。在Block里面用blockTarget進(jìn)行操作。
返回值類型(^block變量名)(形參列表) = ^(形參列表) {};調(diào)用Block保存的代碼block變量名(實(shí)參);默認(rèn)情況下,,Block內(nèi)部不能修改外面的局部變量Block內(nèi)部可以修改使用__block修飾的局部變量

?

參考 收藏:https://www.zhihu.com/question/30779258/answer/49492783

Objective-C中的Block原理

云端之巔 Objective-C中block的底層原理

iOS學(xué)習(xí)之block總結(jié)及block內(nèi)存管理(必看)

Block總結(jié)以及內(nèi)存管理

轉(zhuǎn)載于:https://www.cnblogs.com/dhui69/p/6541324.html

總結(jié)

以上是生活随笔為你收集整理的iOS之Block总结以及内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。