I. Introduction to Block
Block objects are c-level syntax and runtime functionality. They are similar to standard C functions, but in addition to executable code, they may also contain variable bindings to automatic (stack) or managed (heap) memory. Thus, a block can maintain a set of states (data) that can be used to influence behavior when executed.
You can use blocks to form function expressions that can be passed to the API, optionally stored and used by multiple threads. A block is particularly useful as a callback because it contains both the code to execute on the callback and the data required during execution.
OS X V10.6 developer tools ship with blocks in GCC and Clang. You can use blocks in OS X V10.6 and later as well as iOS 4.0 and later. The block runtime is open source and can be found in LLVM’s compile-RT subproject repository. The block has also been submitted to the C Standard Working Group as N1370: Apple’s extension to C. Since both Objective-C and C++ are derived from C, blocks are designed to work with all three languages (and Objective-C++).
A Block is essentially an OC object that encapsulates a function call and its calling environment. It consists of an ISA pointer, an IMPL function pointer, and a descriptor. It’s kind of like the function pointer in C.
Block usage
Block basic expression:
/** * return_type specifies the returned object/keyword, etc. (void can be omitted) * blockName specifies the name of the block * var_type specifies the type of the argument (void can be, * varName indicates the parameter name */
return_type (^blockName)(var_type) = ^return_type (var_type varName) { ... };
blockName(var);
Copy the code
1. The Block syntax
1.1 When the return type isvoid
void (^blockName)(var_type) = ^void (var_type varName) { ... };
blockName(var);
// can be omitted into
void (^blockName)(var_type) = ^(var_type varName) { ... };
blockName(var);
Copy the code
1.2 When the parameter type isvoid
return_type (^blockName)(void) = ^return_type (void) {... }; blockName();// can be omitted into
return_type (^blockName)(void) = ^return_type { ... };
blockName();
Copy the code
1.3 When both the return type and parameter type arevoid
void (^blockName)(void) = ^void (void) {... }; blockName();// can be omitted into
void (^blockName)(void) = ^{ ... };
blockName();
Copy the code
1.4 the anonymous Block
When a Block is implemented, the right-hand side of the equals sign is an anonymous Block, which has no blockName and is called an anonymous Block.
^return_type (var_type varName) { ... };
Copy the code
1.5 typedef
Simplify Block declarations
typedef return_type (^BlockTypeName)(var_type);
Copy the code
example
/ / declare
typedef void(^ClickBlock)(NSInteger index);
/ / 1
@property (nonatomic.copy) ClickBlock clickBlock;
/ / 2
(void)methodHandle:(ClickBlock)handle { ... }
Copy the code
2. Application scenarios
2.1 Response Events
2.2 Data Transfer
2.3 Chain syntax
The idea is to take a Block as the return value of the method, of type the caller itself, and to take the method assetter
Form return, so that you can achieve a continuous call, that is, chain programming.
Declare a method add in the CaculateMaker. H file
// CaculateMaker.h
@interface CaculateMaker : NSObject
@property (nonatomic.assign) CGFloat result;
- (CaculateMaker *(^)(CGFloat num))add;
@end
Copy the code
Implement the add method in CaculateMaker. M file
#import "CaculateMaker.h"
@implementation CaculateMaker
- (CaculateMaker *(^)(CGFloat num))add {
return ^CaculateMaker *(CGFloat num) {
_result += num;
return self;
};
}
@end
Copy the code
Use the class
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
Copy the code
Iii. Block in-depth exploration
1. The Block
In main.m we write the code:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int age = 20;
void (^block)(void) = ^ {NSLog(@"age is %d", age);
};
block();
}
return 0;
}
Copy the code
Compile the main.m file separately and run the following command from the terminal:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
Copy the code
Get main. CPP file, we extract useful information from the file, tidy up the above code
int main(int argc, char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
int age = 20;
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age));
block->FuncPtr(block);
}
return 0;
}
Copy the code
The contents of __main_block_IMPL_0 and __main_block_func_0 are
struct __block_impl { // __block_impl impl
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __main_block_desc_0 { // __main_block_desc_0* Desc
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0.sizeof(struct __main_block_impl_0)};
struct __main_block_impl_0 {
struct __block_impl impl; //
struct __main_block_desc_0* Desc; //
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { // constructorimpl.isa = &_NSConcreteStackBlock; . }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3j_fvy599js5nnglvw7jslq09t80000gn_T_main_cef707_mi_0, age);
}
Copy the code
From the above we can see that the internal Block can access the external variables, which leads to the Block variable capture mechanism.
2. Variable capture of Block
2.1 Local variable auto
The local variable auto, the local variable that we normally write, has auto by default. Such as:
int age = 20; // equivalent to auto age = 20;
Copy the code
2.2 Local Variable static
Static modified local variables are not destroyed. As you can see from the following code, modifying static variables outside the Block can still affect the values inside the Block:
static int height = 30;
int age = 20;
void (^block)(void) = ^ {NSLog(@"age is %d height = %d",age,height);
};
age = 25;
height = 35;
block();
// Execution result
age is 20 height = 35
Copy the code
Why has this changed? Because age is passed directly, height is passed *height, which means that the memory address is passed directly. You can follow the above instructions to try to compile into CPP files, to learn more. Disadvantages are permanent storage, large memory overhead.
2.3 Global Variables
Global variables cannot and do not need to be captured inside a Block because they are stored in the global static area and are accessed directly. The disadvantage is also large memory overhead.
3. The Block type
Block type | The environment |
---|---|
__NSGlobalBlock__ |
The auto variable is not accessed |
__NSStackBlock__ |
The auto variable is accessed |
__NSMallocBlock__ |
__NSStackBlock__ Call the copy |
Each type of block calls copy as shown below
Block of the class | The configuration storage domain of the copy source | Print effect |
---|---|---|
_NSConcreteGlobalBlock |
The data area of the program | Do nothing |
_NSConcreteStackBlock |
The stack | Copy from stack to heap |
_NSConcreteMallocBlock |
The heap | Reference count increment |
When ARC is enabled, there are only blocks of type _NSConcreteGlobalBlock and _NSConcreteMallocBlock, because in ARC mode, The compiler automatically copies blocks on the stack to the heap as appropriate (blocks automatically invoke the copy method). The situation is as follows:
- Block as a function return value;
- Assign a block to
__strong
When the pointer; - Block is the Cocoa API name
usingBlock
Method parameters; - Block as a method parameter of the GCD API;
Therefore, the proposed Block declaration under ARC is written:
@property (strong.nonatomic) void (^block)(void);
@property (copy.nonatomic) void (^block)(void);
Copy the code
3.1 Block types are ultimately inherited fromNSObject
Let’s verify this with a piece of code:
void (^block)(void) = ^{
NSLog(@"Hello Block");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
block();
Copy the code
The printed result is:
2021-04-10 01:24:01.844162+0800 studyOC[40328:4640930] Hello Block 2021-04-10 01:24:01.844502+0800 StudyOC [40328:4640930] __NSGlobalBlock__ 2021-04-10 01:24:01.844541+0800 studyOC[40328:4640930] NSBlock 2021-04-10 01:24:01. 844566 + 0800 studyOC NSObject [40328-4640930]Copy the code
Block types are inherited from NSBlock, and ultimately from NSObject. This validates __NSGlobalBlock__. Block validates all other types of Block. It is important to note that you should not use clang to convert OC code to C++ code to verify the above conclusion. In LLVM x.0, the compiled file is not a C++ file, but an intermediate file. The Runtime is involved, so the C++ code is a bit inaccurate.
3.2 Block internal access object typeauto
variable
If the Block is on the stack, there is no strong reference to the auto variable; But if a Block is copied onto the heap,
-
The copy method inside the Block is called;
-
The copy method calls the _Block_object_assign function internally;
-
The _Block_object_assign function performs operations based on the __strong, __weak, and __unsafe_unretained modifier of the auto variable, for example, adding one to the reference count to form a retained or weak reference.
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); Copy the code
If a Block is removed from the heap,
-
Dispose function inside the Block is called.
-
The _Block_object_dispose function is called internally.
-
The _Block_object_dispose function automatically releases the referenced auto variable (release)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); Copy the code
4. __block
The modifier
__block wraps the modified variable into an object at compile time. To verify this, we convert the following code into C++ code:
__block int age = 18;
void (^block)(void) = ^ {NSLog(@"Hello Block %d", age);
};
block();
Copy the code
It can be observed that age of type int is converted into an object by __block:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; // A pointer to oneself
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref. }Copy the code
__block can be used to solve the problem that the Block cannot modify the value of the auto variable; But __block cannot modify global or static variables.
4.1 __block
Memory management
-
When a Block is on the stack, there is no strong reference to the __block variable;
-
When a Block is copied to the heap, the copy method inside the Block is called; The copy method calls the _Block_object_assign function internally; The _Block_object_assign function forms a strong reference to the __block variable.
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); Copy the code
-
When a Block is removed from the heap, the Dispose method inside the Block is called. The _Block_object_dispose function is called internally. The _Block_object_dispose function automatically releases the referenced __block variable (release);
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); Copy the code
4.2 __block
的 __forwarding
Pointer to the
5. Block circular reference
Circular references occur frequently when using blocks, so how does this circular reference occur and how to solve it? Let’s analyze.
5.1 Causes of Circular Reference
The problem with circular references is that two objects hold each other so that they cannot be freed.
Similarly, A circular reference occurs when A Block holds an object (strongly referenced) and an object holds A Block (strongly referenced). As A result, when object A is about to be released from the heap, object A still holds object B, and object B also holds object A, and it is stuck there, and no one can be released. As shown in figure:
5.2 How to Solve the problem of Circular Reference
So how do we solve this problem?
A. by__weak
,__unsafe_unretained
Makes a Block a weak reference to an object
- use
__weak
Resolving circular references__weak
The modified object will behave automatically when it is releasednil
;__weak
Modified object registered toautoreleasepool
;__weak
Can only be used in ARC mode and can only modify objects, not primitive data types.
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"Hello Block, %p", weakSelf);
};
self.block();
Copy the code
__weak has a certain impact on performance. When an object has a large number of __weak references, when the object is discarded, all objects whose __weak references have been assigned nil, consuming CPU resources. That being said, use it when you need it. For more information on the underlying principle of __weak, see this article: Objective-C weak keyword implementation source code parsing.
- use
__unsafe_unretained
Resolving circular references
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog(@"Hello Block, %p", weakSelf);
};
self.block();
Copy the code
__weak only supports iOS 5.0+ and OS X Mountain Lion+ as deployed versions. __unsafe_unretained is more compatible. __unsafe_unretained and __weak both prevent holding, but __weak is an extremely safe behavior for a pointer object to be returned to nil when the object it points to is freed. __unsafe_unretained points to the same memory as the object, even after it has been destroyed. This can lead to a crash caused by accessing the freed object, but __unsafe_unretained doesn’t have as much of a performance impact.
B. use__block
Resolve (Block must be called)
__block id weakSelf = self;
self.block = ^{
NSLog(@"Hello Block, %p", weakSelf);
weakSelf = nil;
};
self.block();
Copy the code
Through the use of__block
, the relationship between Block and object becomes
When we’re done, manually set the weakly referenced object tonil
So their relationship becomes
Blocks and objects don’t hold each other.
Reference documentation
- Official Apple documentation for Blocks Programming Topics
- IOS Block In-depth Understanding (1
- Block usage in iOS, Examples, parsing and underlying principles (this is probably the most detailed Block parsing)
- How to modify block External variables inside a Block
- Objective-c weak keyword implementation source parsing