[Object-C]_[初级]_[关于块block的引用外部变量的规则]

场景

  1. 在开发 Object-C 程序时, 很多情况下会用到它的块 block 特性, 这个 block 其实就是 lambda 表达式. 这个 blocklambda有什么区别, 还有什么需要注意的编程点?

  2. 我们在使用 dispatch_async 函数进行 GCD 异步编程时, 在 block 里引用的外部范围的 object 变量是否需要 retain? 如果不 retain 的话, autoreleasepoolblock 所属的范围结束后调用对象的 release 方法那不是会销毁,而 block之后再调用从而导致野指针?

说明

  1. Object-C 在现在逐步被 Swift 代替开发新的模块. Apple 的战略估计是类似 Android 系统里 Koltin 代替 Java. 但是在它们之间也可以互相调用. 所以仍然使用 Object-C 开发 iOSmacOS 程序也未尝不可. Object-C 的其中两个优势是还可以和 C++ 混编,轻松调用C/C++API. 当然和 Java 不同, Object-C 没有继续被 Apple 发展,所以它的性能在未来应该会比 Swift 低的 is-swift-faster-than-objective-c. 这些属于某个公司的语言, Object-C, Swift, go , Java, Koltin 个人觉得使用寿命肯定不如社区语言 C/C++,Python的. 所以我们在需要 Object-C 开发时, Object-C 只作为胶水语言,主要逻辑还是使用 C/C++ 开发会更好, 这样性能肯定不会低于 Swift, 大部分逻辑还可以跨平台使用.

  2. 我们通常会使用 dispatch_async 把数据发到界面线程处理,比如弹出一个警告对话框,像我之前说的在 Win32 界面开发里的做法原理是一样的.DispatchAsync使用lambda表达式来简化发送数据到界面线程.

dispatch_async(dispatch_get_main_queue(),^(){
	// 弹出警告对话框.
});
  1. Object-C 开发中, 我都会在 xcode里关闭 arc, 自己控制 Object-C 的引用计数,也就是所谓的 mrc . 这样能更好的精确控制对象的生命周期,减少内存使用,提高性能,即使使用自动计数也会有内存泄漏的情况. 前面的两个问题都可以归结到 block 对外部变量的引用规则。以下我们就来说说,并且是在没有自动引用计数的前提下,也能方便打印出实际的引用计数个数,还有就是 arcvoid*Object-c 对象不能直接转换,还有一些限制, 还是比较麻烦的. 目前来说,搞了 C++ 还是比较喜欢手动控制内存对象的生命周期,不太喜欢编译器做太多事情.

block

  1. 块在 OC 里作为一个 lambda 表达式, 用的频率还是很高的, 比如 dispatch_async 调用, 调用一些比较排序函数. 在块里的引用外部变量,需要知道它们的规则,才能避免出错. 以下例子我调整项目的为 mrc,以便知道引用计数做了什么变化。

图1:
在这里插入图片描述

  1. 以下是引用普通类型变量的规则:
    1. 引用全局变量(可改)和本地静态变量(可改).
    2. 引用全局函数.
    3. 所处的封闭范围的本地变量和函数参数.(只读)
      – 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
    4. __block声明的本地变量,会传递引用.
void testBlockTypeOfVariable(int times)
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    
    int y = 100;
    __block int x = 10;
    static BOOL hasThumbnail = NO;
    

    // 块声明:
    // block赋值时可以赋值给 typedef 声明的块类型或者块的声明类型。
    // typedef void (^dispatch_block_t)(void);
    void (^blockTemp)() = ^(){
        NSLog(@"== 输出类型变量 ==");
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        PRINT(@"y is %d",y);
        PRINT(@"x is %d",x);
    };
    
    
    NSLog(@"调用 blockWork 之前");
    blockTemp();
    
    // 1. 引用全局变量(可改)和本地静态变量(可改).
    // 2. 引用全局函数.
    // 3. 所处的封闭范围的本地变量和函数参数.(只读)
    //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
    // 4.__block声明的本地变量,会传递引用.
    dispatch_block_t blockWork = ^(){
        NSLog(@"========= blockWork ENTER =========");
        int number = 10;
        
        // 1. 引用全局变量(可改)和本地静态变量(可改).
        gNumber+=10;
        hasThumbnail = YES;
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        
        // 2. 引用全局函数.
        PRINT(@"PRINT 全局函数");
        
        // 3. 所处的封闭范围的本地变量和函数参数.(只读)
        //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
        // 不允许写
        // y+=100;
        PRINT(@"y is %d",y);
        
        // 4.__block声明的本地变量,会传递引用,可改.
        x+=number;
        x+=y;
        PRINT(@"x is %d",x);
        
        NSLog(@"=========  blockWork LEAVE ========= ");
    };
    blockWork();
    
    NSLog(@"调用 blockWork 之后");
    blockTemp();

    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}

  1. 以下是引用 Object-C 对象的规则:
    1. 复制块时,块会对块里所引用的外部 object-c 对象创建一个 strong 引用, 即它的引用计数+1.
    2. 块释放时,也会对应的对所引用的 object-c 的引用计数-1.
    3. 所以在块里的外部 object-c 对象,不需要担心它的声明周期,即使是异步执行的块. 比如 dispatch_async 函数.
    4. 赋值块给Obj类的func属性, 注意需要设置为 copy(arcstrong) 属性块才会在堆里创建而不至于在函数执行完之后失效.
    5. 注意,在给块属性设置copy 时,如果是确定是线程安全的, 那么可以设置nonatomic,不然在设置属性为nil时,并不会立即release 掉块,而是加入到自动释放池里.
