Apple’s Objective-C compiler allows users to freely mix C++ and Objective-C in the same source file, and the resulting language is called Objective-C++. Compared to other languages (e.g. Swift, Kotlin, Dart, etc.) that use file isolation and bridge communication with C++ (e.g. Kotlin uses JNI and Dart uses FFI), Objective-C and C++’s same-file mashup is certainly comfortable. Although OC/C++ mashups can be written in a single file, there are some caveats to understand: Objective-C++ does not add C++ functionality to OC classes, nor does it add OC functionality to C++, e.g., you cannot call C++ objects with OC syntax, nor can you add constructors and destructors to OC objects. Nor can this and self be used interchangeably. The class architecture is independent, C++ classes cannot inherit OC classes, and OC classes cannot inherit C++ classes.
This article explores the previously confusing issue of OC’s Block and C++’s lambda mix.
Experimental environment: C++ version is C++14, OC is limited to ARC only.
Basic Understanding
Before exploring in depth, understand the two by way of comparison.
Syntax
|
|
Principle
Here is not to do a deeper exploration of the bottom of the Block of OC, just to expand the code to achieve a comparative effect.
By rewriting clang -rewrite-objc, you can get the following result.
|
|
C++ lambda takes a very different implementation mechanism and converts the lambda expression into an anonymous C++ class. Here is a look at the C++ lambda implementation with the help of cppinsights.
|
|
You can see that: the lambda expression add is converted to class __lambda_12_15 and the operator () is overloaded, and calls to add are converted to calls to add.operator().
Capturing variables
OC Block is only possible to capture variables in the normal way and in the __block way.
C++ lambda brings more flexibility to capture variables in these ways.
|
|
Memory management
OC Block and C++ lambda both have their roots in stack objects, but their subsequent development is very different. OC Block are essentially OC objects, they are stored by reference, never by value. OC Blocks must be copied to the heap in order to extend their lifecycle. OC Blocks follow the OC reference counting rules, and copy and release must be balanced (same for Block_copy and Block_release). The first copy will move Block from the stack to the heap, and another copy will increase its reference count. When the reference count reaches 0, Block is destroyed and the object it captures is released.
C++ lambda stores by value, not by reference. All captured variables are stored in the anonymous class object as member variables of the anonymous class object. When lambda expressions are copied, all of these variables are copied as well, simply by triggering the appropriate constructors and destructors. There is an extremely important point here: variables are captured by reference. These variables are stored as references in anonymous objects and they don’t get any special treatment. This means that after the lifetime of these variables is over, lambda may still access them, resulting in undefined behavior or crashes, e.g.
|
|
In contrast, this points to a store on the heap, which has a guaranteed lifetime, but even so, it is not absolutely guaranteed to be life-safe, and in some cases it is necessary to extend the lifetime with the help of smart pointers.
Closures mixed capture problem
The previous discussion is all independent of each other, OC’s Block does not involve C++ objects, and C++’s lambda does not involve OC objects, which is probably what we would like to see, but the mixup process will reveal that this is just wishful thinking on our part. The two tend to extend their magic wands into each other’s domain, which can lead to some rather puzzling problems.
C++’s lambda captures OC objects
Can C++’s lambda capture OC variables? If so, is there a circular reference problem? If there is a circular reference problem, how should I handle it?
value capture OC object
As the code shows, there is a C++ field cppObj in the OCClass class, and in the initialization method of OCClass, cppObj is initialized and its field callback is assigned a value. You can see that self is captured in lambda, which can be considered value capture according to the previous rules.
|
|
|
|
Unfortunately, such a capture occurs by circular reference: the OCClass object ocObj holds cppObj, and cppObj holds ocObj via callback.

Looking at the corresponding assembly code, you can see that the capture triggers the ARC semantics and automatically retain on self.

These lines of assembly code add a reference count to self.
Finally, looking at the parameters of the anonymous class, you can see that self is of type OCClass *, which is a pointer type.

Then it can be simply assumed that the capture pseudocode is as follows, and that the retain behavior occurs under ARC semantics.
To solve the problem of circular references, __weak can be used.

Looking at the assembly code again, I see that the previous objc_retain logic has disappeared and is replaced by objc_copyWeak.
Capture OC objects by reference
So is it possible to capture self by reference capture?
You can see that there is also no objc_retain logic in the assembly code.

Finally, looking at the parameters of the anonymous class, we can see that self is of type OCClass *&, which is a pointer reference type.

You can see that reference capture does not retain self, and you can simply assume that the capture pseudocode is as follows, and no retainment behavior occurs under ARC semantics.
When is the captured OC object released?
Take this code snippet as an example.

