Block Is Used for Communication between OC and C++

Background

In iOS development, we often encounter the situation of mixing OC and C++, so how to communicate between then? This article will introduce how to use block for communication between oc and c++. Due to the different memory management style of oc and c++, you may encounter problems when using block. This article will answer them one by one.

Objective-C uses OC as its abbreviation in this article.

2. Knowledge points that must be mastered before reading

2.1 OC memory management

OC uses counting references to manage memory and is RAC. Simply put, when pointing to an instance, the reference count is +1, when it is released, it is -1, and when it is 0, it is released.

2.2 Block

  • Essence: The essence of block in oc is an object
  • Storage: global area, heap area
  • Creation: Usually the created block is stored in the global area, and variables on the stack or heap are captured to the heap area.

2.3 C++ memory management

  • Manual release, pointer release uses release (pointer), object release uses delete object

2.4 Stack memory release

  • The data in the stack space does not need to be released
  • The memory in the heap space needs to be released. oc is automatically released by its RAC mechanism, and c++ is released manually by the developer.

3. Problem Description

3.1 Problem description

The c++ object points to a block. The block captures the oc variable and copies it to the heap area. However, the block in the heap area will be automatically released when the function body ends. At a certain point, the c++ object calls the block and will crash because the block Already released.

3.2 Code

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
@interface TestA ()
@property (nonatomic, copy) TestBlockCallback callback;
@end

@implementation TestA

- (void)dealloc{
NSLog(@"%s", __func__);
release();
}

- (instancetype)init{
if (self = [super init]) {
test_listener *listener = new test_listener();
listener->bind(^{

});
// listener will bind to global instance, it will not release before it release by devoloper
bindListener(listener);
}
return self;
}

- (void)test{

}

+ (void)runCpp {
runApp();
}

4.How to solve

4.1 Steps

(1) Create an oc object A, which holds a block.
(2) Pass the block of object A to the c++ object. There is an attribute in the c++ object pointing to the block.
(3) The C++ object calls block and triggers the callback function in object A.

4.2 Code implementation

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
OC file
#import "TestA.h"
#include "test_c.hpp"

#define WeakSelf(objc) __weak __typeof(objc)weakSelf = objc;

#include <iostream>
using namespace::std;

@interface TestA ()
@property (nonatomic, copy) TestBlockCallback callback;
@end

@implementation TestA

- (void)dealloc{
NSLog(@"%s", __func__);
release();
}

- (instancetype)init{
if (self = [super init]) {
WeakSelf(self);
self.callback = ^{
printf("call back\n");
[weakSelf test];
};
test_listener *listener = new test_listener();
listener->bind(self.callback);
bindListener(listener);
}
return self;
}

- (void)test{

}

+ (void)runCpp {
runApp();
}

@end

c++ file
test_c header file
#ifndef test_c_hpp
#define test_c_hpp

#include <stdio.h>

typedef void(^TestBlockCallback)();

class test_listener {

public:
TestBlockCallback callback;

public:
test_listener();
~test_listener();

void run();
void bind(TestBlockCallback callback);
};

void bindListener(test_listener *listener);
void runApp();
void release();

#endif /* test_c_hpp */

test_c specific implementation file
#include "test_c.hpp"

test_listener::test_listener() {}

test_listener::~test_listener() {
callback = nullptr;
}

void test_listener::bind(TestBlockCallback callback) {
this->callback = callback;
}

void test_listener::run(){
this->callback();
}

test_listener *_listener;

void bindListener(test_listener *listener){
_listener = listener;
}

void runApp() {
_listener->run();
}

void release() {
if (_listener == nullptr) {
return;
}
delete _listener;
}

5. Summary

5.1 Principle analysis

The block is created in the global area and will not be released, but once the instance is captured, it will be copied to the heap.
However, the memory in the heap space will be automatically released when the count reference is 0. After the block is initialized, the count reference is 0 and the function body is released, so calling block in C++ will crash.

5.2 Solution description

Therefore, we need to extend the life cycle of the block and hold the block through the object. Under the premise that the object is not released, the block value exists, and there will be no problem of crash caused by c++ calls.

5.3 Graphic description