void testObjectCObject()
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    NSString* str = [NSString stringWithFormat:@"%@",@"Tobey"];
    
    // 阶段1: 创建block,并输出引用的 NSString* 的引用计数.
    dispatch_block_t blockObject = ^(){
        NSLog(@"========= blockObject ENTER =========");
        PRINT(@"%@",str);
        PRINT(@"str retainCount %d",[str retainCount]);
        
        NSLog(@"=========  blockObject LEAVE ========= ");
    };
    blockObject();
    
    // 阶段2: 调用 Block_copy 函数复制块.
    // 1. 复制块时,块会对块里所引用的外部object-c对象创建一个 strong引用, 即它的引用计数+1.
    // 2. 块释放时,也会对应的对所引用的object-c的引用计数-1.
    // 3. 所以在块里的外部object-c对象,不需要担心它的声明周期,即使是异步执行的块. 比如 dispatch_async 函数.
    PRINT(@"Copy block");
    dispatch_block_t blockObject2 = Block_copy(blockObject);
    blockObject2();
    Block_release(blockObject2);
    PRINT(@"Release block");
    PRINT(@"str retainCount %d",[str retainCount]);
    
    // 阶段3: 赋值块给Obj类的func属性, 注意需要设置为 copy 属性块才会在堆里创建而不至于在函数执行完之后失效.
    // 1. 注意,在给块属性设置copy时,如果是确定是线程安全的, 那么可以设置nonatomic,不然在设置属性为nil时,并不会立即release 掉块,而是加入到自动释放池里.
    PRINT(@"赋值块给Object-C的成员属性(copy).");
//    @autoreleasepool {
        Obj* o = [Obj new];
        o.func = blockObject;
        o.str = str;
        
        o.func();
        PRINT(@"释放块属性.");
        o.func = nil;
        o.str = nil;
//    }
    blockObject();
    
    // 阶段4: 异步调用块.dispatch_async函数复制了块. 在块执行完之后会释放块.
    dispatch_async(dispatch_get_main_queue(),blockObject);
    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}
  1. 以下是引用 cpp 对象的规则:
    1. 未声明__block的C++对象,必须有一个const copy拷贝构造函数.在块定义时,就会调用const copy拷贝构造函数.
    2. 声明_block可以必须有一个 copy构造函数,并且copy比const copy优先._block声明的cpp对象,不会调用拷贝构造函数,除非对block进行复制才会调用一次拷贝构造函数.块在拷贝时,会对引用的对象进行调用拷贝构造函数.
void testCppObject()
{
    // 阶段1: const 的外部对象.
    // 1. 未声明__block的C++对象,必须有一个const copy拷贝构造函数.
    // 2. 在进入block前已经调用了两次拷贝构造函数?什么原因?
    // 3. 应该是在复制block时,因为引用了A本地对象,调用了一次A 对象的拷贝构造函数放在栈里,
    //    接着这个栈里的对象赋值给block的局部变量a1又拷贝了一次.
    A a1;
    
    NSLog(@"================ funcBlockCpp declare ================");
    dispatch_block_t funcBlockCpp = ^(){
        
        // const 类型不允许赋值.
        // a1.i = 78;
        NSLog(@"================ funcBlockCpp BEGIN ================");
        NSLog(@"a1.i : %d",a1.i);
        NSLog(@"================ funcBlockCpp END ================");
    };

    funcBlockCpp();
    
    // 阶段2; __block 的引用对象.
    // 1. 声明_block可以必须有一个 copy构造函数,并且copy比const copy优先.
    // 2. 在进入block之前只调用了一次拷贝构造函数. 尽量使用__block声明C++对象,减少拷贝次数.
    // 3. __block的C++对象,在复制block时就调用了A的拷贝构造函数放到栈里,因为声明了__block为mutable的,
    //   block里的局部变量a2就只是引用了block栈里的A对象.
    __block A a2;
    NSLog(@"================ funcBlockCpp2 declare ================");
    dispatch_block_t funcBlockCpp2 = ^(){
        NSLog(@"================ funcBlockCpp BEGIN ================");
        NSLog(@"a2.i : %d",a2.i);
        a2.i = 192;
        NSLog(@"================ funcBlockCpp END ================");
    };
    
    funcBlockCpp2();
    
    // 阶段3: 复制块.
    NSLog(@"================ 复制funcBlockCpp2 ================");
    dispatch_block_t funcBlockCpp2Copy = Block_copy(funcBlockCpp2);
    
    funcBlockCpp2Copy();
    Block_release(funcBlockCpp2Copy);
    
}