You can see that std::function is destructed in the destructor of CppClass, and std::function releases the OC variable oc2 that it captures.
Conclusion
The essence of C++ lambda is to create an anonymous struct type to store captured variables. ARC will ensure that C++ struct types containing OC object fields follow ARC semantics:
- the constructor of the C++ structure initializes the OC object field to
nil; - when the OC object field is assigned a value, it
releasesthe previous value andretainthe new value (orcopyif it is ablock). - when the destructor of a C++ struct is called, it
releasethe OC object field.
C++ lambda captures OC objects by value or by reference.
- capturing OC objects by reference is equivalent to using
__unsafe_unretained, which has lifecycle issues and is inherently dangerous and not recommended. - value capture is equivalent to using
__strong, which may cause circular references, so you can use__weakif necessary.
How does OC’s Block capture C++ objects?
Take a look at how OC’s Block captures C++ objects.
The HMRequestMonitor in the code is a C++ structure with WaitForDone and SignalDone methods that are mainly for synchronization.
The upload method uses the HMRequestMonitor object for the purpose of waiting for network request results synchronously (the code has been adjusted for typography).
|
|
Here, std::weak_ptr is used directly.
does not use __block

The following conclusions can be drawn from experiments.
-
C++ objects are captured by OC’s
Blockand by value passing. A breakpoint shows that the copy constructor ofstd::weak_ptris called. -
The weak reference count of
monitorchanges as follows.- Initialize
monitorwithweak_count = 1; - When initializing
weakMonitor,weak_count = 2, which increases by 1. - After OC Block capture,
weak_count = 4, increased by 2. By looking at the assembly code, there are 2 places.weakMinotorwas copied on the first capture, at line 142 of the assembly code.weakMinotoris copied again whenBlockis copied from the stack to the heap, in assembly line 144.
- Initialize
Here we need to pay attention to: C++
weak_countis strange, its value = number of weak references + 1, the reason for this design is more complicated, please refer to: https://stackoverflow.com/questions/5671241/how-does-weak-ptr-work
If instead of using std::weak_ptr, std::shared_ptr is caught and its strong reference count is 3, the logic is the same as for std::weak_ptr above. (Essentially, std::shared_ptr and std::weak_ptr are both C++ classes)
using __block
So is it possible to use __block to modify a captured C++ variable? Experimentation has shown that it is possible.

The following conclusions can be drawn.
- the
Blockof OC can capture C++ objects by reference passing. - the
weakreference count ofmonitoris as follows.- Initialize
monitorwithweak_count = 1; - Initialize
weakMonitorwithweak_count = 2, increasing by 1. - After OC
Blockcapture,weak_count = 2, mainly because the move constructor is triggered, which is only a transfer of ownership and does not change the reference count.
- Initialize

Questions about __block
Those who know C++ may wonder, since the move constructor is triggered here, only the ownership has been transferred, meaning that monitor is passed in as the right value and has become nullptr to be extinguished, then why is monitor still accessible in the example? It can be verified that.
-
When the following code is executed for the first time

will find the address of the monitor variable as
-
When the assignment of
blockis executed, the move constructor ofstd::shared_ptris called.

- The address of
thisin the move constructor is0x0000600003b0c830; - The address of
__ris also0x0000700001d959e8, which is the same as the address ofmonitor.
- The address of
-
When the execution of
blockis finished, print the address ofmonitoragain, you will find that the address ofmonitorhas changed and is consistent withthisin step 2, which means thatmonitorhas changed tothisin step 2.
During the whole process, the address of monitor changes before and after 2 different std::shared_ptr objects. So monitor can still be accessed.
When is a captured C++ object released?

Also when OC’s Block is released, it is released for the C++ object it captures.
captures shared_from_this
C++’s this is a pointer, which is essentially an integer. OC’s Block capturing this is not fundamentally different from capturing an integer, so we won’t discuss it in detail here. The focus here is on C++’s shared_from_this class, which is a smart pointer version of this.
If a C++ class wants to access
shared_from_this, it must inherit from classenable_shared_from_thisand pass its own class name as a template parameter.
|
|
According to the previous conclusion, in the CppClass member function attachOCBlock, ocBlock captures shared_from_this directly, which also triggers a circular reference, and also takes std::weak_ptr to resolve it.
Conclusion
OC’s Block can capture C++ objects.
- if a C++ object on the stack is captured in the normal way, the copy constructor is called.
- If a C++ object on the stack is captured using the
__blockmethod, the move constructor is called, and the__block-modified C++ object is redirected when it is captured.
Summary
This article started with a brief comparison between OC’s Block and C++’s lambda in 4 dimensions: syntax, principles, variable capture and memory management, and then focused more on OC/C++’s closure hybrid capture. The reason why I went to such great lengths is that I don’t want to “guess” and “try and fail” in a confusing way, but only by understanding the mechanism behind it can I write better OC/C++ mixed code, and I also hope to bring some help to readers who have the same confusion. However, this is only the tip of the iceberg for the whole field of OC/C++ mashups, and there are still a lot of difficult issues to be explored in the future.