场景
-
在开发
Object-C
程序时, 很多情况下会用到它的块block
特性, 这个block
其实就是lambda
表达式. 这个block
和lambda
有什么区别, 还有什么需要注意的编程点? -
我们在使用
dispatch_async
函数进行GCD
异步编程时, 在block
里引用的外部范围的object
变量是否需要retain
? 如果不retain
的话,autoreleasepool
在block
所属的范围结束后调用对象的release
方法那不是会销毁,而block
之后再调用从而导致野指针?
说明
-
Object-C
在现在逐步被Swift
代替开发新的模块.Apple
的战略估计是类似Android
系统里Koltin
代替Java
. 但是在它们之间也可以互相调用. 所以仍然使用Object-C
开发iOS
和macOS
程序也未尝不可.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
, 大部分逻辑还可以跨平台使用. -
我们通常会使用
dispatch_async
把数据发到界面线程处理,比如弹出一个警告对话框,像我之前说的在Win32
界面开发里的做法原理是一样的.DispatchAsync使用lambda表达式来简化发送数据到界面线程.
dispatch_async(dispatch_get_main_queue(),^(){
// 弹出警告对话框.
});
- 在
Object-C
开发中, 我都会在xcode
里关闭arc
, 自己控制Object-C
的引用计数,也就是所谓的mrc
. 这样能更好的精确控制对象的生命周期,减少内存使用,提高性能,即使使用自动计数也会有内存泄漏的情况. 前面的两个问题都可以归结到block
对外部变量的引用规则。以下我们就来说说,并且是在没有自动引用计数
的前提下,也能方便打印出实际的引用计数个数,还有就是arc
下void*
和Object-c
对象不能直接转换,还有一些限制, 还是比较麻烦的. 目前来说,搞了C++
还是比较喜欢手动控制内存对象的生命周期,不太喜欢编译器做太多事情.
块 block
- 块在
OC
里作为一个lambda
表达式, 用的频率还是很高的, 比如dispatch_async
调用, 调用一些比较排序函数. 在块里的引用外部变量,需要知道它们的规则,才能避免出错. 以下例子我调整项目的为mrc
,以便知道引用计数做了什么变化。
图1:
- 以下是引用普通类型变量的规则:
- 引用全局变量(可改)和本地静态变量(可改).
- 引用全局函数.
- 所处的封闭范围的本地变量和函数参数.(只读)
– 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block. - __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__);
}
- 以下是引用
Object-C
对象的规则:- 复制块时,块会对块里所引用的外部
object-c
对象创建一个strong
引用, 即它的引用计数+1. - 块释放时,也会对应的对所引用的
object-c
的引用计数-1. - 所以在块里的外部
object-c
对象,不需要担心它的声明周期,即使是异步执行的块. 比如dispatch_async
函数. - 赋值块给Obj类的func属性, 注意需要设置为
copy
(arc
是strong
) 属性块才会在堆里创建而不至于在函数执行完之后失效. - 注意,在给块属性设置
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__);
}
- 以下是引用
cpp
对象的规则:- 未声明__block的C++对象,必须有一个const copy拷贝构造函数.在块定义时,就会调用const copy拷贝构造函数.
- 声明_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 =========