[Object-C]_[初级]_[关于块block的引用外部变量的规则]

本文详细探讨了Objective-C中Block的使用场景与规则,包括Block如何处理外部变量、Object-C对象及C++对象的引用,解释了Block在GCD异步编程中的作用,以及在MRC模式下Block对外部对象生命周期的影响。

场景

  1. 在开发 Object-C 程序时, 很多情况下会用到它的块 block 特性, 这个 block 其实就是 lambda 表达式. 这个 blocklambda有什么区别, 还有什么需要注意的编程点?

  2. 我们在使用 dispatch_async 函数进行 GCD 异步编程时, 在 block 里引用的外部范围的 object 变量是否需要 retain? 如果不 retain 的话, autoreleasepoolblock 所属的范围结束后调用对象的 release 方法那不是会销毁,而 block之后再调用从而导致野指针?

说明

  1. Object-C 在现在逐步被 Swift 代替开发新的模块. Apple 的战略估计是类似 Android 系统里 Koltin 代替 Java. 但是在它们之间也可以互相调用. 所以仍然使用 Object-C 开发 iOSmacOS 程序也未尝不可. Object-C 的其中两个优势是还可以和 C++ 混编,轻松调用C/C++API. 当然和 Java 不同, Object-C 没有继续被 Apple 发展,所以它的性能在未来应该会比 Swift 低的 is-swift-faster-than-objective-c. 这些属于某个公司的语言, Object-C, Swift, go , Java, Koltin 个人觉得使用寿命肯定不如社区语言 C/C++,Python的. 所以我们在需要 Object-C 开发时, Object-C 只作为胶水语言,主要逻辑还是使用 C/C++ 开发会更好, 这样性能肯定不会低于 Swift, 大部分逻辑还可以跨平台使用.

  2. 我们通常会使用 dispatch_async 把数据发到界面线程处理,比如弹出一个警告对话框,像我之前说的在 Win32 界面开发里的做法原理是一样的.DispatchAsync使用lambda表达式来简化发送数据到界面线程.

dispatch_async(dispatch_get_main_queue(),^(){
	// 弹出警告对话框.
});
  1. Object-C 开发中, 我都会在 xcode里关闭 arc, 自己控制 Object-C 的引用计数,也就是所谓的 mrc . 这样能更好的精确控制对象的生命周期,减少内存使用,提高性能,即使使用自动计数也会有内存泄漏的情况. 前面的两个问题都可以归结到 block 对外部变量的引用规则。以下我们就来说说,并且是在没有自动引用计数的前提下,也能方便打印出实际的引用计数个数,还有就是 arcvoid*Object-c 对象不能直接转换,还有一些限制, 还是比较麻烦的. 目前来说,搞了 C++ 还是比较喜欢手动控制内存对象的生命周期,不太喜欢编译器做太多事情.

block

  1. 块在 OC 里作为一个 lambda 表达式, 用的频率还是很高的, 比如 dispatch_async 调用, 调用一些比较排序函数. 在块里的引用外部变量,需要知道它们的规则,才能避免出错. 以下例子我调整项目的为 mrc,以便知道引用计数做了什么变化。

图1:
在这里插入图片描述

  1. 以下是引用普通类型变量的规则:
    1. 引用全局变量(可改)和本地静态变量(可改).
    2. 引用全局函数.
    3. 所处的封闭范围的本地变量和函数参数.(只读)
      – 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
    4. __block声明的本地变量,会传递引用.
