美文网首页
iOS-block3-__block变量的内存管理、__forw

iOS-block3-__block变量的内存管理、__forw

作者: Imkata | 来源:发表于2019-12-03 09:04 被阅读0次

上文讲了一下__block的原理,但是关于__block还有一些其他东西,这篇文章就来慢慢讲述。

一. __block变量的内存管理

通过上文我们知道,block捕获对象类型的auto变量就会多出两个函数用于做内存管理操作(__main_block_copy_0和__main_block_dispose_0),如下:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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};

block也会使用这两个函数管理__block修饰的变量的内存,这也从侧面证明“编译器会将__block变量包装成一个对象”,这句话是对的,因为只有对象才需要内存管理。
下面我们就研究使用__block修饰的变量的内存管理:

  1. 当block在栈上时,并不会对__block变量产生强引用
  2. 当block被copy到堆时(自己拷贝的或者ARC下系统自动拷贝的)
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会对__block变量形成强引用(retain)

如下图:

__block的内存管理-copy

解释:刚开始的时候,__block变量和block0、block1肯定都在栈区,假如他们同时使用__block变量。
当把block0复制到堆区,也会把__block变量复制到堆区,并且block0强引用__block变量。
再把block1复制到堆区,就不会再次复制__block变量了(因为已经拷贝过了),这时候block0和block1都会强引用着__block变量。

  1. 当block从堆中移除时
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的__block变量(release)

如下图:

__block的内存管理-dispose

解释:当没有block引用着__block变量,__block变量才会被释放。

为什么要这么管理呢?
因为__block变量是个对象,block内部使用了它,所以需要block来管理它的内存。

二. 对象类型的auto变量和__block变量的区别

(static修饰的变量和全局变量在全局区)

先把下面代码转成C++代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int no = 20;
        
        __block int age = 10;
        
        NSObject *object = [[NSObject alloc] init];
        __weak NSObject *weakObject = object;
        
        MJBlock block = ^{
            age = 20;
            
            NSLog(@"%d", no);
            NSLog(@"%d", age);
            NSLog(@"%p", weakObject);
        };
        
            block();
    }
    return 0;
}

转成的C++代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int no;  
  NSObject *__weak weakObject;  
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->weakObject, (void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->weakObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
    
}

可以发现,结构体多了三个成员,分别对应捕获的三个auto变量,如下👇:

 int no;  
 __Block_byref_age_0 *age; // by ref
 NSObject *__weak weakObject;  

1. 对象类型的auto变量、__block变量

相同点:

  1. 当block在栈上时,对它们都不会产生强引用
  2. 当block拷贝到堆上时,都会通过copy函数来处理它们

对象类型的auto变量(假设变量名叫做p)

_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

__block变量(假设变量名叫做a)

_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  1. 当block从堆上移除时,都会通过dispose函数来释放它们

对象类型的auto变量(假设变量名叫做p)

_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

__block变量(假设变量名叫做a)

_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

不同点:

  1. 对于对象类型的auto变量,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  2. 对于__block变量:_Block_object_assign函数只会产生强引用(retain)

2. 普通的的auto变量

例如:int a = 10,就是值引用,然后放到__main_block_impl_0结构体里面。

三. __forwarding指针的作用

我们知道__Block_byref_age_0结构体中的__forwarding指针存的是自己的地址,当我们想要访问age,需要先通过age结构体中的__forwarding获取自己,然后再获取age:

(age->__forwarding->age) = 20;

为什么设计这么奇怪呢?如下图:

__block的__forwarding指针

解释:如果栈上的block复制到堆上了,那么栈上堆上肯定都有一块内存。如果我们想把20赋值到堆上的block,如果不用__forwarding指针,访问栈上的block,就会把20赋值到栈上的block。如果使用__forwarding指针,不管访问的block在哪,最后赋值到的一定是堆上的block。

四. __block修饰的对象类型

以前我们是使用__block修饰基本数据类型,如果使用__block修饰对象类型会怎么样呢?

如下代码,内部会发生什么呢?

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        //不能反过来(如:__weak __block),因为__weak只能修饰OC对象
        __block __weak MJPerson *weakPerson = person;
        
        MJBlock block = ^{
            NSLog(@"%p", weakPerson);
        };
        
        block();
    }
    return 0;
}

转成C++代码,如下:

struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__weak weakPerson;  //外面是弱指针,这里就是弱指针 (以前这里直接是int age;的)
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakPerson_0 *weakPerson; // 默认强指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

