Summary of basic principles of iOS

This paper mainly introduces block types, circular reference solutions and block bottom analysis

Block type

There are three main types of blocks

  • __NSGlobalBlock__: global block, stored in a global area

  • __NSMallocBlock__: heap block, because block is both a function and an object

  • __NSStackBlock__Block: the stack area

Strong holding can be disabled by __weak, and a block is still a stack block

conclusion

  • Blocks are stored directly in the global area

  • If a block accesses an external variable and copies it, that is, copy

    • If at this pointBlocks are strong referencesThe block,Stored in the heap area, that is, the heap block
    • If this is bLock becomes a weak reference by __weakThe block,Stored in the stack area, that is, the stack block

Block loop reference

  • Normal releaseWhen A calls the dealloc method and sends A release signal to B, B receives the release signal. If B’s retainCount (reference count) is 0, B’s dealloc method will be called
  • A circular reference: A and B hold each other, so A cannot call dealloc method to send release signal to B, and B cannot receive release signal either. So neither A nor B can be released

As shown in the figure below

Resolving circular references

Do the following two pieces of code have circular references?

NSString *name = @"CJL"; self.block = ^(void){ NSLog(@"%@",self.name); }; self.block(); UIView animateWithDuration:1 animations:^{NSLog(@"%@",self.name); };Copy the code

Loop references occur in code one, because the external variable name is used inside the block, causing the block to hold self, which originally held the block, and thus self and the block to hold each other. There is no circular reference in code 2. Although external variables are also used, self does not hold the animation block. Only the animation holds self, which does not constitute mutual holding

There are several common ways to resolve circular references.

  • [Method 1]weak-strong-dance
  • 【 Method 2 】__blockModify objects (note that this is required inside blocksemptyObject, andBlock must call)
  • 【 Mode 3 】 PassRecursion object selfAs a block,parameterFor internal use within blocks
  • [Mode 4] UseNSProxy

Weak-stong-dance

  • If there is no nested block inside the block, use it directly__weakYou can just modify self
typedef void(^CJLBlock)(void);

@property(nonatomic, copy) CJLBlock cjlBlock;

__weak typeof(self) weakSelf = self;
self.cjlBlock = ^(void){
     NSLog(@"%@",weakSelf.name);
}
Copy the code

At this point, weakSelf and self point to the same piece of memory space, and the use of __weak will not lead to the change of self’s reference count, which can be verified by printing the pointer addresses of weakSelf and self and self’s reference count, as shown below

  • If a block contains nested blocks, use both__weak 和 __strong
__weak typeof(self) weakSelf = self;
self.cjlBlock = ^(void){
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.name);
    });
};
self.cjlBlock();
Copy the code

StrongSelf is a temporary variable in the scope of cjlBlock, which releases strongSelf after the internal block executes

This is breaking self’s strong reference to a block, depending on the mediator pattern, and is auto-set to nil, auto-release

Method 2: __block decorates a variable

This approach, which also relies on the mediator pattern, is manual release and modifies objects with __block, mainly because __block modifies objects that can be changed

__block ViewController *vc = self; self.cjlBlock = ^(void){ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",vc.name); vc = nil; // manually release}); }; self.cjlBlock();Copy the code

Notice that the block here has to be called, if it’s not called, vc is not null, it’s still a circular reference, self and block are not released

Method 3: Object self as argument

The object self is supplied as an argument to the block for internal use, with no reference counting problems

typedef void(^CJLBlock)(ViewController *);

@property(nonatomic, copy) CJLBlock cjlBlock;

self.cjlBlock = ^(ViewController *vc){
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",vc.name);
    });
};
self.cjlBlock(self);
Copy the code

The fourth way to use circular references is to use the NSProxy virtual class

NSProxy virtual class

  • OCIs the onlySingle inheritanceLanguage, but it isRuntime-based mechanismsSo we can passNSProxyTo implement thePseudo multiple inheritanceTo fill the gap of multiple inheritance
  • NSProxy 和 NSObjectIt’s a class of the same class, or oneVirtual class, just implements the protocol of NSObject
  • NSProxyIt’s actually aMessage redirection encapsulates an abstract class, like a proxy, middleware can implement message forwarding to another instance by inheriting it and overriding the following two methods
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
Copy the code

Usage scenarios

There are two scenarios for using NSProxy

  • implementationMultiple inheritancefunction
  • To solve theNSTimer&CADisplayLinkWhen creatingStrong reference to selfQuestion, referenceYYKittheYYWeakProxy.

Circular reference solution principle

This is mainly done by replacing self with a custom NSProxy class object and using methods to implement message forwarding

The following is an implementation of the NSProxy subclass and the scenario in which it is used

  • Custom oneNSProxyA subclass ofCJLProxy