void testBlockTypeOfVariable(int times)
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    
    int y = 100;
    __block int x = 10;
    static BOOL hasThumbnail = NO;
    

    // 块声明:
    // block赋值时可以赋值给 typedef 声明的块类型或者块的声明类型。
    // typedef void (^dispatch_block_t)(void);
    void (^blockTemp)() = ^(){
        NSLog(@"== 输出类型变量 ==");
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        PRINT(@"y is %d",y);
        PRINT(@"x is %d",x);
    };
    
    
    NSLog(@"调用 blockWork 之前");
    blockTemp();
    
    // 1. 引用全局变量(可改)和本地静态变量(可改).
    // 2. 引用全局函数.
    // 3. 所处的封闭范围的本地变量和函数参数.(只读)
    //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
    // 4.__block声明的本地变量,会传递引用.
    dispatch_block_t blockWork = ^(){
        NSLog(@"========= blockWork ENTER =========");
        int number = 10;
        
        // 1. 引用全局变量(可改)和本地静态变量(可改).
        gNumber+=10;
        hasThumbnail = YES;
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        
        // 2. 引用全局函数.
        PRINT(@"PRINT 全局函数");
        
        // 3. 所处的封闭范围的本地变量和函数参数.(只读)
        //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
        // 不允许写
        // y+=100;
        PRINT(@"y is %d",y);
        
        // 4.__block声明的本地变量,会传递引用,可改.
        x+=number;
        x+=y;
        PRINT(@"x is %d",x);
        
        NSLog(@"=========  blockWork LEAVE ========= ");
    };
    blockWork();
    
    NSLog(@"调用 blockWork 之后");
    blockTemp();

    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}

  1. 以下是引用 Object-C 对象的规则:
    1. 复制块时,块会对块里所引用的外部 object-c 对象创建一个 strong 引用, 即它的引用计数+1.
    2. 块释放时,也会对应的对所引用的 object-c 的引用计数-1.
    3. 所以在块里的外部 object-c 对象,不需要担心它的声明周期,即使是异步执行的块. 比如 dispatch_async 函数.
    4. 赋值块给Obj类的func属性, 注意需要设置为 copy(arcstrong) 属性块才会在堆里创建而不至于在函数执行完之后失效.
    5. 注意,在给块属性设置 copy 时,如果是确定是线程安全的, 那么可以设置nonatomic,不然在设置属性为nil时,并不会立即release 掉块,而是加入到自动释放池里.
void testObjectCObject()
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    NSString* str = [NSString stringWithFormat:@"%@",@"Tobey"];
    
    // 阶段1: 创建block,并输出引用的 NSString* 的引用计数.
    dispatch_block_t blockObject = ^(){
        NSLog(@"========= blockObject ENTER =========");
        PRINT(@"%@",str);
        PRINT(@"str retainCount %d",[str retainCount]);
        
        NSLog(@"=========  blockObject LEAVE ========= ");
    };
    blockObject();
    
    // 阶段2: 调用 Block_copy 函数复制块.
    // 1. 复制块时,块会对块里所引用的外部object-c对象创建一个 strong引用, 即它的引用计数+1.
    // 2. 块释放时,也会对应的对所引用的object-c的引用计数-1.
    // 3. 所以在块里的外部object-c对象,不需要担心它的声明周期,即使是异步执行的块. 比如 dispatch_async 函数.
    PRINT(@"Copy block");
    dispatch_block_t blockObject2 = Block_copy(blockObject);
    blockObject2();
    Block_release(blockObject2);
    PRINT(@"Release block");
    PRINT(@"str retainCount %d",[str retainCount]);
    
    // 阶段3: 赋值块给Obj类的func属性, 注意需要设置为 copy 属性块才会在堆里创建而不至于在函数执行完之后失效.
    // 1. 注意,在给块属性设置copy时,如果是确定是线程安全的, 那么可以设置nonatomic,不然在设置属性为nil时,并不会立即release 掉块,而是加入到自动释放池里.
    PRINT(@"赋值块给Object-C的成员属性(copy).");
//    @autoreleasepool {
        Obj* o = [Obj new];
        o.func = blockObject;
        o.str = str;
        
        o.func();
        PRINT(@"释放块属性.");
        o.func = nil;
        o.str = nil;
//    }
    blockObject();
    
    // 阶段4: 异步调用块.dispatch_async函数复制了块. 在块执行完之后会释放块.
    dispatch_async(dispatch_get_main_queue(),blockObject);
    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}
  1. 以下是引用 cpp 对象的规则:
    1. 未声明__block的C++对象,必须有一个const copy拷贝构造函数.在块定义时,就会调用const copy拷贝构造函数.
    2. 声明_block可以必须有一个 copy构造函数,并且copy比const copy优先._block声明的cpp对象,不会调用拷贝构造函数,除非对block进行复制才会调用一次拷贝构造函数.块在拷贝时,会对引用的对象进行调用拷贝构造函数.