完整代码

//
//  main.m
//  test-object-c-block
//
//  Created by sai on 4/23/20.
//  Copyright (c) 2020 tobey. All rights reserved.
//

#import <Foundation/Foundation.h>
#include <vector>

NSString* gUrl = @"https://infoworld.blog.csdn.net";
int gNumber = 100;
BOOL shouldKeepRunning = YES;        // global

class A{
public:
    A():i(88){
        NSLog(@"A() -> i: %d",i);
    }
    
    A(const A& a){
        
        i = a.i;
        i+=1;
        NSLog(@"A(const A& a) -> i: %d",i);
    }
    
    A(A& a){
        
        i = a.i;
        i+=100;
        NSLog(@"A(A& a) -> i: %d",i);
    }
    
    ~A(){
        NSLog(@"~A() -> i: %d",i);
    }
    int i;
};

// Object-C 对象
@interface Obj : NSObject

@property (nonatomic,copy,readwrite) dispatch_block_t func;
@property (copy,readwrite) NSString* str;

@end

@implementation Obj

@synthesize func;
@synthesize str;

@end

void PRINT(NSString *format, ...)
{
    va_list args;
    va_start(args, format);
    NSLogv([@" -> " stringByAppendingString:format],args);
    va_end(args);
}

void testObjectCObject()
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    NSString* str = [NSString stringWithFormat:@"%@",@"Tobey"];
    
    // 阶段1: 创建block,并输出引用的 NSString* 的引用计数.
    dispatch_block_t blockObject = ^(){
        NSLog(@"========= blockObject ENTER =========");
        PRINT(@"%@",str);
        PRINT(@"str retainCount %d",[str retainCount]);
        
        NSLog(@"=========  blockObject LEAVE ========= ");
    };
    blockObject();
    
    // 阶段2: 调用 Block_copy 函数复制块.
    // 1. 复制块时,块会对块里所引用的外部object-c对象创建一个 strong引用, 即它的引用计数+1.
    // 2. 块释放时,也会对应的对所引用的object-c的引用计数-1.
    // 3. 所以在块里的外部object-c对象,不需要担心它的声明周期,即使是异步执行的块. 比如 dispatch_async 函数.
    PRINT(@"Copy block");
    dispatch_block_t blockObject2 = Block_copy(blockObject);
    blockObject2();
    Block_release(blockObject2);
    PRINT(@"Release block");
    PRINT(@"str retainCount %d",[str retainCount]);
    
    // 阶段3: 赋值块给Obj类的func属性, 注意需要设置为 copy 属性块才会在堆里创建而不至于在函数执行完之后失效.
    // 1. 注意,在给块属性设置copy时,如果是确定是线程安全的, 那么可以设置nonatomic,不然在设置属性为nil时,并不会立即release 掉块,而是加入到自动释放池里.
    PRINT(@"赋值块给Object-C的成员属性(copy).");
//    @autoreleasepool {
        Obj* o = [Obj new];
        o.func = blockObject;
        o.str = str;
        
        o.func();
        PRINT(@"释放块属性.");
        o.func = nil;
        o.str = nil;
//    }
    blockObject();
    
    // 阶段4: 异步调用块.dispatch_async函数复制了块. 在块执行完之后会释放块.
    dispatch_async(dispatch_get_main_queue(),blockObject);
    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}

