• [iOS开发]block再学习


    一、认识block

    (一)block定义

    带有自动变量(局部变量)的匿名函数叫做block

    (二)block分类

    • 全局block——__NSGlobalBlock__
    • 堆block——__NSMallocBlock__
    • 栈block——__NSStackBlock__

    总结:

    不使用外界变量的block是__NSGlobalBlock__类型
    使用外界变量的block是__NSMallocBlock__类型
    在堆block拷贝前的block是__NSStackBlock__类型

    除此之外,还有三种系统级别的block类型

    • _NSConcreteAutoBlock
    • _NSConcreteFinalizingBlock
    • _NSConcreteWeakBlockVariable

    二、block循环引用

    (一)循环引用的产生

    self.name = @"Billy";
    self.block = ^{
        NSLog(@"%@", self.name);
    };
    
    • 1
    • 2
    • 3
    • 4

    同时,编译器给出警告:

    ⚠️Capturing 'self' strongly in this block is likely to lead to a retain cycle
    
    • 1

    循环引用的问题在于:

    1. self持有了block
    2. block持有了self(self.name)

    这样就形成了self -> block -> self的循环引用。循环引用时:A、B互相引用,引用计数不能为0,dealloc不会被调用。

    (二)解决循环引用

    1. 强弱共舞

    __weak typeof(self) weakSelf = self;
    self.name = @"Billy";
    self.block = ^{
    	NSLog(@"%@", weakSelf.name);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用 中介者模式 __weak typeof(self) weakSelf = self将循环引用改为weakself -> self -> block -> weakself。表面看上去还是一个“引用圈”,但是weakself -> self这一层是弱引用——引用计数不处理,使用weak表管理。所以此时在页面析构时self就能正常的调用dealloc了。
    但并不是最终的解决方案,此时仍有可能存在着问题,比如如下代码:

    __weak typeof(self) weakSelf = self;
    self.name = @"Billy";
    self.block = ^{
    	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    		NSLog(@"%@", weakSelf.name);
    	});
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这种延时情况,如若调用block之后立马返回上一页进行页面释放,3秒后weakself指向的self已经为nil了,此时的打印就只能打印出null
    于是就有了强弱共舞

    __weak typeof(self) weakSelf = self;
    self.name = @"Billy";
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongSelf.name);
        });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    再加一层临时的强持有,此时的引用就变成了strongself -> weakself -> self -> block -> strongself
    看上去又是一个循环引用,但实际上strongSelf是个临时变量,当block作用域结束后就会释放,从而打破循环引用进行释放(让释放延后了3秒)。

    2. 其他中间者模式

    既然有自动置空,那么也可以手动置空。

    __block UIViewController *viewController = self;
    self.name = @"Billy";
    self.block = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", viewController.name);
            viewController = nil;
        });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上述代码也是使用 中介者模式 打破循环应用的——使用viewController作为中介者代替self从而打破循环引用
    此时的引用情况为viewController -> self -> block -> viewController (viewController在用完之后手动置空),这里依然会存在问题:但是只要不调用block,仍然存在着循环应用
    解决循环引用还有一种方式——不引用

    self.name = @"Felix";
    self.block = ^(UIViewController *viewController) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", viewController.name);
            viewController = nil;
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上述代码使用当前viewController作为参数传入block时拷贝一份,就不会出现持有的情况,同时还能使用self的内存空间,能够完美避免循环引用。

    3. Q&A

    Q:Masonry中是否存在循环引用?

    A:Monsary使用的block是当做参数传递的,即便block内部持有self,设置布局的view持有block,但是block不持有view,当block执行完后就释放了,self的引用计数-1,所以block也不会持有self,所以不会导致循环引用

    Q:[UIView animateWithDuration: animations:]中是否存在循环引用?

    A:UIView动画是类方法,不被self持有(即self持有了view,但view没有实例化)所以不会循环引用

    三、block底层

    (一)block本质

    1.

    int main(int argc, const char * argv[]) {
    //    int a = 10;
        void(^block)(void) = ^{
            printf("Billy");
    //        printf("Billy - %d",a);
        };
        block();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    转化成C++代码:

    int main(int argc, const char * 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);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    main函数中可以看到block的赋值是__main_block_impl_0类型,它是C++中的构造函数:

    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;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    block的本质是个 __main_block_impl_0 的结构体对象

    fp传递了具体的block的实现__main_block_func_0,然后保存在block结构体的impl中,这就说明了block声明只是将block实现保存起来,具体的函数实现需要自行调用。

    2. 当block为堆block时(外接传入变量)重新clang编译

    int main(int argc, const char * argv[]) {
        int a = 10;
        void(^block)(void) = ^{
    //        printf("Billy");
            printf("Billy - %d",a);
        };
        block();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    此时的block构造函数中就会多出一个参数a,并且在block结构体中也会多出一个属性a

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        ...
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
            printf("Billy - %d",a);
        }
    
    ...
    
    int main(int argc, const char * argv[]) {
        int a = 10;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    接着看__main_block_func_0的实现

    __cself__main_block_impl_0的指针,即block本身
    int a = __cself->aint a = block->a
    由于a只是个属性,所以是堆block只是值拷贝(值相同,内存地址不同)
    这也是为什么捕获的外界变量不能直接进行操作的原因,如a++会报错

    __block修饰外界变量

    int main(int argc, const char * argv[]) {
        __block int a = 10;
        void(^block)(void) = ^{
    //        printf("Billy");
            printf("Billy - %d",a);
        };
        block();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    请添加图片描述
    __block修饰的属性在底层会生成响应的结构体,保存原始变量的指针,并传递一个指针地址给block,因此是指针拷贝。

    (二)block的copy

    我们打断点调试:
    请添加图片描述
    可以看到objc_retainBlock,继续step into:
    请添加图片描述
    可以看到调用block的copy函数:_Block_copy

    1.

    struct Block_layout *aBlock;
    
    if (!arg) return NULL;
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    // 判断flags标识位
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    static int32_t latching_incr_int(volatile int32_t *where) {
        while (1) {
            int32_t old_value = *where;
            if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
                return BLOCK_REFCOUNT_MASK;
            }
            if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
                return old_value+2;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    为什么引用计数是 +2 而不是 +1 ?因为flags的第一号位置已经存储着释放标记。

    2.

    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    
    • 1
    • 2
    • 3

    是否是全局block——是的话直接返回block

    3.

    else {
        // Its a stack block.  Make a copy.
        size_t size = Block_size(aBlock);
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        // 开辟堆空间
        if (!result) return NULL;
        memmove(result, aBlock, size); // bitcopy first
    #if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
    
    #if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            uintptr_t oldDesc = ptrauth_blend_discriminator(
                        &aBlock->descriptor,
                        _Block_descriptor_ptrauth_discriminator);
            uintptr_t newDesc = ptrauth_blend_discriminator(
                        &result->descriptor,
                        _Block_descriptor_ptrauth_discriminator);
    
            result->descriptor =
                        ptrauth_auth_and_resign(aBlock->descriptor,
                                                ptrauth_key_asda, oldDesc,
                                                ptrauth_key_asda, newDesc);
        }
    #endif
    #endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    1. 先通过malloc在堆区开辟一片空间
    2. 再通过memmove将数据从栈区拷贝到堆区
    3. invokeflags同时进行修改
    4. block的isa标记成_NSConcreteMallocBlock

    (三)__block的深入探究

    1. 第一层拷贝(block)

    block中的第一层拷贝其实就是上面的_Block_copy,将block从栈拷贝到堆。

    2. 第二层拷贝(捕获变量的内存空间)

    在函数声明时会传__main_block_desc_0_DATA结构体,在里面又会去调用__main_block_copy_0函数,__main_block_copy_0里面会调用_Block_object_assign——这就是第二层拷贝的调用入口。

    //
    // When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
    // to do the assignment.
    //
    void _Block_object_assign(void *destArg, const void *object, const int flags) {
        const void **dest = (const void **)destArg;
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_OBJECT:
            /*******
            id object = ...;
            [^{ object; } copy];
            ********/
    
            _Block_retain_object(object);
            *dest = object;
            break;
    
          case BLOCK_FIELD_IS_BLOCK:
            /*******
            void (^object)(void) = ...;
            [^{ object; } copy];
            ********/
    
            *dest = _Block_copy(object);
            break;
        
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            /*******
             // copy the onstack __block container to the heap
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __block ... x;
             __weak __block ... x;
             [^{ x; } copy];
             ********/
    
            *dest = _Block_byref_copy(object);
            break;
            
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
            /*******
             // copy the actual field held in the __block container
             // Note this is MRC unretained __block only. 
             // ARC retained __block is handled by the copy helper directly.
             __block id object;
             __block void (^object)(void);
             [^{ object; } copy];
             ********/
    
            *dest = object;
            break;
    
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            /*******
             // copy the actual field held in the __block container
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __weak __block id object;
             __weak __block void (^object)(void);
             [^{ object; } copy];
             ********/
    
            *dest = object;
            break;
    
          default:
            break;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    根据flags & BLOCK_ALL_COPY_DISPOSE_FLAGS进到不同分支来处理捕获到的变量

    枚举值数值含义
    BLOCK_FIELD_IS_OBJECT3对象
    BLOCK_FIELD_IS_BLOCK7block变量
    BLOCK_FIELD_IS_BYREF8__block修饰的结构体
    BLOCK_FIELD_IS_WEAK16__weak修饰的变量
    BLOCK_BYREF_CALLER128处理block_byref内部对象内存的时候会加的一个额外的标记,配合上面的枚举一起使用

    此时捕获到的变量是被__block修饰的BLOCK_FIELD_IS_BYREF类型,就会调用*dest = _Block_byref_copy(object);

    static struct Block_byref *_Block_byref_copy(const void *arg) {
        // 临时变量的保存
        struct Block_byref *src = (struct Block_byref *)arg;
    
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // src points to stack
            // 用原目标的大小在堆区生成一个Block_byref
            struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
            copy->isa = NULL;
            // byref value 4 is logical refcount of 2: one for caller, one for stack
            copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
            
            // 原来的区域和新的区域都指向同一个对象,使得block具备了修改能力
            copy->forwarding = copy; // patch heap copy to point to itself
            src->forwarding = copy;  // patch stack to point to heap copy
            copy->size = src->size;
    
            if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                // Trust copy helper to copy everything of interest
                // If more than one field shows up in a byref block this is wrong XXX
                struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
                struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
                copy2->byref_keep = src2->byref_keep;
                copy2->byref_destroy = src2->byref_destroy;
    
                if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                    struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                    struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                    copy3->layout = src3->layout;
                }
                // 第三层拷贝
                (*src2->byref_keep)(copy, src);
            }
            else {
                // Bitwise copy.
                // This copy includes Block_byref_3, if any.
                memmove(copy+1, src+1, src->size - sizeof(*src));
            }
        }
        // already copied to heap
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
            latching_incr_int(&src->forwarding->flags);
        }
        
        return src->forwarding;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 用原目标name的大小在堆区生成一个Block_byref
    • copy->forwarding = copy; & src->forwarding = copy;——原来的区域和新的区域都指向同一个对象,使得block具备了修改能力
    • (*src2->byref_keep)(copy, src)开始第三层拷贝

    3. 第三层拷贝(拷贝对象)

    (*src2->byref_keep)(copy, src)点进去会来到Block_byref结构来,而byref_keepBlock_byref的第5个属性

    struct Block_byref {
        void * __ptrauth_objc_isa_pointer isa;
        struct Block_byref *forwarding;
        volatile int32_t flags; // contains ref count
        uint32_t size;
    };
    
    struct Block_byref_2 {
        // requires BLOCK_BYREF_HAS_COPY_DISPOSE
        BlockByrefKeepFunction byref_keep;
        BlockByrefDestroyFunction byref_destroy;
    };
    
    struct Block_byref_3 {
        // requires BLOCK_BYREF_LAYOUT_EXTENDED
        const char *layout;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    请添加图片描述
    第5位就等于byref_keep,所以在第二层拷贝时会调用__Block_byref_id_object_copy_131

    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这个(char*)dst + 40看到__Block_byref_name_0就顿悟了,刚好取得变量name对象。

    struct __Block_byref_name_0 {
      void *__isa;
    __Block_byref_name_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSString *name;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    _Block_object_assign在对BLOCK_FIELD_IS_OBJECT情况时会做出如下操作:

    	case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
            /*******
             // copy the actual field held in the __block container
             // Note this is MRC unretained __block only. 
             // ARC retained __block is handled by the copy helper directly.
             __block id object;
             __block void (^object)(void);
             [^{ object; } copy];
             ********/
    
          *dest = object;
          break;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    block捕获的外接变量由ARC自动管理,捕获到name进行拷贝
    block中有三层拷贝:拷贝block、拷贝捕获变量的内存地址、拷贝对象

    4. _Block_object_dispose

    void _Block_object_dispose(const void *object, const int flags) {
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            // get rid of the __block data structure held in a Block
            _Block_byref_release(object);
            break;
          case BLOCK_FIELD_IS_BLOCK:
            _Block_release(object);
            break;
          case BLOCK_FIELD_IS_OBJECT:
            _Block_release_object(object);
            break;
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            break;
          default:
            break;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    // API entry point to release a copied Block
    void _Block_release(const void *arg) {
        struct Block_layout *aBlock = (struct Block_layout *)arg;
        if (!aBlock) return;
        if (aBlock->flags & BLOCK_IS_GLOBAL) return;
        if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    
        if (latching_decr_int_should_deallocate(&aBlock->flags)) {
            _Block_call_dispose_helper(aBlock);
            _Block_destructInstance(aBlock);
            free(aBlock);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 如果是释放对象就什么也不做(自动释放)
    • 如果是__block修饰,就将指向指回原来的区域并使用free释放
  • 相关阅读:
    Linux安装MySQL(源码安装)
    【2023,学点儿新Java-16】编程语言的学习方法总结 | 编程的本质和架构 | 如何深度理解编程知识和技能 | 如何成为优秀的软件开发工程师 | 附:Java初学者的困惑!
    矩阵分析与应用+张贤达
    【ARC 自动引用计数 Objective-C语言】
    牛客手速月赛61 F(又是被20级打爆的一天)
    PyTorch 从tensor.grad 看 backward(权重参数) 和 gradient accumulated
    聊聊Java中的自定义异常
    【开源】基于JAVA的校园二手交易系统
    2308,2314,2324,2329,2339,2346
    ffmpeg的下载和编译(vs2022)
  • 原文地址:https://blog.csdn.net/weixin_52192405/article/details/126040210