@interface CJLProxy : NSProxy - (id)transformObjc:(NSObject *)objc; + (instancetype)proxyWithObjc:(id)objc; @end @interface CJLProxy () @property(nonatomic, weak, readonly) NSObject *objc; @end @implementation CJLProxy - (id)transformObjc:(NSObject *)objc{ _objc = objc; return self; } + (instancetype)proxyWithObjc:(id)objc{ return [[self alloc] transformObjc:objc]; } //2. - (void)forwardInvocation:(NSInvocation *)invocation{SEL SEL = [invocation selector]; if ([self.objc respondsToSelector:sel]) { [invocation invokeWithTarget:self.objc]; }} / / 1, query the signature of the method - (NSMethodSignature *) methodSignatureForSelector: (SEL) SEL {NSMethodSignature * signature; if (self.objc) { signature = [self.objc methodSignatureForSelector:sel]; }else{ signature = [super methodSignatureForSelector:sel]; } return signature; } - (BOOL)respondsToSelector:(SEL)aSelector{ return [self.objc respondsToSelector:aSelector]; } @endCopy the code
  • The customCatClasses andDogclass
/ / * * * * * * * * the Cat class * * * * * * * * @ interface Cat: NSObject @ end @ implementation Cat - (void) eat {NSLog (@ "cats eat fish"); } @ the end / / Dog class * * * * * * * * * * * * * * * * @ interface Dog: NSObject @ end @ implementation Dog - (void) shut {NSLog (@ "Dog"); } @endCopy the code
  • Implemented by CJLProxyMultiple inheritancefunction
- (void)cjl_proxyTest{
    Dog *dog = [[Dog alloc] init];
    Cat *cat = [[Cat alloc] init];
    CJLProxy *proxy = [CJLProxy alloc];
    
    [proxy transformObjc:cat];
    [proxy performSelector:@selector(eat)];
    
    [proxy transformObjc:dog];
    [proxy performSelector:@selector(shut)];
}
Copy the code
  • Solve the problem through CJLProxyA strong reference to self in the timerThe problem
self.timer = [NSTimer timerWithTimeInterval:1 target:[CJLProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code

conclusion

There are basically two solutions to looping applications, self -> block -> self for example

  • Break a strong reference to a block by self. You can use weak in the block attribute modifier, but this will cause the block to be freed as soon as it is created, so breaking a strong reference from there will not work

  • Break block’s strong reference to self, which is mainly the communication between self scope and block scope. Communication has several ways, such as proxy, value passing, notification, parameter passing, etc., used to solve the loop. Common solutions are as follows:

    • weak-strong-dance
    • __block(Object inside block is empty and block is called)
    • The objectselfAs a block,parameter
    • throughNSProxySubclass ofself

Block underlying analysis

Mainly through clang, breakpoint debugging and other ways to analyze the Block bottom

nature

  • defineblock.cfile
#include "stdio.h"

int main(){

    void(^block)(void) = ^{
        printf("CJL");
    };
    return 0;
}
Copy the code
  • throughxcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c, compile block.c toblock.cpp, where the block is compiled at the bottom to the following form
int main(){ void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); return 0; } static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("CJL"); } / / * * * * * * simplify void * * * * * * block (*) (void) = __main_block_impl_0 (__main_block_func_0, & __main_block_desc_0_DATA)); // Constructor block->FuncPtr(block); // The block call executesCopy the code

Block = __main_block_IMPL_0, which is a function

  • To view__main_block_impl_0, it is aThe structure of the bodyBlock is a block__main_block_impl_0Type of object, that’s why, rightblockTo be able to% @Reasons for printing
** struct __main_block_impl_0 {struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; Struct __block_impl {void *isa; int Flags; int Reserved; void *FuncPtr; };Copy the code

Summary: The essence of a block is an object, a function, or a structure. Since a block function has no name, it is also called an anonymous function

The relationship between block source code compiled by Clang is shown below, using a variable decorated with __block as an example

1. Why do blocks need to be called

In the underlying block’s __main_block_IMPL_0 structure, created by its constructor of the same name, the first passed block’s internal implementation code block, __main_block_func_0, is expressed as FP, and then assigned to the FuncPtr property of the IMPL, The call is then made in main, which is why the block needs to be called. If not called, the code block implemented inside the block will not execute, which can be summarized as follows

  • Function declarationThe internal implementation of a block is declared as a function__main_block_func_0
  • Perform specific function implementations: by calling a blockFuncPtrPointer to call block execution

2. How does a block get external variables

  • Define a variable and call it in a block
int main(){
    int a = 11;
    void(^block)(void) = ^{
        printf("CJL - %d", a);
    };
    
     block();
    return 0;
}
Copy the code
  • The underlying compilation looks like this
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; Void *fp, struct __main_block_desc_0 *desc, int _A, int flags=0); a(_a) { impl.isa = &_NSConcreteStackBlock; // Block isa defaults to stackBlock imp. Flags = Flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; Printf ("CJL - %d", a); } int main(){ int a = 11; void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)); block)->FuncPtr(block); return 0; }Copy the code

The ain __main_block_func_0 is a copy of the value, and if you do a++ inside a block implementation, it is problematic and will cause confusion in the compiler code that a is read-only

Summary: When a block captures an external variable, it automatically generates the same internal property to hold

The principle of __block

  • rightaAdd a__block, and then on a in the block++operation
int main(){

    __block int a = 11;
    void(^block)(void) = ^{
        a++;
        printf("CJL - %d", a);
    };
    
     block();
    return 0;
}
Copy the code