void testCppObject()
{
    // 阶段1: const 的外部对象.
    // 1. 未声明__block的C++对象,必须有一个const copy拷贝构造函数.
    // 2. 在进入block前已经调用了两次拷贝构造函数?什么原因?
    // 3. 应该是在复制block时,因为引用了A本地对象,调用了一次A 对象的拷贝构造函数放在栈里,
    //    接着这个栈里的对象赋值给block的局部变量a1又拷贝了一次.
    A a1;
    
    NSLog(@"================ funcBlockCpp declare ================");
    dispatch_block_t funcBlockCpp = ^(){
        
        // const 类型不允许赋值.
        // a1.i = 78;
        NSLog(@"================ funcBlockCpp BEGIN ================");
        NSLog(@"a1.i : %d",a1.i);
        NSLog(@"================ funcBlockCpp END ================");
    };

    funcBlockCpp();
    
    // 阶段2; __block 的引用对象.
    // 1. 声明_block可以必须有一个 copy构造函数,并且copy比const copy优先.
    // 2. 在进入block之前只调用了一次拷贝构造函数. 尽量使用__block声明C++对象,减少拷贝次数.
    // 3. __block的C++对象,在复制block时就调用了A的拷贝构造函数放到栈里,因为声明了__block为mutable的,
    //   block里的局部变量a2就只是引用了block栈里的A对象.
    __block A a2;
    NSLog(@"================ funcBlockCpp2 declare ================");
    dispatch_block_t funcBlockCpp2 = ^(){
        NSLog(@"================ funcBlockCpp BEGIN ================");
        NSLog(@"a2.i : %d",a2.i);
        a2.i = 192;
        NSLog(@"================ funcBlockCpp END ================");
    };
    
    funcBlockCpp2();
    
    // 阶段3: 复制块.
    NSLog(@"================ 复制funcBlockCpp2 ================");
    dispatch_block_t funcBlockCpp2Copy = Block_copy(funcBlockCpp2);
    
    funcBlockCpp2Copy();
    Block_release(funcBlockCpp2Copy);
    
}

完整代码

//
//  main.m
//  test-object-c-block
//
//  Created by sai on 4/23/20.
//  Copyright (c) 2020 tobey. All rights reserved.
//

#import <Foundation/Foundation.h>
#include <vector>

NSString* gUrl = @"https://infoworld.blog.youkuaiyun.com";
int gNumber = 100;
BOOL shouldKeepRunning = YES;        // global

class A{
public:
    A():i(88){
        NSLog(@"A() -> i: %d",i);
    }
    
    A(const A& a){
        
        i = a.i;
        i+=1;
        NSLog(@"A(const A& a) -> i: %d",i);
    }
    
    A(A& a){
        
        i = a.i;
        i+=100;
        NSLog(@"A(A& a) -> i: %d",i);
    }
    
    ~A(){
        NSLog(@"~A() -> i: %d",i);
    }
    int i;
};

// Object-C 对象
@interface Obj : NSObject

@property (nonatomic,copy,readwrite) dispatch_block_t func;
@property (copy,readwrite) NSString* str;

@end

@implementation Obj

@synthesize func;
@synthesize str;

@end

void PRINT(NSString *format, ...)
{
    va_list args;
    va_start(args, format);
    NSLogv([@" -> " stringByAppendingString:format],args);
    va_end(args);
}

void testObjectCObject()
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    NSString* str = [NSString stringWithFormat:@"%@",@"Tobey"];
    
    // 阶段1: 创建block,并输出引用的 NSString* 的引用计数.
    dispatch_block_t blockObject = ^(){
        NSLog(@"========= blockObject ENTER =========");
        PRINT(@"%@",str);
        PRINT(@"str retainCount %d",[str retainCount]);
        
        NSLog(@"=========  blockObject LEAVE ========= ");
    };
    blockObject();
    
    // 阶段2: 调用 Block_copy 函数复制块.
    // 1. 复制块时,块会对块里所引用的外部object-c对象创建一个 strong引用, 即它的引用计数+1.
    // 2. 块释放时,也会对应的对所引用的object-c的引用计数-1.
    // 3. 所以在块里的外部object-c对象,不需要担心它的声明周期,即使是异步执行的块. 比如 dispatch_async 函数.
    PRINT(@"Copy block");
    dispatch_block_t blockObject2 = Block_copy(blockObject);
    blockObject2();
    Block_release(blockObject2);
    PRINT(@"Release block");
    PRINT(@"str retainCount %d",[str retainCount]);
    
    // 阶段3: 赋值块给Obj类的func属性, 注意需要设置为 copy 属性块才会在堆里创建而不至于在函数执行完之后失效.
    // 1. 注意,在给块属性设置copy时,如果是确定是线程安全的, 那么可以设置nonatomic,不然在设置属性为nil时,并不会立即release 掉块,而是加入到自动释放池里.
    PRINT(@"赋值块给Object-C的成员属性(copy).");
//    @autoreleasepool {
        Obj* o = [Obj new];
        o.func = blockObject;
        o.str = str;
        
        o.func();
        PRINT(@"释放块属性.");
        o.func = nil;
        o.str = nil;