首先,发现__main_block_impl_0里面默认有一个强指针指向__Block_byref_weakPerson_0结构体。由于外面是用__weak修饰的,所以__Block_byref_weakPerson_0结构体里面有一个弱指针指向person对象,如下图:

__block修饰对象类型

上面的代码还可以看出__Block_byref_weakPerson_0结构体里面多了两个方法copy和dispose方法。(这两个方法在以前我们使用__block修饰基本数据类型的时候是没有的)

在__Block_byref_weakPerson_0结构体创建里面(就是__block __weak MJPerson *weakPerson = person;的底层实现),传入了copy和dispose两个函数地址,如下:

//copy block内存管理相关
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
    
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->weakPerson, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));

       //__block __weak MJPerson *weakPerson = person;的底层实现
       //就是创建__Block_byref_weakPerson_0结构体
__Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0),
            __Block_byref_id_object_copy_131, //传入copy方法的地址
            __Block_byref_id_object_dispose_131, //传入dispose方法的地址
            person};

        MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 8
 void (*__Block_byref_id_object_dispose)(void*); // 8
 MJPerson *__weak weakPerson;  //外面是弱指针,这里就是弱指针 (以前这里直接是int age;的)
};

//copy 对象内存管理相关
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
//dispose 
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

观察上面的copy,发现它里面也有调用_Block_object_assign函数,(char*)dst就是结构体自己的地址值,但是它却+40 (8+8+4+4+8+8),从上上面我们分析可知:(char*)dst+40就是“MJPerson *__weak weakPerson;”的地址值。然后_Block_object_assign函数再根据传入的对象是强还是弱,再决定retain还是弱引用。所以上图的第二根指针的强弱,取决于外面修饰对象的的是强指针还是弱指针?

当block销毁的时候,block内部会调用__main_block_dispose_0函数,然后上图的第一根线就没了。第一根线就没了,__Block_byref_weakPerson_0也会销毁,它也会调用自己里面的__Block_byref_id_object_dispose_131函数,__Block_byref_id_object_dispose_131函数再把person引用计数器减一(或者销毁)。

总结:

对于block:

  1. 如果block是在栈上,将不会对__Block_byref_weakPerson_0产生强引用
  2. 如果栈上的block被拷贝到堆上
    _Block_object_assign函数会对__Block_byref_weakPerson_0产生强引用
  3. 如果堆上的block被移除
    _Block_object_dispose函数会对__Block_byref_weakPerson_0产生弱引用或者移除

对于__block修饰的对象类型:

  1. 当__block变量在栈上时,不会对指向的对象产生强引用
  2. 当__block变量被copy到堆时(自己拷贝的或者ARC下系统自动拷贝的)
    会调用__block变量内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain,一直是弱的。这个特例只会在MRC并且是__block修饰对象类型才有
  3. 如果__block变量从堆上移除
    会调用__block变量内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放指向的对象(release)

下面验证上面的“注意”:

将项目切换成MRC环境,使用__block修饰对象类型,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block MJPerson *person = [[MJPerson alloc] init];
        
        MJBlock block = [^{
            NSLog(@"%p", person);
        } copy];

        [person release];

        block();  //在此处打断点

        [block release];
    }
    return 0;
}

在上面代码断点处,可以发现打印:“[MJPerson dealloc]”,此时block还没释放,但是person却不在了,验证了我们上面说的“注意”。根据这一点,在MRC环境下,我们可以使用__block来解决循环引用,这个后面会说到。

解释如上代码,当block被拷贝到堆上的时候,__block修饰的person也会被拷贝到堆上,这时候会调用__block修饰的变量内部的copy函数,MRC环境下,copy函数内部只会对person对象产生弱引用。如下图:

__block修饰的对象.png

如果将上面代码的__block去掉,那么在断点处就不会打印:“[MJPerson dealloc]”,之后block释放之后person才会释放。这时候就是block直接捕获person对象了,如下图:

直接引用.png

五. block循环引用

1. 循环引用产生的原因

如下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;
        person.block = ^{
            NSLog(@"age is %d", person.age);
        };
    }
    
    NSLog(@"111111111111");
    return 0;
}

首先“ MJPerson *person = [[MJPerson alloc] init];”执行完,会有个person强指针指向MJPerson。MJPerson里面有个_block,当执行完 person.block = ^{NSLog(@"age is %d", person.age);},MJPerson和block之间的循环引用就会产生,如下图:

循环引用.png

(block是封装了函数调用以及函数调用环境的OC对象)

同理,self和block之间的循环引用如下图:

block捕获self