void testBlockTypeOfVariable(int times)
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    
    int y = 100;
    __block int x = 10;
    static BOOL hasThumbnail = NO;
    

    // 块声明:
    // block赋值时可以赋值给 typedef 声明的块类型或者块的声明类型。
    // typedef void (^dispatch_block_t)(void);
    void (^blockTemp)() = ^(){
        NSLog(@"== 输出类型变量 ==");
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        PRINT(@"y is %d",y);
        PRINT(@"x is %d",x);
    };
    
    
    NSLog(@"调用 blockWork 之前");
    blockTemp();
    
    // 1. 引用全局变量(可改)和本地静态变量(可改).
    // 2. 引用全局函数.
    // 3. 所处的封闭范围的本地变量和函数参数.(只读)
    //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
    // 4.__block声明的本地变量,会传递引用.
    dispatch_block_t blockWork = ^(){
        NSLog(@"========= blockWork ENTER =========");
        int number = 10;
        
        // 1. 引用全局变量(可改)和本地静态变量(可改).
        gNumber+=10;
        hasThumbnail = YES;
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        
        // 2. 引用全局函数.
        PRINT(@"PRINT 全局函数");
        
        // 3. 所处的封闭范围的本地变量和函数参数.(只读)
        //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
        // 不允许写
        // y+=100;
        PRINT(@"y is %d",y);
        
        // 4.__block声明的本地变量,会传递引用,可改.
        x+=number;
        x+=y;
        PRINT(@"x is %d",x);
        
        NSLog(@"=========  blockWork LEAVE ========= ");
    };
    blockWork();
    
    NSLog(@"调用 blockWork 之后");
    blockTemp();

    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}

void testCppObject()
{
    // 阶段1: const 的外部对象.
    // 1. 未声明__block的C++对象,必须有一个const copy拷贝构造函数.
    // 2. 在块定义时,就会调用const copy拷贝构造函数.
    A a1;
    
    NSLog(@"================ funcBlockCpp declare ================");
    dispatch_block_t funcBlockCpp = ^(){
        
        // const 类型不允许赋值.
        // a1.i = 78;
        NSLog(@"================ funcBlockCpp BEGIN ================");
        NSLog(@"a1.i : %d",a1.i);
        NSLog(@"================ funcBlockCpp END ================");
    };

    funcBlockCpp();
    
    // 阶段2; __block 的引用对象.
    // 1. 声明_block可以必须有一个 copy构造函数,并且copy比const copy优先.
    // 2. _block声明的cpp对象,不会调用拷贝构造函数,除非对block进行复制才会调用一次拷贝构造函数.块在拷贝时,会对引用的对象进行调用拷贝构造函数.
    __block A a2;
    NSLog(@"================ funcBlockCpp2 declare ================");
    dispatch_block_t funcBlockCpp2 = ^(){
        NSLog(@"================ funcBlockCpp2 BEGIN ================");
        NSLog(@"a2.i : %d",a2.i);
        a2.i = 192;
        NSLog(@"================ funcBlockCpp2 END ================");
    };
    
    funcBlockCpp2();
    
    // 阶段3: 复制块.
    NSLog(@"================ 复制funcBlockCpp2 ================");
    dispatch_block_t funcBlockCpp2Copy = Block_copy(funcBlockCpp2);
    
    funcBlockCpp2Copy();
    Block_release(funcBlockCpp2Copy);
    
}

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        // insert code here...
        NSLog(@"Hello, World!");
        testBlockTypeOfVariable(10);
        testObjectCObject();
        testCppObject();
        dispatch_async(dispatch_get_main_queue(),^(){
            shouldKeepRunning = NO;
        });
        
        NSRunLoop *theRL = [NSRunLoop currentRunLoop];
        while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    }
    return 0;
}


输出