//    }
    blockObject();
    
    // 阶段4: 异步调用块.dispatch_async函数复制了块. 在块执行完之后会释放块.
    dispatch_async(dispatch_get_main_queue(),blockObject);
    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}

void testBlockTypeOfVariable(int times)
{
    NSLog(@"====================== %s BEGIN ==========================",__FUNCTION__);
    
    int y = 100;
    __block int x = 10;
    static BOOL hasThumbnail = NO;
    

    // 块声明:
    // block赋值时可以赋值给 typedef 声明的块类型或者块的声明类型。
    // typedef void (^dispatch_block_t)(void);
    void (^blockTemp)() = ^(){
        NSLog(@"== 输出类型变量 ==");
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        PRINT(@"y is %d",y);
        PRINT(@"x is %d",x);
    };
    
    
    NSLog(@"调用 blockWork 之前");
    blockTemp();
    
    // 1. 引用全局变量(可改)和本地静态变量(可改).
    // 2. 引用全局函数.
    // 3. 所处的封闭范围的本地变量和函数参数.(只读)
    //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
    // 4.__block声明的本地变量,会传递引用.
    dispatch_block_t blockWork = ^(){
        NSLog(@"========= blockWork ENTER =========");
        int number = 10;
        
        // 1. 引用全局变量(可改)和本地静态变量(可改).
        gNumber+=10;
        hasThumbnail = YES;
        PRINT(@"gNumber is %d",gNumber);
        PRINT(@"hasThumbnail is %d",hasThumbnail);
        
        // 2. 引用全局函数.
        PRINT(@"PRINT 全局函数");
        
        // 3. 所处的封闭范围的本地变量和函数参数.(只读)
        //   -- 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block.
        // 不允许写
        // y+=100;
        PRINT(@"y is %d",y);
        
        // 4.__block声明的本地变量,会传递引用,可改.
        x+=number;
        x+=y;
        PRINT(@"x is %d",x);
        
        NSLog(@"=========  blockWork LEAVE ========= ");
    };
    blockWork();
    
    NSLog(@"调用 blockWork 之后");
    blockTemp();

    NSLog(@"====================== %s END ==========================",__FUNCTION__);
}

void testCppObject()
{
    // 阶段1: const 的外部对象.
    // 1. 未声明__block的C++对象,必须有一个const copy拷贝构造函数.
    // 2. 在块定义时,就会调用const copy拷贝构造函数.
    A a1;
    
    NSLog(@"================ funcBlockCpp declare ================");
    dispatch_block_t funcBlockCpp = ^(){
        
        // const 类型不允许赋值.
        // a1.i = 78;
        NSLog(@"================ funcBlockCpp BEGIN ================");
        NSLog(@"a1.i : %d",a1.i);
        NSLog(@"================ funcBlockCpp END ================");
    };

    funcBlockCpp();
    
    // 阶段2; __block 的引用对象.
    // 1. 声明_block可以必须有一个 copy构造函数,并且copy比const copy优先.
    // 2. _block声明的cpp对象,不会调用拷贝构造函数,除非对block进行复制才会调用一次拷贝构造函数.块在拷贝时,会对引用的对象进行调用拷贝构造函数.
    __block A a2;
    NSLog(@"================ funcBlockCpp2 declare ================");
    dispatch_block_t funcBlockCpp2 = ^(){
        NSLog(@"================ funcBlockCpp2 BEGIN ================");
        NSLog(@"a2.i : %d",a2.i);
        a2.i = 192;
        NSLog(@"================ funcBlockCpp2 END ================");
    };
    
    funcBlockCpp2();
    
    // 阶段3: 复制块.
    NSLog(@"================ 复制funcBlockCpp2 ================");
    dispatch_block_t funcBlockCpp2Copy = Block_copy(funcBlockCpp2);
    
    funcBlockCpp2Copy();
    Block_release(funcBlockCpp2Copy);
    
}

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        // insert code here...
        NSLog(@"Hello, World!");
        testBlockTypeOfVariable(10);
        testObjectCObject();
        testCppObject();
        dispatch_async(dispatch_get_main_queue(),^(){
            shouldKeepRunning = NO;
        });
        
        NSRunLoop *theRL = [NSRunLoop currentRunLoop];
        while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    }
    return 0;
}


输出

