场景

  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.csdn.net";
int gNumber = 100;
BOOL shouldKeepRunning = YES;        // globalclass 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;@endvoid 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

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

  1. block引用外部变量原理

    block在赋值时才会生成对应的block结构体实例(结构体数据结构在编译时已经生成),赋值时会扫一遍里面引用的外部变量(嵌套block中的外部变量也算,只不过嵌套block中的外部变量会被内外两个b ...

  2. java lambda 变量_为什么Java中lambda表达式不能改变外部变量的值,也不能定义自己的同名的本地变量呢?...

    你问的问题在 Project Lambda 的概述文档上已经解释了,这都属于设计上的取舍. 不能改变外部变量的值是因为线程安全问题.当然这可能不是唯一原因,可能有其他考虑,但文档上清清楚楚说明了:Wh ...

  3. [Java]_[初级]_[使用正则高效替换字符串的多个占位符为多个值]

    场景 在开发基于模板内容的Java程序时, 比如一个邮件内容模板,在内容里放置一些占位符$email,$name等来作为替换实际内容的符号.那么这时候如何做才可以少生成不必要的String字符串,从而 ...

  4. [Python]_[初级]_[多线程下载单个文件]

    场景 使用Python做自动化测试时,有时候需要从网络下载软件安装包并安装.但是使用urllib库时,默认都是单线程下载文件,如果文件比较小还好说,如果文件有20M时,普通的网速就要等待很长的时间.有 ...

  5. [ATL/WTL]_[初级]_[自定义多列TreeView]

    场景 在开发 Win32,WTL,MFC 程序时,经常会用到 ListView 这个表格控件,ListView 的数据是按照行来显示的,行与行之间没有并没有什么关系.但是如果行之间有父子关系,比如像树 ...

  6. [wxWidgets]_[初级]_[使用wxFormBuilder设计XRC文件快速开发界面]

    场景: 1.wxWidgets是一个优秀的界面库,它的xrc界面布局格式也是很优秀的,可以使用类似mfc的所见即所得的设计方式,我发现它和xcode的interface builder的理念如出一辙. ...

  7. [JavaScript]_[初级]_[关于forof或者for...of循环语句的用法]

    场景 在开发网页时,经常需要枚举NodeList类型的数据,比如通过document.body.childNodes获取的, 通过DOM.querySelectorAll也能获取到NodeList对象 ...

  8. [JavaScript]_[初级]_[关于forin或for...in循环语句的用法]

    场景 在使用 JavaScript 开发或阅读代码时,会遇到forin语法的循环语句.这个循环语句到底会遍历什么数据? 说明 用于迭代具有可枚举属性的对象(除了Symbol内置对象). 迭代可枚举属性 ...

  9. [Cocoa]_[初级]_[关于nib文件加载过程awakeFromNib]

    场景 在用xcode开发Cocoa程序时, 我们一般会使用IB来设计界面xib. 而在xib文件里我们往往会对用到的自定义NSView进行绑定以便引用使用. 但是这些xib里的object是什么时候进 ...

最新文章

  1. Stream Part.4
  2. BDB c++例子,从源码编译到运行
  3. mysql 的自动启动 使用配置文件 /etc/my.cnf
  4. android 网络图片查看器,Handler的用法
  5. 服务器备份文件格式,证书服务器,备份,还原
  6. centos jupyter 安装_centos7安装 jupyter
  7. [Ruby on Rails]Rails 3使用ActionMailer通过163发送邮件
  8. rename批量修改文件名
  9. 天主教、新教、东正教
  10. GNU Radio 之 rtl-sdr
  11. UTAU - 完整无乱码汉化策略及资源配布
  12. CenterOs操作
  13. win10应用商店怎么重新安装?
  14. 阿里云个人申请短信验证码申请总是失败
  15. 网络公益信息特征和用户行为规律研究以微博为例
  16. Launcher3 翻页动画详解与修改
  17. 【网络】为什么百度查到的ip和ipconfig查到的不一样;详解公网Ip和私网ip;详解网络分类ABC;
  18. 2014完,2015启
  19. Google Earth Engine(GEE)—— GRIDMET: 爱达荷大学网格化地表气象数据集
  20. PSTN 与 PBX 业务

热门文章

  1. 7.css内外边距设置
  2. NUXT 踩坑 —— 封装 Axios 请求拦截
  3. 特写|巨头造车:一场生死存亡的跨界战争
  4. java制作一个应用程序_一个制作java小应用程序的全过程
  5. HDU 6040 Hints of sd0061 思维
  6. Windows系统下完全隐藏桌面窗口
  7. 周记#2.纵有疾风起,人生不言弃
  8. 写给NOIp2018前的你们
  9. Gartner2018年度的十大安全项目
  10. Redsi通过geo计算距离