2020-05-02 11:49:15.993 test-object-c-block[795:303] Hello, World!
2020-05-02 11:49:15.995 test-object-c-block[795:303] ====================== testBlockTypeOfVariable BEGIN ==========================
2020-05-02 11:49:15.996 test-object-c-block[795:303] 调用 blockWork 之前
2020-05-02 11:49:15.996 test-object-c-block[795:303] == 输出类型变量 ==
2020-05-02 11:49:15.997 test-object-c-block[795:303]  -> gNumber is 100
2020-05-02 11:49:15.997 test-object-c-block[795:303]  -> hasThumbnail is 0
2020-05-02 11:49:15.997 test-object-c-block[795:303]  -> y is 100
2020-05-02 11:49:15.998 test-object-c-block[795:303]  -> x is 10
2020-05-02 11:49:15.998 test-object-c-block[795:303] ========= blockWork ENTER =========
2020-05-02 11:49:15.999 test-object-c-block[795:303]  -> gNumber is 110
2020-05-02 11:49:15.999 test-object-c-block[795:303]  -> hasThumbnail is 1
2020-05-02 11:49:15.999 test-object-c-block[795:303]  -> PRINT 全局函数
2020-05-02 11:49:16.000 test-object-c-block[795:303]  -> y is 100
2020-05-02 11:49:16.000 test-object-c-block[795:303]  -> x is 120
2020-05-02 11:49:16.001 test-object-c-block[795:303] =========  blockWork LEAVE ========= 
2020-05-02 11:49:16.001 test-object-c-block[795:303] 调用 blockWork 之后
2020-05-02 11:49:16.002 test-object-c-block[795:303] == 输出类型变量 ==
2020-05-02 11:49:16.002 test-object-c-block[795:303]  -> gNumber is 110
2020-05-02 11:49:16.002 test-object-c-block[795:303]  -> hasThumbnail is 1
2020-05-02 11:49:16.003 test-object-c-block[795:303]  -> y is 100
2020-05-02 11:49:16.003 test-object-c-block[795:303]  -> x is 120
2020-05-02 11:49:16.004 test-object-c-block[795:303] ====================== testBlockTypeOfVariable END ==========================
2020-05-02 11:49:16.004 test-object-c-block[795:303] ====================== testObjectCObject BEGIN ==========================
2020-05-02 11:49:16.004 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.005 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.005 test-object-c-block[795:303]  -> str retainCount 1
2020-05-02 11:49:16.006 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.006 test-object-c-block[795:303]  -> Copy block
2020-05-02 11:49:16.007 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.007 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.008 test-object-c-block[795:303]  -> str retainCount 2
2020-05-02 11:49:16.008 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.009 test-object-c-block[795:303]  -> Release block
2020-05-02 11:49:16.009 test-object-c-block[795:303]  -> str retainCount 1
2020-05-02 11:49:16.010 test-object-c-block[795:303]  -> 赋值块给Object-C的成员属性(copy).
2020-05-02 11:49:16.011 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.012 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.012 test-object-c-block[795:303]  -> str retainCount 3
2020-05-02 11:49:16.013 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.014 test-object-c-block[795:303]  -> 释放块属性.
2020-05-02 11:49:16.015 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.016 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.016 test-object-c-block[795:303]  -> str retainCount 1
2020-05-02 11:49:16.017 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.018 test-object-c-block[795:303] ====================== testObjectCObject END ==========================
2020-05-02 11:49:16.018 test-object-c-block[795:303] A() -> i: 88
2020-05-02 11:49:16.019 test-object-c-block[795:303] ================ funcBlockCpp declare ================
2020-05-02 11:49:16.020 test-object-c-block[795:303] A(const A& a) -> i: 89
2020-05-02 11:49:16.020 test-object-c-block[795:303] ================ funcBlockCpp BEGIN ================
2020-05-02 11:49:16.021 test-object-c-block[795:303] a1.i : 89
2020-05-02 11:49:16.022 test-object-c-block[795:303] ================ funcBlockCpp END ================
2020-05-02 11:49:16.022 test-object-c-block[795:303] A() -> i: 88
2020-05-02 11:49:16.023 test-object-c-block[795:303] ================ funcBlockCpp2 declare ================
2020-05-02 11:49:16.024 test-object-c-block[795:303] ================ funcBlockCpp2 BEGIN ================
2020-05-02 11:49:16.024 test-object-c-block[795:303] a2.i : 88
2020-05-02 11:49:16.025 test-object-c-block[795:303] ================ funcBlockCpp2 END ================
2020-05-02 11:49:16.025 test-object-c-block[795:303] ================ 复制funcBlockCpp2 ================
2020-05-02 11:49:16.026 test-object-c-block[795:303] A(A& a) -> i: 292
2020-05-02 11:49:16.027 test-object-c-block[795:303] ================ funcBlockCpp2 BEGIN ================
2020-05-02 11:49:16.028 test-object-c-block[795:303] a2.i : 292
2020-05-02 11:49:16.028 test-object-c-block[795:303] ================ funcBlockCpp2 END ================
2020-05-02 11:49:16.029 test-object-c-block[795:303] ~A() -> i: 192
2020-05-02 11:49:16.030 test-object-c-block[795:303] ~A() -> i: 192
2020-05-02 11:49:16.032 test-object-c-block[795:303] ~A() -> i: 89
2020-05-02 11:49:16.033 test-object-c-block[795:303] ~A() -> i: 88
2020-05-02 11:49:16.034 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.035 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.035 test-object-c-block[795:303]  -> str retainCount 2
2020-05-02 11:49:16.036 test-object-c-block[795:303] =========  blockObject LEAVE ========= 

参考

Objective-C ARC: strong vs retain and weak vs assign

Blocks Programming Topics

Programming with Objective-C

is-swift-faster-than-objective-c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值