2020-05-02 11:49:15.993 test-object-c-block[795:303] Hello, World!
2020-05-02 11:49:15.995 test-object-c-block[795:303] ====================== testBlockTypeOfVariable BEGIN ==========================
2020-05-02 11:49:15.996 test-object-c-block[795:303] 调用 blockWork 之前
2020-05-02 11:49:15.996 test-object-c-block[795:303] == 输出类型变量 ==
2020-05-02 11:49:15.997 test-object-c-block[795:303]  -> gNumber is 100
2020-05-02 11:49:15.997 test-object-c-block[795:303]  -> hasThumbnail is 0
2020-05-02 11:49:15.997 test-object-c-block[795:303]  -> y is 100
2020-05-02 11:49:15.998 test-object-c-block[795:303]  -> x is 10
2020-05-02 11:49:15.998 test-object-c-block[795:303] ========= blockWork ENTER =========
2020-05-02 11:49:15.999 test-object-c-block[795:303]  -> gNumber is 110
2020-05-02 11:49:15.999 test-object-c-block[795:303]  -> hasThumbnail is 1
2020-05-02 11:49:15.999 test-object-c-block[795:303]  -> PRINT 全局函数
2020-05-02 11:49:16.000 test-object-c-block[795:303]  -> y is 100
2020-05-02 11:49:16.000 test-object-c-block[795:303]  -> x is 120
2020-05-02 11:49:16.001 test-object-c-block[795:303] =========  blockWork LEAVE ========= 
2020-05-02 11:49:16.001 test-object-c-block[795:303] 调用 blockWork 之后
2020-05-02 11:49:16.002 test-object-c-block[795:303] == 输出类型变量 ==
2020-05-02 11:49:16.002 test-object-c-block[795:303]  -> gNumber is 110
2020-05-02 11:49:16.002 test-object-c-block[795:303]  -> hasThumbnail is 1
2020-05-02 11:49:16.003 test-object-c-block[795:303]  -> y is 100
2020-05-02 11:49:16.003 test-object-c-block[795:303]  -> x is 120
2020-05-02 11:49:16.004 test-object-c-block[795:303] ====================== testBlockTypeOfVariable END ==========================
2020-05-02 11:49:16.004 test-object-c-block[795:303] ====================== testObjectCObject BEGIN ==========================
2020-05-02 11:49:16.004 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.005 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.005 test-object-c-block[795:303]  -> str retainCount 1
2020-05-02 11:49:16.006 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.006 test-object-c-block[795:303]  -> Copy block
2020-05-02 11:49:16.007 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.007 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.008 test-object-c-block[795:303]  -> str retainCount 2
2020-05-02 11:49:16.008 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.009 test-object-c-block[795:303]  -> Release block
2020-05-02 11:49:16.009 test-object-c-block[795:303]  -> str retainCount 1
2020-05-02 11:49:16.010 test-object-c-block[795:303]  -> 赋值块给Object-C的成员属性(copy).
2020-05-02 11:49:16.011 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.012 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.012 test-object-c-block[795:303]  -> str retainCount 3
2020-05-02 11:49:16.013 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.014 test-object-c-block[795:303]  -> 释放块属性.
2020-05-02 11:49:16.015 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.016 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.016 test-object-c-block[795:303]  -> str retainCount 1
2020-05-02 11:49:16.017 test-object-c-block[795:303] =========  blockObject LEAVE ========= 
2020-05-02 11:49:16.018 test-object-c-block[795:303] ====================== testObjectCObject END ==========================
2020-05-02 11:49:16.018 test-object-c-block[795:303] A() -> i: 88
2020-05-02 11:49:16.019 test-object-c-block[795:303] ================ funcBlockCpp declare ================
2020-05-02 11:49:16.020 test-object-c-block[795:303] A(const A& a) -> i: 89
2020-05-02 11:49:16.020 test-object-c-block[795:303] ================ funcBlockCpp BEGIN ================
2020-05-02 11:49:16.021 test-object-c-block[795:303] a1.i : 89
2020-05-02 11:49:16.022 test-object-c-block[795:303] ================ funcBlockCpp END ================
2020-05-02 11:49:16.022 test-object-c-block[795:303] A() -> i: 88
2020-05-02 11:49:16.023 test-object-c-block[795:303] ================ funcBlockCpp2 declare ================
2020-05-02 11:49:16.024 test-object-c-block[795:303] ================ funcBlockCpp2 BEGIN ================
2020-05-02 11:49:16.024 test-object-c-block[795:303] a2.i : 88
2020-05-02 11:49:16.025 test-object-c-block[795:303] ================ funcBlockCpp2 END ================
2020-05-02 11:49:16.025 test-object-c-block[795:303] ================ 复制funcBlockCpp2 ================
2020-05-02 11:49:16.026 test-object-c-block[795:303] A(A& a) -> i: 292
2020-05-02 11:49:16.027 test-object-c-block[795:303] ================ funcBlockCpp2 BEGIN ================
2020-05-02 11:49:16.028 test-object-c-block[795:303] a2.i : 292
2020-05-02 11:49:16.028 test-object-c-block[795:303] ================ funcBlockCpp2 END ================
2020-05-02 11:49:16.029 test-object-c-block[795:303] ~A() -> i: 192
2020-05-02 11:49:16.030 test-object-c-block[795:303] ~A() -> i: 192
2020-05-02 11:49:16.032 test-object-c-block[795:303] ~A() -> i: 89
2020-05-02 11:49:16.033 test-object-c-block[795:303] ~A() -> i: 88
2020-05-02 11:49:16.034 test-object-c-block[795:303] ========= blockObject ENTER =========
2020-05-02 11:49:16.035 test-object-c-block[795:303]  -> Tobey
2020-05-02 11:49:16.035 test-object-c-block[795:303]  -> str retainCount 2
2020-05-02 11:49:16.036 test-object-c-block[795:303] =========  blockObject LEAVE ========= 