因为self是局部变量,所以block也会捕获self,也会造成循环引用。block底层C++代码如下:

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *const __strong self; //block内部有个self强指针,指向self对象
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

2. ARC如何解决循环引用

① 用__weak解决

首先,我们想一下,如何解决循环引用,肯定是把其中一根线变成虚线就可以了,但是把哪根线变成虚线呢?我们想了下,person中的block属性不能变成虚的,因为person要拥有block,那么只能把block里面的person变成虚线了,怎么把block里面的person变成虚线呢?其实就是在上面的代码,把__strong变成__weak就好了,前面我们已经学过了,在外面用__weak修饰,里面就会变成__weak了,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;

        //__weak MJPerson *weakPerson = person; //这样写死了,不推荐
        __weak typeof(person) weakPerson = person;
        person.block = ^{
            NSLog(@"age is %d", weakPerson.age);
        };
    }
    
    NSLog(@"111111111111");
    return 0;
}

这时候block底层C++代码如下:

struct __MJPerson__test_block_impl_0 {
  struct __block_impl impl;
  struct __MJPerson__test_block_desc_0* Desc;
  MJPerson *const __weak weakPerson;  //weakPerson弱指针
  __MJPerson__test_block_impl_0(void *fp, struct __MJPerson__test_block_desc_0 *desc, MJPerson *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

使用__weak后图示如下:

__weak.png

解释:当{}里面的代码执行完,MJPerson *person局部变量就会消失,person指向MJPerson的线就会消失,这时候没有实线指向MJPerson,MJPerson就会销毁,MJPerson销毁后,MJPerson指向block的线就会消失,这时候没有实线指向block,block也会销毁,循环引用解除。

② 用__unsafe_unretained解决

上面的代码,把__weak替换成__unsafe_unretained也能解决循环引用,代码就省略了。

它们有什么区别呢?直接看字面意思,__weak:不会产生强引用,__unsafe_unretained:不会产生强引用,不安全。

__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,这时候如果再去访问指针指向的地址就会报野指针错误

如下图,如果使用__weak,当weakPerson指向的对象销毁时,会把weakPerson置为nil,使用__unsafe_unretained就不会。

__weak会把指针置为nil

③ 用__block解决(必须要调用block)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //使用__block解决循环引用
        __block MJPerson *person = [[MJPerson alloc] init];
        
        person.age = 10;
        person.block = ^{
            NSLog(@"age is %d", person.age);
            person = nil;
        };
        
        person.block();
    }
    
    NSLog(@"111111111111");
    return 0;
}

如上代码,即可解决循环引用。

首先我们分析这里的循环引用是如何产生的。
前面我们说过使用__block修饰对象,block内部会捕获__block变量,__block变量内部又有person对象,而且默认是强引用,对象又拥有block,所以block、__block变量、person对象之间会有循环引用,示意图如下:

循环引用.png

所以,如果想要解决__block的循环引用,我们可以调用block,并且在block里面把person对象置为nil,这样__block变量就不会持有person对象了,循环引用被打破,代码如上,示意图如下:(以前我们也验证过,__block变量里的person指针就是我们在外面拿到的person指针)

打破三者循环引用

总结:不用想也知道ARC环境下使用__weak解决循环引用最好。

3. MRC如何解决循环引用

我们知道MRC不支持使用__weak的,否则报错“Cannot create __weak reference in file using manual reference counting”
所以,相对于ARC,MRC就只有两种方式解决循环引用了。

① 使用__unsafe_unretained解决

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // MRC不支持__weak的否则报错:👇
        // Cannot create __weak reference in file using manual reference counting
        
        __unsafe_unretained MJPerson *person = [[MJPerson alloc] init];

        person.age = 10;
        person.block = [^{
            NSLog(@"age is %d", person.age);
        } copy];
        
        [person release];
    }
    
    NSLog(@"111111111111");
    return 0;
}

如上代码,使用__unsafe_unretained修饰person,当block引用person的时候,person的引用计数器不会增加,还是刚开始创建的时候的1,所以当“ [person release];”person就挂了。
如果不使用__unsafe_unretained修饰,person创建的时候引用计数器为1,因为person默认强指针,所以block引用person的时候又加1,所以当“ [person release];”person引用计数器还是1,不会挂。

② 使用__block解决

还记得上面验证的“注意”吗?MRC环境,使用__block修饰对象类型,对象不会被retain,所以也可以解决循环引用。

代码省略,可自行将上面的__unsafe_unretained替换成__block进行验证。

补充1:block属性的建议写法

MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);

ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
  1. 使用copy会将栈上的block拷贝到堆上,如果不使用copy,block就不会拷贝到堆上,因为MRC编译器不会自动copy,所以只能用copy。
  2. 以前我们说过,ARC环境,并且把block赋值给强指针,编译器会自动把block拷贝到堆上,所以ARC使用copy和strong都可以,没区别。
  3. 为了统一好记,我们统一对block使用copy。

为什么一定要copy到堆上呢?如果在栈上我们就无法控制block的生命周期,在堆上,什么时候让它生让它死都可以。

补充2:__strong typeof(weakSelf) myself = weakSelf;

为什么要这么写:

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) myself = weakSelf;
//报错:
//Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to strong variable first
    NSLog(@"age is %d", myself->_age);
};
  1. 原因一,如果在block内部通过weakSelf->_age会报错“弱指针不允许访问,因为有可能为null,让你使用强指针”,所以我们就用__strong强指针来访问self。
  2. 原因二,访问self的时候我们使用一个临时的强指针来访问self,这样在整个block执行期间,可以保证self对象不会被销毁,同时,block调用完后,临时的强指针被销毁,一切又回归原来的样子。这样既能保证整个block执行期间,self对象不会被销毁,又能保证不会产生循环引用。

面试题:

  1. block的原理是怎样的?本质是什么?
    block是封装了函数调用以及调用环境的OC对象,比如函数的调用地址、捕获的变量都封装到了里面。

  2. __block的作用是什么?有什么使用注意点?
    编译器会将__block变量包装成一个对象,就是__Block_byref_person_0这种结构体,可以解决block内部无法修改auto变量的问题(自己思考为什么不能修改)。
    使用注意:__block变量内部自己也会进行内存管理,而且MRC环境下,__block修饰对象,对象不会被retain的。

  3. 为什么使用__block修饰auto变量,在block内部就能修改此变量了?
    block内部有个指针指向__Block_byref_person_0结构体,通过访问结构体,再通过结构体访问变量进行修改的。

  4. block的属性修饰词为什么是copy?使用block有哪些使用注意?
    block一旦没有进行copy操作,就不会在堆上。使用注意:循环引用问题。

  5. block在修改NSMutableArray,需不需要添加__block?

NSMutableArray *array = [NSMutableArray array];
person.block = ^{
    [array addObject:@"123"];
};  

不需要。
如上代码,这个我们以前说过,给array数组添加成员只是操作array,除非要把array指针指向其他地方才要使用__block(比如:array = nil)。
一般如果没必要不要用__block修饰,因为还要包装一层。

Demo地址:block循环引用

相关文章

  • iOS-block3-__block变量的内存管理、__forw

    上文讲了一下__block的原理,但是关于__block还有一些其他东西,这篇文章就来慢慢讲述。 一. __blo...

  • Android内存管理机制

    Android内存管理机制的定义 内存的分配 内存的回收 管理机制的说明 管理的内存对象 进程 对象 变量 管理的...

  • V语言学习笔记-22内存管理

    内存管理 自动内存管理 因为V语言中限制没有全局变量,没有模块级变量,只有局部变量,变量只能在函数或者方法内部定义...

  • Java学习笔记4(对象与内存的控制)

    Java 内存管理分为:内存分配和内存回收实例变量 和 类变量 局部变量 特点:作用时间短,存储在方法的栈内存中 ...

  • python内存管理

    转载自 内存管理 包括: 变量无须事先声明 变量无须指定类型 不用关心内存管理 变量名会被"回收" del 语句能...

  • JVM 内存管理

    内存管理=内存分配+内存回收 内存分配 JMM指Java内存管理线程之间的共享变量存储在主内存(main memo...

  • js 执行上下文,作用域,变量内存管理

    js 执行上下文,作用域,变量内存管理 JS中变量的定义在内存中包括三个部分:* 变量标示(变量标示存储在内存的栈...

  • Java学习-3-基本数据类型

    变量申请内存来存储值。当创建变量的时候,需要在内存申请空间。内存管理系统根据变量的类型为变量分配存储空间,分配的空...

  • Java基本数据类型

    引言: 1.变量就是申请内存来存储值,在创建变量的时候就需要在内存中申请空间,内存管理系统根据变量的类型为变量分配...

  • 学习四:Java基本数据类型

    变量就是申请内存来存储值。当创建变量的时候,需要在内存中申请空间。内存管理系统根据变量的类型为变量分配存储空间,分...

网友评论

      本文标题:iOS-block3-__block变量的内存管理、__forw

      本文链接:https://www.haomeiwen.com/subject/qnixgctx.html