[Object-C]_[初级]_[关于块block的引用外部变量的规则]
场景
在开发
Object-C
程序时, 很多情况下会用到它的块block
特性, 这个block
其实就是lambda
表达式. 这个block
和lambda
有什么区别, 还有什么需要注意的编程点?我们在使用
dispatch_async
函数进行GCD
异步编程时, 在block
里引用的外部范围的object
变量是否需要retain
? 如果不retain
的话,autoreleasepool
在block
所属的范围结束后调用对象的release
方法那不是会销毁,而block
之后再调用从而导致野指针?
说明
Object-C
在现在逐步被Swift
代替开发新的模块.Apple
的战略估计是类似Android
系统里Koltin
代替Java
. 但是在它们之间也可以互相调用. 所以仍然使用Object-C
开发iOS
和macOS
程序也未尝不可.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
, 大部分逻辑还可以跨平台使用.我们通常会使用
dispatch_async
把数据发到界面线程处理,比如弹出一个警告对话框,像我之前说的在Win32
界面开发里的做法原理是一样的.DispatchAsync使用lambda表达式来简化发送数据到界面线程.
dispatch_async(dispatch_get_main_queue(),^(){// 弹出警告对话框.
});
- 在
Object-C
开发中, 我都会在xcode
里关闭arc
, 自己控制Object-C
的引用计数,也就是所谓的mrc
. 这样能更好的精确控制对象的生命周期,减少内存使用,提高性能,即使使用自动计数也会有内存泄漏的情况. 前面的两个问题都可以归结到block
对外部变量的引用规则。以下我们就来说说,并且是在没有自动引用计数
的前提下,也能方便打印出实际的引用计数个数,还有就是arc
下void*
和Object-c
对象不能直接转换,还有一些限制, 还是比较麻烦的. 目前来说,搞了C++
还是比较喜欢手动控制内存对象的生命周期,不太喜欢编译器做太多事情.
块 block
- 块在
OC
里作为一个lambda
表达式, 用的频率还是很高的, 比如dispatch_async
调用, 调用一些比较排序函数. 在块里的引用外部变量,需要知道它们的规则,才能避免出错. 以下例子我调整项目的为mrc
,以便知道引用计数做了什么变化。
图1:
- 以下是引用普通类型变量的规则:
- 引用全局变量(可改)和本地静态变量(可改).
- 引用全局函数.
- 所处的封闭范围的本地变量和函数参数.(只读)
– 如果说栈变量,那么会自动识别为const类型.并以传值的方式传给 block. - __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__);
}
- 以下是引用
Object-C
对象的规则:- 复制块时,块会对块里所引用的外部
object-c
对象创建一个strong
引用, 即它的引用计数+1. - 块释放时,也会对应的对所引用的
object-c
的引用计数-1. - 所以在块里的外部
object-c
对象,不需要担心它的声明周期,即使是异步执行的块. 比如dispatch_async
函数. - 赋值块给Obj类的func属性, 注意需要设置为
copy
(arc
是strong
) 属性块才会在堆里创建而不至于在函数执行完之后失效. - 注意,在给块属性设置
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__);
}
- 以下是引用
cpp
对象的规则:- 未声明__block的C++对象,必须有一个const copy拷贝构造函数.在块定义时,就会调用const copy拷贝构造函数.
- 声明_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的引用外部变量的规则]相关推荐
- block引用外部变量原理
block在赋值时才会生成对应的block结构体实例(结构体数据结构在编译时已经生成),赋值时会扫一遍里面引用的外部变量(嵌套block中的外部变量也算,只不过嵌套block中的外部变量会被内外两个b ...
- java lambda 变量_为什么Java中lambda表达式不能改变外部变量的值,也不能定义自己的同名的本地变量呢?...
你问的问题在 Project Lambda 的概述文档上已经解释了,这都属于设计上的取舍. 不能改变外部变量的值是因为线程安全问题.当然这可能不是唯一原因,可能有其他考虑,但文档上清清楚楚说明了:Wh ...
- [Java]_[初级]_[使用正则高效替换字符串的多个占位符为多个值]
场景 在开发基于模板内容的Java程序时, 比如一个邮件内容模板,在内容里放置一些占位符$email,$name等来作为替换实际内容的符号.那么这时候如何做才可以少生成不必要的String字符串,从而 ...
- [Python]_[初级]_[多线程下载单个文件]
场景 使用Python做自动化测试时,有时候需要从网络下载软件安装包并安装.但是使用urllib库时,默认都是单线程下载文件,如果文件比较小还好说,如果文件有20M时,普通的网速就要等待很长的时间.有 ...
- [ATL/WTL]_[初级]_[自定义多列TreeView]
场景 在开发 Win32,WTL,MFC 程序时,经常会用到 ListView 这个表格控件,ListView 的数据是按照行来显示的,行与行之间没有并没有什么关系.但是如果行之间有父子关系,比如像树 ...
- [wxWidgets]_[初级]_[使用wxFormBuilder设计XRC文件快速开发界面]
场景: 1.wxWidgets是一个优秀的界面库,它的xrc界面布局格式也是很优秀的,可以使用类似mfc的所见即所得的设计方式,我发现它和xcode的interface builder的理念如出一辙. ...
- [JavaScript]_[初级]_[关于forof或者for...of循环语句的用法]
场景 在开发网页时,经常需要枚举NodeList类型的数据,比如通过document.body.childNodes获取的, 通过DOM.querySelectorAll也能获取到NodeList对象 ...
- [JavaScript]_[初级]_[关于forin或for...in循环语句的用法]
场景 在使用 JavaScript 开发或阅读代码时,会遇到forin语法的循环语句.这个循环语句到底会遍历什么数据? 说明 用于迭代具有可枚举属性的对象(除了Symbol内置对象). 迭代可枚举属性 ...
- [Cocoa]_[初级]_[关于nib文件加载过程awakeFromNib]
场景 在用xcode开发Cocoa程序时, 我们一般会使用IB来设计界面xib. 而在xib文件里我们往往会对用到的自定义NSView进行绑定以便引用使用. 但是这些xib里的object是什么时候进 ...
最新文章
- Stream Part.4
- BDB c++例子,从源码编译到运行
- mysql 的自动启动 使用配置文件 /etc/my.cnf
- android 网络图片查看器,Handler的用法
- 服务器备份文件格式,证书服务器,备份,还原
- centos jupyter 安装_centos7安装 jupyter
- [Ruby on Rails]Rails 3使用ActionMailer通过163发送邮件
- rename批量修改文件名
- 天主教、新教、东正教
- GNU Radio 之 rtl-sdr
- UTAU - 完整无乱码汉化策略及资源配布
- CenterOs操作
- win10应用商店怎么重新安装?
- 阿里云个人申请短信验证码申请总是失败
- 网络公益信息特征和用户行为规律研究以微博为例
- Launcher3 翻页动画详解与修改
- 【网络】为什么百度查到的ip和ipconfig查到的不一样;详解公网Ip和私网ip;详解网络分类ABC;
- 2014完,2015启
- Google Earth Engine(GEE)—— GRIDMET: 爱达荷大学网格化地表气象数据集
- PSTN 与 PBX 业务