精华内容
下载资源
问答
  • BLOCK
    万次阅读
    2022-04-01 15:14:54

    一、前言

    • Block 和 Delegate 是对象间传递消息的常用机制,这两个机制可以说是各有千秋。 Delegate 可以很方便把目标动作的执行过程划分为多个方法,以展现不同时间节点下特定的操作;Block 则擅长处理一个回调多个落点的情况,并且它可以通过捕捉上下文信息,来达到减少创建额外变量,集中消息处理逻辑的目的。
    • 结合以上两种通信方式的特点,我们可以添加一些额外的桥接处理,让 Delegate 机制也能享有 Block 机制所拥有的部分优点,桥接处理的核心就是用 Block 实现委托方法。
    • 由于 Runtime 的存在,在消息转发的最后一步,可以轻松地拦截对未定义方法的调用,并且针对当前消息做一些额外的处理,比如改变它的入参、设置另一个消息接受者等。借助于这一特性,我们可以创建一个统一的 Delegate 对象,并在这个对象的 -forwardInvocation: 方法中,用预先设置的 Block 替换对委托方法的调用,以达到用 Block 实现委托方法的目的。

    二、NSInvocation 基本使用

    • 苹果官方对 NSInvocation 的用途给出的解释如下:
    NSInvocation objects are used to store and forward messages between objects and between applications
    
    • 一个 NSInvocation 对象包含了 Objective-C 消息的所有要素:消息接收对象、 方法选择器 (SEL) 、参数以及返回值,并且这些要素都可以由开发者直接设置。如下所示,简单使用 NSInvocation:
    NSString *foo = @"foo";
    NSMethodSignature *signature = [foo methodSignatureForSelector:@selector(stringByAppendingString:)];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.selector = @selector(stringByAppendingString:);
    
    NSString *bar = @"bar";
    [invocation setArgument:&bar atIndex:2];
    [invocation invokeWithTarget:foo];
    
    void *result = nil;
    [invocation getReturnValue:&result];
    
    NSString *resultString = (__bridge NSString *)(result);
    NSLog(@"%@", resultString);
    
    • 上述代码执行结果:
    foobar
    
    • 可以看到,这个结果和执行 [foo stringByAppendingString:bar] 的结果是一致的。
    • 关于 NSInvocation 的使用,需要注意以下两点:
      • 一般方法的自定义参数从索引 2 开始,前两个分别是对象自身以及发送方法的 SEL;
      • 从 -getArgument:atIndex: 和 -getReturnValue: 方法中获取的对象是不会被 retain 的,所以如果使用 ARC ,那么以下代码都是错误的:
    NSString *bar = nil;
    [invocation getArgument:&bar atIndex:2];
    
    NSString *result = nil;
    [invocation getReturnValue:&result];
    
      • ARC 编译环境下局部对象默认具有 __strong 属性,它会针对这个对象添加 release 代码,所以可能会因为 release 已经释放的对象而崩溃。正确代码如下:
    void *bar = nil;
    // __unsafe_unretained NSString *bar = nil;
    // __weak NSString *bar = nil;
    [invocation getArgument:&bar atIndex:2];
    
    void *result = nil;
    // __unsafe_unretained NSString *result = nil;
    // __weak NSString *result = nil;
    [invocation getReturnValue:&result];
    
      • 如果是在两个 NSInvocation 对象间传递参数/返回值,那么可以直接传入指针获取并设置目标地址,以返回值为例:
    ....
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    NSInvocation *shadowInvocation = [NSInvocation invocationWithMethodSignature:signature];
    ....
    void *resultBuffer = malloc(invocation.methodSignature.methodReturnLength);
    memset(resultBuffer, 0, invocation.methodSignature.methodReturnLength);
    
    [invocation getReturnValue:resultBuffer];
    [shadowInvocation setReturnValue:resultBuffer];
    ....
    free(resultBuffer);
    
    • 这时,如果返回值是一个 NSString 对象,那么 resultBuffer 实际上是指向 NSString 对象指针的指针,这时可以这样读取实际内容:
    NSString *result = (__bridge NSString *)(*(void **)resultBuffer);
    
    • 不过在已经知道返回值是一个对象时,一般会直接传入对象指针的地址,以便直接读取对象。

    三、获取方法签名

    ① 从对象中获取方法签名

    • NSMethodSignature 是创建一个有效 NSInvocation 对象的必要成分,它提供了方法调用所必须的参数和返回值信息。
    • NSObject 类用以下两个方法获取实例方法的方法签名:
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
    + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
    
    • 既然类也是对象,那么类方法的方法签名也就可以通过 -methodSignatureForSelector: 方法获取。

    ② 从协议中获取方法签名

    • 由于协议定义了接口的参数和返回值信息,所以从协议中也可以获取到特定方法的方法签名。利用 protocol_getMethodDescription 函数,可以获取到描述类型的 C 字符串,再通过这个字符串构造方法签名。
    • 针对协议中的接口有 required 和 optional 两种,并且不允许重复这一特点,可以创建构造方法签名的函数:
    static NSMethodSignature *ydw_getProtocolMethodSignature(Protocol *protocol, SEL selector, BOOL isInstanceMethod) {
        struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, YES, isInstanceMethod);
        if (!methodDescription.name) {
            methodDescription = protocol_getMethodDescription(protocol, selector, NO, isInstanceMethod);
        }
        return [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
    }
    

    ③ 从 Block 中获取方法签名

    • 苹果并没有提供一个开放的接口,供开发者获取 Block 的方法签名。不过根据 LLVM 对 Block 结构描述,可以通过操作指针获取签名字符串。
    • Block 的结构如下:
    // Block internals.
    typedef NS_OPTIONS(int, YDWBlockFlags) {
        YDWBlockFlagsHasCopyDisposeHelpers = (1 << 25),
        YDWBlockFlagsHasSignature          = (1 << 30)
    };
    typedef struct ydw_block {
        __unused Class isa;
        YDWBlockFlags flags;
        __unused int reserved;
        void (__unused *invoke)(struct ydw_block *block, ...);
        struct {
            unsigned long int reserved;
            unsigned long int size;
            // requires YDWBlockFlagsHasCopyDisposeHelpers
            void (*copy)(void *dst, const void *src);
            void (*dispose)(const void *);
            // requires YDWBlockFlagsHasSignature
            const char *signature;
            const char *layout;
        } *descriptor;
        // imported variables
    } *YDWBlockRef;
    
    • 可以看到,只要获取 descriptor 指针,然后根据不同条件添加特定的偏移量,就可以获取到 signature:
    static NSMethodSignature *ydw_signatureForBlock(id block) {
        YDWBlockRef layout = (__bridge YDWBlockRef)(block);
        
        // 没有签名,直接返回空
        if (!(layout->flags & YDWBlockFlagsHasSignature)) {
            return nil;
        }
        
        // 获取 descriptor 指针
        void *desc = layout->descriptor;
        
        // 跳过 reserved 和 size 成员
        desc += 2 * sizeof(unsigned long int);
        
        // 如果有 Helpers 函数, 跳过 copy 和 dispose 成员
        if (layout->flags & YDWBlockFlagsHasCopyDisposeHelpers) {
            desc += 2 * sizeof(void *);
        }
        
        // desc 为 signature 指针的地址,转换下给 objcTypes
        char *objcTypes = (*(char **)desc);
        
        return [NSMethodSignature signatureWithObjCTypes:objcTypes];
    }
    

    四、方法调用 -> Block 调用

    • 经过上文,已经可以获取到 Block 和接口方法的签名信息,现在根据这个签名信息,结合方法对应的 NSInvocation 对象,创建和 Block 关联的 NSInvocation 对象。

    ① 存储 Block 信息

    • 首先要做的是,存储 Block 的签名信息,并且和接口方法的签名信息做匹配处理。因为在调用前,需要将接口方法得到的参数转换成 Block 的入参,调用之后需要将 Block 的返回值重新传给接口方法,所以必须确保两者的签名信息在一定程度上是兼容的。
    - (instancetype)initWithMethodSignature:(NSMethodSignature *)methodSignature block:(id)block {
        return [self initWithMethodSignature:methodSignature blockSignature:ydw_signatureForBlock(block) block:block];
    }
    
    - (instancetype)initWithMethodSignature:(NSMethodSignature *)methodSignature blockSignature:(NSMethodSignature *)blockSignature block:(id)block {
        NSAssert(ydw_isCompatibleBlockSignature(blockSignature, methodSignature), @"Block signature %@ is not compatible with method signature %@", blockSignature, methodSignature);
        
        if (self = [super init]) {
            _methodSignature = methodSignature;
            _blockSignature = blockSignature;
            _block = block;
        }
        return self;
    }
    

    ② 签名匹配

    • Block 的签名信息相较于方法的签名信息,只在参数类型上少了 SEL。
    • 方法的签名信息如果要获取自定义参数类型的话,需要从索引 2 开始,而 Block 的自定义参数类型信息则从索引 1 开始。
    static BOOL ydw_isCompatibleBlockSignature(NSMethodSignature *blockSignature, NSMethodSignature *methodSignature) {
        NSCParameterAssert(blockSignature);
        NSCParameterAssert(methodSignature);
        
        if ([blockSignature isEqual:methodSignature]) {
            return YES;
        }
        
        // block 参数个数需要小于 method 的参数个数 (针对 block 调用替换 method 调用)
        // 两者返回类型需要一致
        if (blockSignature.numberOfArguments >= methodSignature.numberOfArguments ||
            blockSignature.methodReturnType[0] != methodSignature.methodReturnType[0]) {
            return NO;
        }
        
        // 参数类型需要一致
        BOOL compatibleSignature = YES;
        
        // 自定义参数从第二个开始
        for (int idx = 2; idx < blockSignature.numberOfArguments; idx++) {
    
            // block 相比 method ,默认参数少了 SEL
            // method: id(@) SEL(:) ....
            // block: block(@?) ....
            const char *methodArgument = [methodSignature getArgumentTypeAtIndex:idx];
            const char *blockArgument = [blockSignature getArgumentTypeAtIndex:idx - 1];
            if (!methodArgument || !blockArgument || methodArgument[0] != blockArgument[0]) {
                compatibleSignature = NO;
                break;
            }
        }   
        return compatibleSignature;
    }
    

    ③ Invocation 调用

    • 得到有效的 Block 签名信息,就可以构造 NSInvocation 对象,不过还需要接口方法的实参信息,这可以通过让外部传入接口方法的 NSInvocation 对象实现。
    - (void)invokeWithMethodInvocation:(NSInvocation *)methodInvocation {
        NSParameterAssert(methodInvocation);
        NSAssert([self.methodSignature isEqual:methodInvocation.methodSignature], @"Method invocation's signature is not compatible with block signature");
        
        NSMethodSignature *methodSignature = methodInvocation.methodSignature;
        NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
        
        void *argumentBuffer = NULL;
        for (int idx = 2; idx < methodSignature.numberOfArguments; idx++) {
            
            // 获取参数类型
            const char *type = [methodSignature getArgumentTypeAtIndex:idx];
            NSUInteger size = 0;
            
            // 获取参数大小
            NSGetSizeAndAlignment(type, &size, NULL);
            
            // 参数缓存
            if (!(argumentBuffer = reallocf(argumentBuffer, size))) {
                return;
            }
            
            // 把 method 的参数传递给 block
            [methodInvocation getArgument:argumentBuffer atIndex:idx];
            [blockInvocation setArgument:argumentBuffer atIndex:idx - 1];
        }
        
        // 调用 block
        [blockInvocation invokeWithTarget:self.block];
        
        // 返回值缓存
        if (methodSignature.methodReturnLength &&
            (argumentBuffer = reallocf(argumentBuffer, methodSignature.methodReturnLength))) {
            
            // 把 block 的返回值传递给 method
            [blockInvocation getReturnValue:argumentBuffer];
            [methodInvocation setReturnValue:argumentBuffer];
        }
        
        // 释放缓存
        free(argumentBuffer);
    }
    
    • reallocf 函数是 realloc 函数的增强版,它可以在后者无法申请到堆空间时,释放旧的堆空间:
    void *reallocf(void *p, size_t s) {
        void *tmp = realloc(p, s);
        if(tmp) return tmp;
        free(p);
        return NULL;
    }
    
    • 这样就可以直接用 argumentBuffer = reallocf(argumentBuffer, size) 形式的语句,否则如果使用 realloc,一旦返回的是 NULL,会造成旧的堆空间无法释放的问题。

    五、实现委托方法

    • 现在已经可以构造 Block 的 NSInvocation 对像,就缺携带参数和返回值信息的接口方法 NSInvocation 对象,接下来就针对实例方法,简单地实现动态委托类。

    ① 储存 Block Invocation 信息

    • 以接口方法选择器对应的字符串为 Key,以 Block 对应的 Invocation 封装类为 Value 储存调用信息:
    - (instancetype)initWithProtocol:(Protocol *)protocol {
        _protocol = protocol;
        _selectorInvocationMap = [NSMutableDictionary dictionary];
        return self;
    }
    
    - (void)implementInstanceMethodOfSelector:(SEL)selector withBlock:(id)block {
        NSMethodSignature *methodSignature = ydw_getProtocolMethodSignature(self.protocol, selector, YES);
        YDWBlockInvocation *invocation = [[YDWBlockInvocation alloc] initWithMethodSignature:methodSignature block:block];
        self.selectorInvocationMap[NSStringFromSelector(selector)] = invocation;
    }
    

    ② 消息转发

    • 向动态委托类发送委托消息后,会触发消息转发机制。在消息转发的最后一步,可以构造委托方法对应的 NSInvocation 对象:
    - (void)forwardInvocation:(NSInvocation *)invocation {
        YDWBlockInvocation *blockInvocation = self.selectorInvocationMap[NSStringFromSelector(invocation.selector)];
        [blockInvocation invokeWithMethodInvocation:invocation];
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
        return self.selectorInvocationMap[NSStringFromSelector(sel)].methodSignature;
    }
    
    - (BOOL)respondsToSelector:(SEL)aSelector {
        return !!self.selectorInvocationMap[NSStringFromSelector(aSelector)];
    }
    

    六、实例

    • 如何使用这个动态委托类:
    @class Computer;
    @protocol ComputerDelegate <NSObject>
    @required
    - (void)computerWillStart:(Computer *)computer;
    - (BOOL)computerShouldBeLocked:(Computer *)computer;
    @end
    
    @interface Computer : NSObject
    @property (weak, nonatomic) id <ComputerDelegate> delegate;
    
    - (void)start;
    - (void)lock;
    
    @end
    
    @implementation Computer
    - (void)start {
        [self.delegate computerWillStart:self];
        // start
    }
    
    - (void)lock {
        __unused BOOL locked = [self.delegate computerShouldBeLocked:self];
        printf("computer should be locked: %d \n", locked);
        // lock
    }
    @end
    
    • 应用如下:
    YDWDynamicDelegate <ComputerDelegate> *dynamicDelegate = (id)[[YDWDynamicDelegate alloc] initWithProtocol:@protocol(ComputerDelegate)];
    [dynamicDelegate implementInstanceMethodOfSelector:@selector(computerWillStart:) withBlock:^(Computer *c) {
        NSLog(@"%@ will start", c);
    }];
    [dynamicDelegate implementInstanceMethodOfSelector:@selector(computerShouldBeLocked:) withBlock:^BOOL(Computer *c) {
        NSLog(@"%@ should not be locked", c);
        return NO;
    }];
    
    Computer *computer = [Computer new];
    computer.delegate = dynamicDelegate;
    [computer start];
    [computer lock];
    
    • 执行结果:
    will start
    should not be locked
    computer should be locked: 0
    

    七、总结

    • 用 Block 实现委托方法的开源方案在比较早的时候就已经出来了,本文的实现就是 BlocksKit 的 A2BlockInvocation 和 A2DynamicDelegate 类的简易版本,不过省略了类方法以及一些边界条件的处理,不过大体的思路基本是一致的,都是围绕 NSInvocation 和消息转发。
    更多相关内容
  • Block底层详解

    千次阅读 2022-03-03 21:10:11
    block详解1.前言2.block的类型2.1.总共三种类型,具体如下:2.2 __ NSGlobalBlock__2、block数据结构3、block的变量捕获(capture)2.1auto变量的捕获2.2对象类型的auto变量4、block的Copy5、__weak问题解决6、__...

    1.前言

    本文旨在对block底层进行详细的探究,源码参考libclosure-79。

    2.Block的类型

    2.1.类型归纳

    block类型环境
    __ NSGlobalBlock__ (_NSConcreteGlobalBlock)没有访问auto变量
    __ NSStackBlock__ (_NSConcreteStackBlock )访问了auto变量
    __ NSMallocBlock__ ( _NSConcreteMallocBlock)__NSStackBlock__调用了copy

    三种Block在内存的中位置如下:
    在这里插入图片描述

    2.2. __ NSGlobalBlock__ 类型

    2.2.1.论证没有访问auto变量的Block是 __ NSGlobalBlock__类型

    dispatch_block_t globalBlock = ^{
    	NSLog(@"this is global block");
    };
    NSLog(@"%@", [globalBlock class]);
    globalBlock();
    

    Console输出如下:
    2022-03-04 17:40:34.791202+0800 Test[36063:420956] __ NSGlobalBlock__
    2022-03-04 17:40:34.792149+0800 Test[36063:420956] this is global block

    2.2.2.底层探究

    对globalBlock所在的文件执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m [-o xxx.cpp ],上面的代码会编译成下面的代码

    //基本数据结构
    struct __block_impl {
      void *isa; //说明block是oc对象
      int Flags;
      int Reserved;
      void *FuncPtr; //函数地址
    };
    
    //block的描述
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size; //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;
      //构造函数
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock; //clang生成的是中间文件,以运行时为准
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    //具体执行的函数
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
       NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_e49a1b_mi_0);
    }
    
    /*执行代码 start*/
    //block初始化
    dispatch_block_t globalBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 
    
    //获取globalBlock的class
    NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_e49a1b_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)globalBlock, sel_registerName("class")));
    
    //调用__main_block_func_0函数, 并block作为参数传递
    ((void (*)(__block_impl *))((__block_impl *)globalBlock)->FuncPtr)((__block_impl *)globalBlock);
    /*执行代码 end*/
    

    2.3 __ NSStackBlock__ 类型

    2.3.1.论证访问auto变量的Block是 __ NSStackBlock__类型

    //开启MRC, ARC下被强引用的block, 会自动拷贝到堆上,成为__ NSMallocBlock__类型
    int  age = 20;
    dispatch_block_t stackBlock = ^{
    	NSLog(@"this is stack block, age:%d", );
    };
    NSLog(@"%@", [stackBlock class]);
    stackBlock();
    

    Console输出如下:
    2022-03-04 17:40:34.791202+0800 Test[36063:420956] __ NSStackBlock__
    2022-03-04 17:40:34.792149+0800 Test[36063:420956] this is stack block, age:20

    2.3.2.底层探究

    对stackBlock所在的文件执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m [-o xxx.cpp ],上面的代码会编译成下面的代码

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int age = __cself->age; // bound by copy
      NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_9f69a9_mi_0, age);
    }
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age;  //capture 了age auto变量
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    /*执行代码 start*/
     int age = 20;
     //block初始化, age以值的形式传递
     dispatch_block_t stackBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
    
    //获取stackBlock的class
    NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_9f69a9_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)stackBlock, sel_registerName("class")));
    
    //调用__main_block_func_0函数, 并block作为参数传递
    ((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock);
    /*执行代码 end*/
    

    哪为啥访问了auto变量就成了__ NSStackBlock__ 类型?原因是auto变量是存在栈当中,函数执行完就会销毁,如果block还是__ NSGobalBlock__,auto变量销毁了再执行block,就会出现野指针。

    2.4__ NSMallocBlock__ 类型

    2.3.1.论证__NSStackBlock__调用了copy就变成__ NSMallocBlock__ 类型

    //开启MRC, ARC下被强引用的__NSStackBlock__类型block, 会自动拷贝到堆上,成为__ NSMallocBlock__类型
    int  age = 20;
    dispatch_block_t mallocBlock = [^{
    	NSLog(@"this is malloc block, age:%d", );
    } copy];
    NSLog(@"%@", [mallocBlock class]);
    mallocBlock();
    

    Console输出如下:
    2022-03-07 16:45:51.057649+0800 Test[78667:918108] NSMallocBlock
    2022-03-07 16:45:51.058730+0800 Test[78667:918108] this is malloc block, age:20

    2.3.2 底层探究

    基本和__ NSStackBlock__ 一致,就不做研究了。

    3.Block底层数据结构

    3.1 整体如下图

    请添加图片描述

    3.2 源码探究和分析

    Block的定义在Block_private.h中

    // Values for Block_layout->flags to describe block objects
    enum {
        BLOCK_DEALLOCATING =      (0x0001),  // runtime
        BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
        BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
    
    #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
        BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
    #endif
    
        BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
        BLOCK_NEEDS_FREE =        (1 << 24), // runtime
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler, 是否有copy和dispose方法
        BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
        BLOCK_IS_GC =             (1 << 27), // runtime
        BLOCK_IS_GLOBAL =         (1 << 28), // compiler,是否全局Block
        BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
        BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler,是否有签名
        BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
    };
    
    #define BLOCK_DESCRIPTOR_1 1
    struct Block_descriptor_1 {
        uintptr_t reserved;
        uintptr_t size;
    };
    
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
        // requires BLOCK_HAS_COPY_DISPOSE
    	void(*copy)(void *, const void *);
    	void(*dispose)(const void *);
    };
    
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature; 
        const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    
    struct Block_layout {
        void * isa; //删除了__ptrauth_objc_isa_pointer, 表示Block的类型
        volatile int32_t flags; // 第一个枚举中定义的值
        int32_t reserved; //保留位
        void (*invoke)(void *, ...); //函数指针,指向block具体的执行函数
        /*
        descriptor 描述Block size、copy、dispose、signature、layout等信息,但copy、dispose只有引用了对象才会出现,所以Block_layout只出现了Block_descriptor_1
    	*/
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    

    在ARC下 Block中访问了__block修饰的变量还有以下数据结构

    // Values for Block_byref->flags to describe __block variables
    enum {
        // Byref refcount must use the same bits as Block_layout's refcount.
        // BLOCK_DEALLOCATING =      (0x0001),  // runtime
        // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    
        BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
        BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
        BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
        BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
        BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
        BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler
    
        BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime
    
        BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
        BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
    };
    
    struct Block_byref {
        void * isa;
        struct Block_byref *forwarding; //保证栈和堆的变量访问的数据都是正确的,具体可以见下面的forwarding指针
        volatile int32_t flags; // contains ref count
        uint32_t size;
    };
    
    struct Block_byref_2 {
        // requires BLOCK_BYREF_HAS_COPY_DISPOSE
        void(*copy)(struct Block_byref*, struct Block_byref*);
        void(*dispose)(struct Block_byref *);
    };
    
    struct Block_byref_3 {
        // requires BLOCK_BYREF_LAYOUT_EXTENDED
        const char *layout;
    };
    

    举例说明

    	__block int age = 20;
        dispatch_block_t block = ^{
            NSLog(@"age:%d", age);
        };
        block();
    

    clang之后代码如下:

    //__Block_byref 类型,对age变量进行包装
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_age_0 *age = __cself->age; // bound by ref
     NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_350481_mi_0, (age->__forwarding->age));
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_age_0 *age; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    /*执行代码 start*/
     __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
    
    dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
        
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    /*执行代码 end*/
    

    4.Block的变量捕获和访问方式

    在这里插入图片描述

    4.1 局部变量的捕获

    值传递

    4.1.1.auto对象局部变量

    代码块

    @interface Person : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation Person
    @end
    
    int main(int argc, char * argv[]) {
        Person *person = [[Person alloc] init];
        person.name = @"abc";
        void (^block)() = ^{
            NSLog(@"name:%@", person.name);
        };
      
        block();
    }
    

    clang之后

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Person *person; //auto对象局部变量是强引用
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      Person *person = __cself->person; // bound by copy
      NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_f5ba61_mii_1, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));
    }
    
    //通过_Block_object_assign做内存管理
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    //通过_Block_object_dispose做内存管理
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      //处理内存管理
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      //处理内存管理
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    
    int main(int argc, char * argv[]) {
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_f5ba61_mii_0);
       
        void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }

    捕获auto对象局部变量,是需要处理内存管理, 主要由_Block_object_assign和_Block_object_dispose来完成,下面对这两个函数的源码

    1. _Block_object_assign源码
    // Values for _Block_object_assign() and _Block_object_dispose() parameters
    enum {
        // see function implementation for a more complete description of these fields and combinations
        BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
        BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
        BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
        BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
        BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
    };
    
    enum {
        BLOCK_ALL_COPY_DISPOSE_FLAGS = 
            BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
            BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
    };
    
    void _Block_object_assign(void *destArg, const void *object, const int flags) {
        const void **dest = (const void **)destArg;
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_OBJECT: //捕获的是对象
            /*******
            id object = ...;
            [^{ object; } copy];
            ********/
    
            _Block_retain_object(object);
            *dest = object;
            break;
    
          case BLOCK_FIELD_IS_BLOCK: //捕获的是Block
            /*******
            void (^object)(void) = ...;
            [^{ object; } copy];
            ********/
    
            *dest = _Block_copy(object);
            break;
        
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: 
          case BLOCK_FIELD_IS_BYREF:
            /*******
             // copy the onstack __block container to the heap
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __block ... x;
             __weak __block ... x;
             [^{ x; } copy];
             ********/
    
            *dest = _Block_byref_copy(object);
            break;
            
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
            /*******
             // copy the actual field held in the __block container
             // Note this is MRC unretained __block only. 
             // ARC retained __block is handled by the copy helper directly.
             __block id object;
             __block void (^object)(void);
             [^{ object; } copy];
             ********/
    
            *dest = object;
            break;
    
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            /*******
             // copy the actual field held in the __block container
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __weak __block id object;
             __weak __block void (^object)(void);
             [^{ object; } copy];
             ********/
    
            *dest = object;
            break;
    
          default:
            break;
        }
    }
    
    1. _Block_object_dispose源码
    void _Block_object_dispose(const void *object, const int flags) {
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            // get rid of the __block data structure held in a Block
            _Block_byref_release(object);
            break;
          case BLOCK_FIELD_IS_BLOCK:
            _Block_release(object);
            break;
          case BLOCK_FIELD_IS_OBJECT:
            _Block_release_object(object);
            break;
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            break;
          default:
            break;
        }
    }
    

    4.1.3.static局部变量

    指针传递

        static int age = 20;
        dispatch_block_t block = ^{
            NSLog(@"age:%d", age);
        };
        block();
    

    clang 之后

    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) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *age = __cself->age; // bound by copy
      NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_938f88_mi_0, (*age));
        }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    /*执行代码 start*/
     static int age = 20;
     
     dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age)); //指针传递
         
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    /*执行代码 end*/
    

    看clang之后的代码,确实把static局部变量捕获到Block内部,且是指针访问。那是为啥呢?

    1. 把static局部变量捕获到Block内部,原因是Block 可能在其他的场景执行
    2. 指针访问,原因是首先static的作用域是定义它的函数或语句块结束时,其作用域随之结束,所以Block也是它的作用域,Block是可以访问和修改的,另外static变量是存在数据区,函数执行完不会销毁,不用担心野指针

    4.2全局变量

    直接访问

    int age = 10;
    int main(int argc, char * argv[]) {
        dispatch_block_t block = ^{
            age++;
        };
    }
    

    clang之后

    int age = 10;
    
    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;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    	age++;
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, char * argv[]) {
        dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    }
    

    可以看出__main_block_impl_0 没有增加 age变量,中间没有age的传递,在__main_block_func_0中是直接访问的

    5.Block的copy

    对三种Block类型执行copy后的结果
    在这里插入图片描述
    注意事项:
    在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:

    1. block作为函数返回值时行
    2. 将block赋值给__strong指针时
    3. block作为Cocoa API中方法名含有usingBlock的方法参数时
    4. block作为GCD API的方法参数时

    6.__block修饰符

    1. __block可以用于解决block内部无法修改auto变量值的问题
    2. __block不能修饰全局变量、静态变量(static)
    3. 编译器会将__block变量包装成一个对象,Block捕获包装后的对象,包装后的对象不能修改,但里面的属性是可以的修改, 参考代码如下:
    __block int age = 20;
    dispatch_block_t block = ^{
    	NSLog(@"age:%d", age);
    };
    //age被包装成__Block_byref_age_0, 自己成为__Block_byref_age_0的属性
    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     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
      ...
    };
    
    

    6.2.内存管理

    1. 当block被copy到堆时
      a.会调用block内部的copy函数
      b.copy函数内部会调用_Block_object_assign函数
      c._Block_object_assign函数会对__block变量形成强引用(retain)

    在这里插入图片描述

    在这里插入图片描述

    6.3.__forwarding指针

    在这里插入图片描述

    在这里插入图片描述在这里插入图片描述__forwarding存在的意义:保证当栈上的__block变量中的__forwarding copy到堆之后,栈上的__block变量通过__forwarding还能正确访问数据。

    7.常见问题

    1. 循环引用
    self.block = ^{
    	NSLog(@"%@", self);
    };
    

    在这里插入图片描述
    解决方式

    //ARC
        __weak typeof(self) weakSelf = self; //__unsafe_unretained
        self.block = ^{
            NSLog(@"%@", weakSelf);
        };
    //MRC
       __block id weakSelf = self; //__unsafe_unretained
       self.block = ^{
           NSLog(@"%@", weakSelf);
       };
    

    在这里插入图片描述

    1. __weak clang 失败,换成 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 xxx.m [-o xxx.cpp ] 即可 //12.0.0 只是一个版本,可以换成其它的
    2. Thread 1: EXC_BAD_ACCESS (code=1, address=0x10) //64bits address=0x10, 32bits address=0x0c
    //出现的场景如下
    dispatch_block_t block = nil;
    block();
    
    //原因,看clang之后的数据结构,就一目了然
    struct __block_impl {
      void *isa; //64bits 8, 32bits 4
      int Flags;//64bits 4, 32bits 4
      int Reserved;//64bits 4, 32bits 4
      void *FuncPtr; //执行block调用的是这个函数地址
    };
    
    1. MRC下数组添加Block, 访问可能Crash
      请添加图片描述
      为什么第二个会Crash呢,原因是第一个Block是__ NSGlobalBlock__ 一直存在数据区域, 第二个是 __ NSStackBlock__ 在栈上,添加到数组不会拷贝到堆上,函数执行完就会销毁。

    8.总结

    1. block本质上也是一个OC对象,它内部也有个isa指针。
    2. block是封装了函数调用以及函数调用环境的OC对象。
    展开全文
  • iOS中Block的使用注意事项

    千次阅读 2022-03-10 19:43:29
    //定义一个Block typedef returnType(^BlockName)(parameterA,parameterB,...) e.g:typedef void(^RequestResult)(BOOL Result) //实例 ^{ NSLog(@"This is a Block"); } Block本质是一个OC对象,内部含有一个isa...

     

    //定义一个Block
    typedef returnType(^BlockName)(parameterA,parameterB,...)
    e.g:typedef void(^RequestResult)(BOOL Result)
    //实例
    ^{
       NSLog(@"This is a Block");
     }

    Block本质是一个OC对象,内部含有一个isa指针,它是一个封装了函数及函数调用环境的OC对象,可以添加到NSArray及NSDictionary等集合中,它是基于C语言及运行时特性,有点类似标准的C函数。但除了可执行代码以外,另外包含了变量同堆或栈的自动绑定。

    常用介绍

    Block的类型:

    1、NSGlobalBlock

    void(^exampleBlock)(void)=^{
      //block
    }
    NSLog(@"exampleBlock is:%@",[exampleBlock class])
    

    打印日志:exampleBlock is:__NSGlobalBlock

    如果一个block没有访问外部局部变量,或者访问的全局变量,或者静态局部变量,此时的block就是一个全局block,并且数据存储的全局区。

    2、NSStackBlock

    int temp = 100
    void(^exampleBlock)(void) = ^{
      //
      NSLog(@"exampleBlock is:%d, temp")
    }
    NSLog(@"exampleBlock is:%@,[exampleBlock class]")

    打印日志:exampleBlock is: __NSMallocBlock__???不是说好的 __NSStackBlock__的吗?为什么打印的是__NSMallocBlock__ 呢?这里是因为我们使用了 ARC ,Xcode 默认帮我们做了很多事情。

    我们可以去 Build Settings 里面,找到 Objective-C Automatic Reference Counting,并将其设置为 No ,然后再 Run 一次代码。你会看到打印日志是:exampleBlock is: __NSStackBlock__

    如果 block 访问了外部局部变量,此时的 block 就是一个栈 block ,并且存储在栈区。由于栈区的释放是由系统控制,因此栈中的代码在作用域结束之后内存就会销毁,如果此时再调用 block 就会发生问题,( 注: 此代码运行在 MRC 下)如:

    void (^simpleBlock)(void);
    void callFunc() {
        int age = 10;
        simpleBlock = ^{
            NSLog(@"simpleBlock-----%d", age);
        };
    }
    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            callFunc();
            simpleBlock();
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return 0;
    }

    打印日志:simpleBlock--------41044160

    3、NSMallocBlock

    当一个__NSStackBlock__类型block做copy操作后,就会将这个block从栈上复制到堆上,而堆上的这个block类型就是__NSMallocBlock__类型。在ARC环境下,编译器会根据情况,自动将block从栈上copy到堆上。具体会进行copy的情况有如下4种:

    1、block作为函数的返回值时;

    2、block赋值给__strong指针,或者赋值给block类型的成员变量时;

    3、block作为cocoa API中方法名含有usingBlock的方法参数时;

    4、block作为GCD API的方法参数时;

    __Block的作用

    简单来说,__block作用是允许block内部访问和修改外部变量,在ARC环境下还可以用来防止循环引用。

    __block int num = 100;
    void(^exampleBlock)(void) = ^{
      NSLog(@"1、num is:%d",num);
      num = 120;
      NSLog(@"2、num is:%d",num);
    };
    exampleBlock();
    NSLog(@"3、num is:%d",num);

    __block主要用来解决block内部无法修改auto变量值的问题,为什么加上__block修饰之后,auto变量值就能修改呢?

    这是因为,加上__block修饰之后,编译器会将__bl;ock变量包装成一个结构体__Block_byref_num_0,结构体内部的*__forwarding是指向自身的指针,并且结构体内部还存储着外部auto变量。

    struct __Block_byref_val_0{
    void *__isa;//isa指针
    __Block_byref_val_0 *__forwarding;
    int __flags;
    int __size;//Block结构体大小
    int num;//捕获到的变量
    }

     从上图可以看到,如果 block 是在栈上,那么这个 __forwarding 指针就是指向它自己,当这个 block 从栈上复制到堆上后,栈上的 __forwarding 指针指向的是复制到堆上的 __block 结构体。堆上的 __block 结构体中的 __forwarding 指向的还是它自己,即 num->__forwarding 获取到堆上的 __block 结构体,num->__forwarding->num 会把堆上的 num 赋值为 120。因此不管是栈上还是堆上的 __block 结构体,最终使用到的都是堆上的 __block 结构体里面的数据。

    • __weak 的作用

    简单来说是为了防止循环引用。

    self 本身会对 block 进行强引用,block 也会对 self 形成强引用,这样就会造成循环引用的问题。我们可以通过使用 __weak 打破循环,使 block 对象对 self 弱引用。

    此时我们注意,由于 block 对 self 的引用为 weak 引用,因此有可能在执行 block时,self 对象本身已经释放,那么我们如何保证 self 对象不在 block 内部释放呢?这就引出了下面__strong 的作用。


    • __strong 的作用

    简单来说,是防止 block 内部引用的外部 weak 变量被提前释放,进而在 block 内部无法获取 weak 变量以继续使用的情况;

    __weak __typeof(self) weakSelf = self;
    void (^exampleBlock)(void) = ^{
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf exampleFunc];
    };
    

    这样就保证了在 block 作用域结束之前,block 内部都持有一个 strongSelf 对象可供使用。

    但是,即便如此,依然有一个场景,就是执行 __strong __typeof(weakSelf) strongSelf = weakSelf; 之前,weakSelf 对象已经释放,这时如果给 self 对象发送消息,这没有问题,Objective-C 的消息发送机制允许我们给一个 nil 对象发送消息,这不会出现问题。但如果有额外的一些操作,比如说将 self 添加到数组,这时因为 self 为 nil,程序就会 Crash。

    我们可以增加一层安全保护来解决这个问题,如:

    __weak __typeof(self) weakSelf = self;
    void (^exampleBlock)(void) = ^{
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            // Add operation here
        }
    };
    

    拓展知识

    • 思考题

    Block 内修改外部 NSMutableString 、NSMutableArray 、NSMutableDictionary 对象,是否需要添加 __block 修饰?

    NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
    [mutableArray addObject:@"1"];
    void (^exampleBlock)(void) = ^{
        // block
        [mutableArray addObject:@"2"];
    };
    exampleBlock();
    NSLog(@"mutableArray: %@", mutableArray);
    

    打印日志:

    mutableArray: ( 1, 2 )

    答案是:不需要,因为在 block 内部,我们只是使用了对象 mutableArray 的内存地址,往其中添加内容。并没有修改其内存地址,因此不需要使用 __block 也可以正确执行。当我们只是使用局部变量的内存地址,而不是对其内存地址进行修改时,我们无需对其添加 __block ,如果添加了 __block 系统会自动创建相应的结构体,这种情况冗余且低效。

    • Block 数据结构

    Block 内部数据结构图如下:

    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    
    struct Block_layout {
        void *isa;
        int flags;
        int reserved; 
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };
    

    Block_layout 结构体成员含义如下:

    isa: 指向所属类的指针,也就是 block 的类型

    flags: 按 bit 位表示一些 block 的附加信息,比如判断 block 类型、判断 block 引用计数、判断 block 是否需要执行辅助函数等;

    reserved: 保留变量;

    invoke: block 函数指针,指向具体的 block 实现的函数调用地址,block 内部的执行代码都在这个函数中;

    descriptor: 结构体 Block_descriptor,block 的附加描述信息,包含 copy/dispose 函数,block 的大小,保留变量;

    variables: 因为 block 有闭包性,所以可以访问 block 外部的局部变量。这些 variables 就是复制到结构体中的外部局部变量或变量的地址;

    Block_descriptor 结构体成员含义如下:

    reserved: 保留变量;

    size: block 的大小;

    copy: 函数用于捕获变量并持有引用;

    dispose: 析构函数,用来释放捕获的资源;


    总结

    使用 Block 过程中需要我们关注的重点有 4 个:

    1. block 的三种类型;

    2. block 避免引起循环引用;

    3. block 对 auto 变量的 copy 操作;

    4. __block、__weak、__strong 的作用;

    展开全文
  • 第一部分:Block本质 block 是Cocoa/Cocoa Touch Framework 中的匿名函式(Anonymous Functions)的实作。所谓的匿名函式,就是一段 具有物件性质的程式码,这一段程式码可以当做函式执行,另一方面,又可以当做...

    Block的简介

    Block 的官方定义是这样的:Block块是封装工作单元的对象,是可以在任何时间执行的代码段,其本质是可移植的匿名函数,可以作为方法和函数的参数传入,可以从方法和函数中返回。

    在iOS4以后,越来越多的系统级的API在使用Block。苹果对于Block的使用主要集中在如下几个方面:

    
     - 完成处理–Completion Handlers
     - 通知处理–Notification Handlers
     - 错误处理–Error Handlers
     - 枚举–Enumeration
     - 动画与形变–View Animation and Transitions
     - 分类–Sorting
     - 线程管理:GCD/NSOperation
    

    第二部分:操作Block外部的变量
    Q:访问Block之外的变量?

    int age=10;
    void (^Block)(void) = ^{
        NSLog(@"age:%d",age);
    };
    Block();
    age = 20;
    Block();
    

    输出值为 age:10
    输出值为 age:10
    原因:创建block的时候,已经把age的值存储在里面了。
    注意此时觉得不能对block里面age的值修改,会报错

    Q:下列代码输出值分别为多少?

    auto int age = 10;
    static int num = 25;
    void (^Block)(void) = ^{
        NSLog(@"age:%d,num:%d",age,num);
    };
    age = 20;
    num = 11;
    Block();
    

    输出结果为:age:10,num:11
    愿意:auto变量block访问方式是值传递,static变量block访问方式是指针传递
    源码证明

    int age = __cself->age; // bound by copy
    int *num = __cself->num; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*num));
    
    int age = 10;
    static int num = 25;
    
    block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &num));
    
    age = 20;
    num = 11;
    

    上述代码可查看 static修饰的变量,是根据指针访问的

    Q:为什么block对auto和static变量捕获有差异?

    auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;static变量一直保存在内存中,指针访问即可

    Q:修改block之外的变量

    在block中假如需要修改block之外定义的变量时,那么在定义变量时必须加上__block关键字这个比较常用

      self.shadowView.alpha = 0.0f;
       __block typeof(self) bself = self;
    [UIView animateWithDuration:0.2f animations:^{
        
            bself.shadowView.alpha = 1.0f;
        
        } completion:^(BOOL finished){
        
        }];
    

    或者

    改变 result的结果
    __block BOOL result = NO;
    [self.dataBaseQueue inDatabase:^(FMDatabase *db){
        
            result = [db executeUpdate:sql];
            
        }];
    

    Q:block在修改NSMutableArray,需不需要添加__block?
    不需要。

    NSMutableArray *array = [NSMutableArray array];
        void(^block)(void) = ^{
            [array addObject:@123];
        };
        Block();
    
    这里  对 array 只是一个使用,而不是赋值,所以不需要 _ _block 进行修饰
    
    
    错误的例子
      NSMutableArray *array = nil;
        void(^block)(void) = ^{
                array = [NSMutableArray array];
        };
        Block();
    
    这里就需要在array的声明处添加__block修饰符,不然编译器会报错
    
    

    总结下,对变量进行赋值的时候,下面这些不需要__block修饰符

    Q:block能否修改变量值?

    auto修饰变量,block无法修改,因为block使用的时候是内部创建了变量来保存外部的变量的值,block只有修改内部自己变量的权限,无法修改外部变量的权限。
    static修饰变量,block可以修改,因为block把外部static修饰变量的指针存入,block直接修改指针指向变量值,即可修改外部变量值。
    全局变量值,全局变量无论哪里都可以修改,当然block内部也可以修改。

    Q:__block 修饰符作用?

    • __block可以用于解决block内部无法修改auto变量值的问题
    • __block不能修饰全局变量、静态变量(static)
    • 编译器会将__block变量包装成一个对象
    • __block修改变量:age->__forwarding->age
    • __Block_byref_age_0结构体内部地址和外部变量age是同一地址

    在这里插入图片描述

    Q:block的属性修饰词为什么是copy?

    block一旦没有进行copy操作,就不会在堆上
    block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期
    @property (copy, nonatomic) CMBCAddressUIBlock finishCompletion;

    Block分类**

    block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

    __NSGlobalBlock __ ( _NSConcreteGlobalBlock )
    __NSStackBlock __ ( _NSConcreteStackBlock )
    __NSMallocBlock __ ( _NSConcreteMallocBlock )

    各类型的block在内存中如何分配的?

    • __NSGlobalBlock __ 在数据区
    • __NSMallocBlock __ 在堆区(访问外部变量强引用在堆区)
    • __NSStackBlock __ 在栈区(访问外部变量弱引用在栈区)
    • 堆:动态分配内存,需要程序员自己申请,程序员自己管理
    • 栈: 自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况
      在这里插入图片描述

    对每种类型block调用copy操作后是什么结果?

    • __NSGlobalBlock __ 调用copy操作后,什么也不做
    • __NSStackBlock __ 调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆
    • __NSStackBlock __ 调用copy操作后,复制效果是:引用计数增加;副本存储位置是堆

    对象类型的auto变量**

    Q:当block内部访问了对象类型的auto变量时,是否会强引用?
    答案:分情况讨论,分为栈block和堆block
    栈block
    a) 如果block是在栈上,将不会对auto变量产生强引用
    b) 栈上的block随时会被销毁,也没必要去强引用其他对象
    堆block
    1.如果block被拷贝到堆上:
    a) 会调用block内部的copy函数
    b) copy函数内部会调用_Block_object_assign函数
    c) _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
    2.如果block从堆上移除
    a) 会调用block内部的dispose函数
    b) dispose函数内部会调用_Block_object_dispose函数
    c) _Block_object_dispose函数会自动释放引用的auto变量(release)
    正确答案:

    如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
    如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用

    Q:__weak 在使用clang转换OC为C++代码时,可能会遇到以下问
    题cannot create __weak reference in file using manual reference

    解决方案:支持ARC、指定运行时系统版本,比如
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

    Q1:gcd的block中引用 Person对象什么时候销毁?

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"age:%d",person.age);
        });
        
        NSLog(@"touchesBegan");
    }
    

    原因:gcd的block默认会做copy操作,即dispatch_after的block是堆block,block会对Person强引用,block销毁时候Person才会被释放。

    Q2:上述代码如果换成__weak,Person什么时候释放?

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        __weak Person *weakPerson = person;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"age:%p",weakPerson);
        });
    
        NSLog(@"touchesBegan");
    }
    

    原因:使用__weak修饰过后的对象,堆block会采用弱引用,无法延时Person的寿命,所以在touchesBegan函数结束后,Person就会被释放,gcd就无法捕捉到Person。

    Q4:如果gcd内部先强引用后弱引用,Person什么时候释放?

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        __weak Person *weakPerson = person;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)),
                       dispatch_get_main_queue(), ^{
                           
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"2-----age:%p",weakPerson);
            });
            NSLog(@"1-----age:%p",person);
        });
    
        NSLog(@"touchesBegan");
    }
    

    原因:Person会等待强引用执行完毕后释放,只要强引用执行完,就不会等待后执行的弱引用,会直接释放的,所以Person释放时间为4秒。

    block循环引用:

    循环引用原因

    Block的循环引用原理和解决方法大家都比较熟悉,此处将结合上文的介绍,介绍一种不常用的解决Block循环引用的方法和一种借助Block参数解决该问题的方法。
    Block循环引用原因:一个对象A有Block类型的属性,从而持有这个Block,如果Block的代码块中使用到这个对象A,或者仅仅是用用到A对象的属性,会使Block也持有A对象,导致两者互相持有,不能在作用域结束后正常释放。

    循环引用解决方式

    解决原理:对象A照常持有Block,但Block不能强引用持有对象A以打破循环。

    第一种方式:__unsafe_unretained

    __unsafe_unretained Person *person = [[Person alloc] init];
    person.block = ^{
        NSLog(@"age is %d", weakPerson.age);
    };
    

    第二种方式:__block

    __block的作用:能够对外部的变量操作和修改

     __block XXController *blkSelf = self;
        self.blk = ^{
            NSLog(@"In Block : %@",blkSelf);
        };
    

    注意上述代码仍存在内存泄露,因为:

    • XXController对象持有Block对象blk
    • blk对象持有__block变量blkSelf
    • __block变量blkSelf持有XXController对象
       __block XXController *blkSelf = self;
        self.blk = ^{
            NSLog(@"In Block : %@",blkSelf);
            blkSelf = nil;//不能省略
        };
        
        self.blk();//该block必须执行一次,否则还是内存泄露
    

    在block代码块内,使用完使用完__block变量后将其设为nil,并且该block必须至少执行一次后,不存在内存泄露,因为此时:

    • XXController对象持有Block对象blk
    • blk对象持有__block变量blkSelf(类型为编译器创建的结构体)
    • __block变量blkSelf在执行blk()之后被设置为nil(__block变量结构体的__forwarding指针指向了nil),不再持有XXController对象,打破循环

    第二种使用__block打破循环的方法,优点是:

    • 可通过__block变量动态控制持有XXController对象的时间,运行时决定是否将nil或其他变量赋值给__block变量
    • 不能使用__weak的系统中,使用__unsafe_unretained来替代__weak打破循环可能有野指针问题,使用__block则可避免该问题

    其缺点也明显:

    • 必须手动保证__block变量最后设置为nil
    • block必须执行一次,否则__block不为nil循环应用仍存在

    因此,还是避免使用第二种不常用方式,直接使用__weak打破Block循环引用

    第三种方式:__weak

    __weak 加入弱引用表示同一个指针地址,weakSelf 是否对引用计数处理

    将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入,Block就不会捕获该对象,而将其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露

        __weak typeof(self) weakSelf = self;
        self.blk = ^{
            __strong typeof(self) strongSelf = weakSelf;
            NSLog(@"Use Property:%@", strongSelf.name);
            //……
        };
        self.blk();
    

    为什么 weakSelf 需要配合 strongSelf 使用

    __weak typeof(self) weakSelf = self;
    [self doSomeBackgroundJob:^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            ...
        }
    }];
    

    在 block 中先写一个 strongSelf,其实是为了避免在 block 的执行过程中,突然出现 self 被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。

    strongSelf 释放时机?

    strongSelf 的作用域在block里面,也就是block执行完毕后就会被释放掉。

    比如下面这样

    __weak __typeof__(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [weakSelf doSomething];
        [weakSelf doOtherThing];
    
    });
    

    在 doSomething 内,weakSelf 不会被释放.可是在执行完第一个方法后 ,weakSelf可能就已经释放掉,再去执行 doOtherThing,会引起 一些奇怪的逻辑,甚至闪退。
    所以需要这么写

    __weak __typeof__(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __strong __typeof(self) strongSelf = weakSelf;
        [strongSelf doSomething];
        [strongSelf doOtherThing];
    });
    

    在比如

     __weak typeof(self) weakSelf = self;
        [self.collectionView performBatchUpdates:^{
            __strong typeof(self) strongSelf = weakSelf;
            if (strongSelf) {
                [strongSelf.collectionView deleteItemsAtIndexPaths:@[ previousIndexPath ]];
                [strongSelf.collectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
            }
        } completion:^(BOOL finished) {
            __strong typeof(self) strongSelf = weakSelf;
            if ([strongSelf.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:didMoveToIndexPath:)]) {
                [strongSelf.dataSource collectionView:strongSelf.collectionView itemAtIndexPath:previousIndexPath didMoveToIndexPath:newIndexPath];
            }
        }];
    

    优点:

    • 简化了两行代码,更优雅
    • 更明确的API设计:告诉API使用者,该方法的Block直接使用传进来的参数对象,不会造成循环引用,不用调用者再使用weak避免循环

    __weak 能够自动置为nil的原因?

    依赖于终中介者模式,自己不方便nil就交给别人来处理

    第四种方式:self->block->self-通讯

    self.block = ^(ViewController *vc) {
            NSLog(@"%@",vc.name)
        };
    

    分析
    self 对block 持有,但是vc)没有对block 持有,所以不造成循环引用。它只是作为一种临时变量压栈进去。

    第五种方式:proxy

    self.timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(test1) userInfo:nil repeats:YES];
    
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    
    -(void)test1{
    
        NSLog(@"%s",__FUNCTION__);
    
    }
    

    1.timer的timerWithTimeInterval这个方法里面的参数target,接收的是viewController的内存地址,而在该方法内部,会形成一个对viewController 的强引用;
    2.而恰巧 timer 是viewController的一个强指针属性,这就造成了强循环引用

    因此我们可以设法在timer和viewController之间加入一个中间人。你timer不是谁把地址传给你你就强引用谁吗?好嘛,你本来是要强引用viewController的,现在你不必了,中间人的地址传给你timer,你timer要调用viewController的什么方法,你先告知中间人,中间人因为这种弱引用了viewController,所以可以把你timer发过来的消息转发给viewController。这样,即解决了消息传递问题,又能将强循环引用斩断,一举两得。
    请添加图片描述

    block 底层编译时

    block 的本质(结构体)

    如图我简单的声明了一个block查看汇编
    请添加图片描述

    block 结构体原型

    struct __mian_block_impl_0 {
      struct __block_impl impl;
      struct __mian_block_desc_0* Desc;
      __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    总结block 是一个结构体,所以block能够打印“%@”

    {}的由来

    如图所示

     void(*block)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA));
    
    

    这两个方法是有两个参数:
    1.__mian_block_func_0
    2.&__mian_block_desc_0_DATA

    static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
      printf("赵苗苗");
        }
    

    在结构体里面传给了 impl.FuncPtr = fp

     __mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp; // __mian_block_func_0 这个函数给了fp 手法:函数式:(以函数作为参数传进来)
        Desc = desc;
      }
    

    小知识点:以函数式作为参数就是函数式编程

    总结结构体里面有匿名函数:({})

    block 为什么需要调用block()

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

    简洁化

    
    // 为什么参数是block 
    // block 里面包含了所有参数,隐藏参数
    // 函数里面拿不到block,所以把block传进来
     block->FuncPtr(block);
    

    因为函数的声明和具体的实现

    block 为什么捕获外部变量要使用__block

    不使用__block如图所示

    请添加图片描述

    static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy 
        printf("赵苗苗 --- %d",a);
        }
    

    分析

    1.a 在编译时就自动生成了相应的变量
    2. int a = __cself->a 是值拷贝
    3. 值拷贝:内存地址与原来的内存地址不一样,所以只能读,不能修改

    使用__block如图所示

    请添加图片描述

    分析

     __Block_byref_a_0 a = {
            // a 是外部变量的地址
            (void*)0,(__Block_byref_a_0 *)&a,
            0, sizeof(__Block_byref_a_0),
            // 值
            10
            
        };
    

    1.把原来的地址和值封装成对象传给了结构体

    static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref // 指针拷贝
        
        // 所以对block 里面的++,就是对外面的++
            (a->__forwarding->a)++;
            printf("赵苗苗 --- %d",(a->__forwarding->a));
        }
    

    __Block_byref_a_0 *a = __cself->a 它是一个指针拷贝
    所以对block 里面的++,就是对外面的++

    block 底层运行时

    block Copy 如何从栈区拷贝到堆区的。

    解读过的源码

    
    
    
    展开全文
  • dropblock的作用不在累述,这里只分享使用4行代码实现dropblock。dropblock的介绍可以参考博主的另一篇文章pytorch 22 8种Dropout方法的简介 及 基于Dropout用4行代码快速实现DropBlock_万里鹏程转瞬至的博客-CSDN...
  • Blocks、Block基础理解 \总结

    千次阅读 2020-08-02 15:56:52
    一、 Blocks是C语言的 扩展功能:带有自动变量(局部变量)的匿名函数。 C语言中用到的变量:自动变量(局部变量)、静态变量、静态局部变量...1、Block语法:^ 返回值类型 (参数列表) {表达式} Block类型变量: a..
  • ATTENTION_SE_ [se_block, cbam_block, eca_block]

    千次阅读 2021-12-09 14:29:01
    1: 三种注意力机制的封装已经完成可参考: ... 应用时可以将注意力机制放置在forword之前就可以 ...from nets.attention import cbam_block...attention_block = [se_block, cbam_block, eca_block] class YoloBody(nn.M
  • 本问主要说client上传文件的时候,向namenode申请新的block的流程和 NameNode如何为block分配存储在哪个DateNode之上. 二.流程概览. DFSClient在写数据时, 首先会调用ClientProtocol.create()方法在Namenode的文件...
  • css中inline-block是什么?本篇文章就给大家介绍在css中inline-block是什么意思,让大家了解在css布局中使用inline-block的好处。有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所帮助。首先我们来了解...
  • BlockManager最重要的功能之一就是维护Namenode内存中的数据块信息, BlockManager中存储的数据块信息包含两个部分。 ■ 数据块与存储这个数据块的数据节点存储的对应关系, 这部分信息保存在数据块对应的...
  • block、inline和inline-block

    万次阅读 多人点赞 2019-01-07 21:49:53
    block、inline、inline-block行内元素和块级元素blockinlineinline-block行内元素和块级元素的区别常见用法 行内元素:又叫内联元素, 块级元素:是一个元素,占用了全部宽度,在前后都是换行符
  • Block Ack机制

    千次阅读 2020-11-12 11:24:38
    BA机制 BA机制是在802.11n中出现的,中文叫做块确认机制,其通过将多个“acknowledgment”汇总到一个帧中来提高信道效率。 分类 immediate BA 立即应答:适用于高带宽,低时延...BA这个block发送的时刻是TXOP(poll TX
  • ios __block修饰词底层实现原理

    千次阅读 2018-10-23 14:25:28
    void (*testBlock)(int) = ((void (*)(int))&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, strongStr, weakStr, (__Block_byref_b_0 *)&b, (__Block_byref_blockStr_1 *)&...
  • ResNet - Conv-block&identity-block的区别

    千次阅读 2021-08-26 15:27:33
    1、basic_block=identity_block 2、左conv-block 右 identity-block 本质区别就是有没有下采样downsample和通道转换 downsample直接利用卷积完成
  • 2.2:在arc下,block 自动加上copy的情况---:返回block。 2.3:在arc下,block 自动加上copy的情况---:强指针__block; 2.4:arc环境下:方法名中有usingBlock的方法参数时,也会进行copy操作。 2.5:在arc下,...
  • 面试题block的原理是怎样的?本质是什么?__block的作用是什么?有什么使用注意点?block的属性修饰词为什么是copy?使用block有哪些使用注意?block在修改NSMutableArray,需不需要添加__block?首先对block有一个...
  • Linux编程点击右侧关注,免费入门到精通!作者丨彭序猿https://www.jianshu.com/p/4db3b4f1d522前言Block 在平时开发中经常使用,...
  • 【Paper Note】SENet论文——SE block详解

    千次阅读 2021-04-27 15:27:28
    SENet这篇文章是CNN based,目前在推荐领域应用也很广泛,正好前几天看了MaskNet的论文,回头看这篇论文时候,发现SE block的作用和Mask block作用相似,顺便记录一下~ SENet论文中给出的结构如下,由于论文是在CNN...
  • block hook就是勾住block进行逻辑注入,且不影响原有block逻辑。 依赖OC的运行时机制,拦截方法比较容易,但是拦截block却没那么简单 前置知识1:Block数据结构 后面的介绍和分析都用到了block的数据结构,这里先...
  • db_block_size
  • 微信小程序之block标签

    千次阅读 2022-03-28 11:25:45
    微信小程序在使用block标签的时候,会发现如果在这个标签里设置class样式的话,不会生效; 因为block并不是一个组件,它仅仅是一个包装元素,只接受控制属性,不会在页面中做任何渲染; 所以它能够设置if,for这些...
  • 本节课讲解NVRAM Block的配置项,将上两节课的内容转化为实践
  • beamer中block样式修改

    千次阅读 2020-09-02 19:56:34
    苦于ppt格式修改的繁琐,最近在用beamer写ppt,还在熟悉的过程当中,遇到一个block的样式问题,查了一些资料,顺便做点总结\documentclass{beamer} \begin{document} \begin{frame}{Mathematics} \begin{block}{...
  • css中display的block属性是什么意思呢?他是指拥有该属性的html元素以块的方式显示,同时,该元素的前面和后面都会换行,也就是说,如果给一个元素B设置的display:block,那么该元素不会和他前面的元素A在一行显示,...
  • Block insecure private network requests

    千次阅读 2022-03-10 09:27:02
    不安全的私有网络访问 chrome更新到94之后,会自动阻止发送不安全的...(ERR_FAILED)(How to fix Chrome block your insecure private network requests) 报错 Access to fetch at ‘...’ from origin ‘’ ha..
  • AutoSAR系列讲解(实践篇)11.2-存储处理与Block

    万次阅读 多人点赞 2020-01-07 17:26:12
    本节主要讲解了NVRAM Block的一些特性
  • display:block含义

    万次阅读 多人点赞 2019-11-30 12:41:19
    根据CSS规范的规定,每一个网页元素都有一个display属性,用于确定该元素的类型,每一个元素都有默认的display属性值,比如div元素,它的默认display属性值为“block”,成为“块级”元素(block-level);而span元素...
  • Eigen子矩阵操作(block)

    千次阅读 多人点赞 2019-09-30 10:25:17
    "Here is now a with bottom-right 2x3 block copied into top-left 2x2 block:" endl a endl endl ; } 执行结果如下: Here is the array a : 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6 0.6...
  • OC中Block基本使用

    千次阅读 2020-10-03 11:48:39
    Block的说明 Block是OC语言中的一种数据类型,它是预先准备好的代码,在需要的时候就直接执行的。可以当做参数传递,也可以当做返回值,一般在实际开发中前者用的比较多。在多线程和网络也使用得相当频繁,一般都是...
  • opencv报错

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,716,899
精华内容 686,759
关键字:

BLOCK