参考

Objective-C ARC: strong vs retain and weak vs assign

Blocks Programming Topics

Programming with Objective-C

is-swift-faster-than-objective-c

classDiagram class BaseComparer { <<abstract>> - _path_base: str - _path_target: str - _base_block_mapping: dict - _target_block_mapping: dict + compare_block() + compare_each_block(pre_fun) + is_color_equal(base_attr, target_attr) + are_colors_visually_identical() + get_not_equal_attrs() + cal_str_sim() + find_best_matched_indexes() + get_block_resource()$ + do_get_block_resource()$ + compare()$ + do_match_normal() + do_match_with_chapter() + filter_mapping_sheet()$ + filter_header_footer()$ + filter_chapter()$ + filter_mapping() } class TableComparer { + compare(block_name, base, target, belong_to) + get_block_resource(block, belong_to) + transpose_table(old_table, new_table) + compare_table(block_name, old_table, new_table, belong_to) + fill_visual_merged_cells(table)$ + compare_ordered_tables(block_name, old_table_obj, new_table_obj, belong_to) + find_differences(array1, array2, old_items, new_items, diff_mode) + get_cell_chars(cell_obj)$ + get_cell_content_list(cell_obj_lists, with_attr) + align_table_col(base_table, target_table) + cell_update_after(update_cells) + row_del_add_after(part, category) + process_delete_add_diff() } class TextComparer { + compare(block_name, base, target, belong_to) + get_block_resource(block, belong_to) } class PictureComparer { - _max_threshold: int = 5 - _min_block_size: int = 200 + get_block_resource(block, belong_to) + _calculate_grid_size(image) + _compute_hash_diff(base_img, target_img) + get_diff_attr(base_item, target_item) } class GraphicComparer { + get_block_resource(block, belong_to) } class DiffItem { - type: str - data_type: str - sub_type: str - block_name: str - old: object - new: object - belong_to: str - diff_point: str - diff_values: tuple } class TableObject { + rows: list[RowObject] + head_type: str + data_id: str } class RowObject { + cells: list[CellObject] + coordinate: tuple + layout: object + style: StyleObject + border: object + row_index: int + data_id: str } class CellObject { + text: str + content: list + style: StyleObject + border: object + row_index: int + col_index: int + merged_ranges: list + data_id: str + layout: object + get_chars() } class StyleObject { + background_color: str + background_style: str } BaseComparer <|-- TableComparer : 继承 BaseComparer <|-- TextComparer : 继承 BaseComparer <|-- PictureComparer : 继承 PictureComparer <|-- GraphicComparer : 继承 TableComparer --> TableObject : 使用 TableComparer --> RowObject : 使用 TableComparer --> CellObject : 使用 TableComparer --> DiffItem : 生成 TextComparer --> TextObject : 使用 TextComparer --> DiffItem : 生成 PictureComparer --> PictureObject : 使用 PictureComparer --> DiffItem : 生成 TableObject "1" --> "*" RowObject : 包含 RowObject "1" --> "*" CellObject : 包含 CellObject "1" --> "1" StyleObject : 关联 类图里面每个属性和方法后面都加上含义,像这样+ from_dict(data:dict): cls ← 从字典创建实例
最新发布
11-06
<think>我们将在类图中为每个属性和方法添加注释,说明其含义。由于类图是文本形式,我们将在每个成员后面用冒号和注释来描述。 注意:由于原类图已经很大,我们只对部分核心类进行详细注释,避免过于冗长。我们将重点放在BaseComparer、TableComparer、DiffItem以及表格相关的数据结构(TableObject, RowObject, CellObject)上。 另外,由于GraphicComparer和PictureComparer相似,且TextComparer相对简单,我们将简要注释。 我们将按照以下格式: 类名 { + 属性名: 类型 # 注释 + 方法名(参数): 返回类型 # 注释 } 对于抽象类和接口,使用<<abstract>>或<<interface>>标记。 注意:由于原类图中有很多方法,我们只注释关键方法,避免过于冗长。 </think>以下是详细注释后的类图,每个属性和方法都添加了含义说明: ```mermaid classDiagram class BaseComparer { <<abstract>> - _path_base: str # 基准文件路径 - _path_target: str # 目标文件路径 - _base_block_mapping: dict # 基准文档映射 - _target_block_mapping: dict # 目标文档映射 + compare_block(): dict # 对比文档级别的增删 + compare_each_block(pre_fun: callable): dict # 对比每种数据类型的详细差异 + is_color_equal(base_attr: str, target_attr: str): bool # 判断两个颜色是否相等 + are_colors_visually_identical(color1_hex: str, color2_hex: str, threshold=5): bool # 判断颜色在视觉上是否相同 + get_not_equal_attrs(base_item: object, target_item: object, compare_attrs: list): tuple # 获取不相同的属性列表 + cal_str_sim(text1: str, text2: str, i_j: tuple): float # 计算字符串相似度 + find_best_matched_indexes(lines1: list, lines2: list, min_similarity=0.6, cal_sim_func=None, data_type=None): list # 查找最佳匹配索引 + get_block_resource(block: object, belong_to: str): list # 从中获取指定类型的资源对象(抽象方法) + do_get_block_resource(block: object, belong_to: str, resource_attr: str, resource_obj: type): list # 实际获取资源对象的实现 + compare(block_name: str, base: list, target: list, belong_to: str): dict # 对比两种资源(抽象方法) + do_match_normal(base: list, target: list, match_functions: callable): tuple # 常规匹配算法 + do_match_with_chapter(base: list, target: list, func: callable): tuple # 按章节匹配算法 + filter_mapping_sheet(mapping: dict, first_sheet: str, is_old: bool): dict # 过滤不需要对比的工作表 + filter_header_footer(base_mapping: dict, target_mapping: dict): tuple # 处理页眉页脚配置 + filter_chapter(base_mapping: dict, target_mapping: dict): tuple # 过滤不需要对比的章节 + filter_mapping(base_mapping: dict, target_mapping: dict): tuple # 根据配置项过滤解析数据 } class TableComparer { + compare(block_name: str, base: list, target: list, belong_to: str): dict # 对比表格资源 + get_block_resource(block: object, belong_to: str): list # 从中获取表格资源 + transpose_table(old_table: TableObject, new_table: TableObject): tuple # 转置表格(行转列,列转行) + compare_table(block_name: str, old_table: TableObject, new_table: TableObject, belong_to: str): dict # 对比两个表格的差异 + fill_visual_merged_cells(table: TableObject) # 填充视觉上合并但实际未合并的单元格 + compare_ordered_tables(block_name: str, old_table_obj: TableObject, new_table_obj: TableObject, belong_to: str): tuple # 对比已排序的表格 + find_differences(array1: list, array2: list, old_items: list, new_items: list, diff_mode='normal'): tuple # 查找表格行列差异 + get_cell_chars(cell_obj: CellObject): list # 获取单元格中的字符列表 + get_cell_content_list(cell_obj_lists: list, with_attr: bool): list # 获取单元格内容列表 + align_table_col(base_table: TableObject, target_table: TableObject) # 对齐表格列数 + cell_update_after(update_cells: list): list # 单元格变更后处理(去除重复项) + row_del_add_after(part: list, category: str): list # 行增删后处理(过滤合并单元格) + process_delete_add_diff(block_name: str, sub_type: str, delete_tables: list, add_tables: list, belong_to: str, head_type: str): tuple # 处理表格增删差异 } class TextComparer { + compare(block_name: str, base: list, target: list, belong_to: str): dict # 对比文本资源 + get_block_resource(block: object, belong_to: str): list # 从中获取文本资源 } class PictureComparer { - _max_threshold: int = 5 # 图片哈希差异最大阈值 - _min_block_size: int = 200 # 图片分最小尺寸 + get_block_resource(block: object, belong_to: str): list # 从中获取图片资源 + _calculate_grid_size(image: Image): int # 计算图片分数量 + _compute_hash_diff(base_img: Image, target_img: Image): float # 计算分图片的哈希差异 + get_diff_attr(base_item: PictureObject, target_item: PictureObject): tuple # 获取图片差异属性 } class GraphicComparer { + get_block_resource(block: object, belong_to: str): list # 从中获取图形资源 } class DiffItem { - type: str # 变更类型(add/delete/update) - data_type: str # 数据类型(table/text/picture等) - sub_type: str # 子类型(row/col/cell等) - block_name: str # 所属名称 - old: object # 变更前对象 - new: object # 变更后对象 - belong_to: str # 所属区域(block/header/footer) - diff_point: str # 差异点描述 - diff_values: tuple # 差异值(旧值,新值) } class TableObject { + rows: list[RowObject] # 表格行对象列表 + head_type: str # 表头类型(horizontal/vertical) + data_id: str # 表格唯一标识 } class RowObject { + cells: list[CellObject] # 行中的单元格列表 + coordinate: tuple # 行坐标 + layout: object # 布局信息 + style: StyleObject # 样式信息 + border: object # 边框信息 + row_index: int # 行索引 + data_id: str # 行唯一标识 } class CellObject { + text: str # 单元格文本内容 + content: list # 单元格内容对象列表 + style: StyleObject # 单元格样式 + border: object # 单元格边框 + row_index: int # 所在行索引 + col_index: int # 所在列索引 + merged_ranges: list # 合并单元格范围 + data_id: str # 单元格唯一标识 + layout: object # 单元格布局 + get_chars(): list[CharObject] # 获取单元格中的字符对象 } class StyleObject { + background_color: str # 背景颜色 + background_style: str # 背景样式 } BaseComparer <|-- TableComparer : 继承 BaseComparer <|-- TextComparer : 继承 BaseComparer <|-- PictureComparer : 继承 PictureComparer <|-- GraphicComparer : 继承 TableComparer --> TableObject : 使用 TableComparer --> RowObject : 使用 TableComparer --> CellObject : 使用 TableComparer --> DiffItem : 生成 TextComparer --> TextObject : 使用 TextComparer --> DiffItem : 生成 PictureComparer --> PictureObject : 使用 PictureComparer --> DiffItem : 生成 TableObject "1" --> "*" RowObject : 包含 RowObject "1" --> "*" CellObject : 包含 CellObject "1" --> "1" StyleObject : 关联 ``` ### 关键类详细说明 1. **BaseComparer (基础比较器)**: - 抽象基类,定义了文档比较的通用逻辑 - 包含路径处理、颜色比较、字符串相似度计算等通用方法 - 实现了文档匹配、章节过滤等核心算法 2. **TableComparer (表格比较器)**: - 继承自BaseComparer,专门处理表格比较 - 核心方法`compare_table()`实现了表格的行列匹配和单元格比较 - 包含合并单元格处理(`fill_visual_merged_cells`)、表格转置(`transpose_table`)等特殊逻辑 3. **DiffItem (差异项)**: - 表示单个差异项的数据结构 - 包含变更类型、数据类型、新旧对象等关键信息 - 是所有比较器生成的标准输出格式 4. **表格数据结构**: - `TableObject`: 表示整个表格,包含表头类型和行列表 - `RowObject`: 表示表格行,包含单元格列表和行列索引 - `CellObject`: 表示单元格,包含内容、样式和合并信息 - `StyleObject`: 表示样式信息,如背景颜色 ### 表格比较流程说明 1. **预处理**: ```python # 对齐表格列数 align_table_col(base_table, target_table) # 填充视觉合并单元格 fill_visual_merged_cells(table) ``` 2. **行列匹配**: ```python # 列匹配 col_matched = match_func(del_cols, add_cols, is_col=True) # 行匹配 row_matched = match_func(del_rows, add_rows, is_col=False) ``` 3. **差异检测**: ```python # 查找行列差异 row_diffs, col_diffs, cell_diffs = find_differences(...) # 生成差异项 diff_item = DiffItem('update', 'table', 'cell', ...) ``` 4. **后处理**: ```python # 单元格变更后处理 cell_update_after(update_cells) # 行增删后处理 row_del_add_after(part, category) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白行峰 (花名)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值