深入研究Block捕获外部变量和__block实现原理

EOCNetworkFetcher.h

typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);

@interface EOCNetworkFetcher : NSObject

@property (nonatomic, strong, readonly) NSURL *url;

- (id)initWithURL:(NSURL *)url;

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion;

@end

EOCNetworkFetcher.m

@interface EOCNetworkFetcher ()

@property (nonatomic, strong, readwrite) NSURL *url;

@property (nonatomic, copy) EOCNetworkFetcherCompletionHandler completionHandler;

@property (nonatomic, strong) NSData *downloadData;

@end

@implementation EOCNetworkFetcher

- (id)initWithURL:(NSURL *)url {

if(self = [super init]) {

_url = url;

}

return self;

}

- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)completion {

self.completionHandler = completion;

//开始网络请求

dispatch_async(dispatch_get_global_queue(0, 0), ^{

_downloadData = [[NSData alloc] initWithContentsOfURL:_url];

dispatch_async(dispatch_get_main_queue(), ^{

//网络请求完成

[self p_requestCompleted];

});

});

}

- (void)p_requestCompleted {

if(_completionHandler) {

_completionHandler(_downloadData);

}

}

@end

EOCClass.m

@implementation EOCClass {

EOCNetworkFetcher *_networkFetcher;

NSData *_fetchedData;

}

- (void)downloadData {

NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

[_networkFetcher startWithCompletionHandler:^(NSData *data) {

_fetchedData = data;

}];

}

@end

在这个例子中,存在3者之间形成环

1、completion handler的block因为要设置_fetchedData实例变量的值,所以它必须捕获self变量,也就是说handler块保留了EOCClass实例;

2、EOCClass实例通过strong实例变量保留了EOCNetworkFetcher,最后EOCNetworkFetcher实例对象也会保留了handler的block。

书上说的3种方法来打破循环。

方法一:手动释放EOCNetworkFetcher使用之后持有的_networkFetcher,这样可以打破循环引用

- (void)downloadData {

NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

[_networkFetcher startWithCompletionHandler:^(NSData *data) {

_fetchedData = data;

_networkFetcher = nil;//加上此行,打破循环引用

}];

}

方法二:直接释放block。因为在使用完对象之后需要人为手动释放,如果忘记释放就会造成循环引用了。如果使用完completion handler之后直接释放block即可。打破循环引用

- (void)p_requestCompleted {

if(_completionHandler) {

_completionHandler(_downloadData);

}

self.completionHandler = nil;//加上此行,打破循环引用

}

方法三:使用weakSelf、strongSelf

- (void)downloadData {

__weak __typeof(self) weakSelf = self;

NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];

_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];

[_networkFetcher startWithCompletionHandler:^(NSData *data) {

__typeof(&*weakSelf) strongSelf = weakSelf;

if (strongSelf) {

strongSelf.fetchedData = data;

}

}];

}

四.@weakify、@strongify实现原理

上面讲完了weakSelf、strongSelf之后,接下来再讲讲@weakify、@strongify,这两个关键字是RAC中避免Block循环引用而开发的2个宏,这2个宏的实现过程很牛,值得我们学习。

@weakify、@strongify的作用和weakSelf、strongSelf对应的一样。这里我们具体看看大神是怎么实现这2个宏的。

直接从源码看起来。

#define weakify(...) \

rac_keywordify \

metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)

#define strongify(...) \

rac_keywordify \

_Pragma("clang diagnostic push") \

_Pragma("clang diagnostic ignored \"-Wshadow\"") \

metamacro_foreach(rac_strongify_,, __VA_ARGS__) \

_Pragma("clang diagnostic pop")

看到这种宏定义,咋一看什么都不知道。那就只能一层层的往下看。

1. weakify

先从weakify(…)开始。

#if DEBUG

#define rac_keywordify autoreleasepool {}

#else

#define rac_keywordify try {} <a href='http://www.jobbole.com/members/wx895846013'>@catch</a> (...) {}

#endif

这里在debug模式下使用@autoreleasepool是为了维持编译器的分析能力,而使用@try/@catch 是为了防止插入一些不必要的autoreleasepool。rac_keywordify 实际上就是autoreleasepool {}

的宏替换。因为有了autoreleasepool {}的宏替换,所以weakify要加上@,形成@autoreleasepool {}。

#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

__VA_ARGS__:总体来说就是将左边宏中 … 的内容原样抄写在右边 __VA_ARGS__ 所在的位置。它是一个可变参数的宏,是新的C99规范中新增的,目前似乎只有gcc支持(VC从VC2005开始支持)。

那么我们使用@weakify(self)传入进去。__VA_ARGS__相当于self。此时我们可以把最新开始的weakify套下来。于是就变成了这样:

rac_weakify_,, __weak, __VA_ARGS__整体替换MACRO, SEP, CONTEXT, …

这里需要注意的是,源码中就是给的两个”,”逗号是连着的,所以我们也要等效替换参数,相当于SEP是空值。

替换完成之后就是下面这个样子:

autoreleasepool {}

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)

现在我们需要弄懂的就是metamacro_concat 和 metamacro_argcount是干什么用的。

继续看看metamacro_concat 的实现

#define metamacro_concat(A, B) \

metamacro_concat_(A, B)

#define metamacro_concat_(A, B) A ## B

## 是宏连接符。举个例子:

假设宏定义为#define XNAME(n) x##n,代码为:XNAME(4),则在预编译时,宏发现XNAME(4)与XNAME(n)匹配,则令 n 为 4,然后将右边的n的内容也变为4,然后将整个XNAME(4)替换为 x##n,亦即 x4,故 最终结果为 XNAME(4) 变为 x4。所以A##B就是AB。

metamacro_argcount 的实现

#define metamacro_argcount(...) \

metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

#define metamacro_at(N, ...) \

metamacro_concat(metamacro_at, N)(__VA_ARGS__)

metamacro_concat是上面讲过的连接符,那么metamacro_at, N = metamacro_atN,由于N = 20,于是metamacro_atN = metamacro_at20。

metamacro_at20的作用就是截取前20个参数,剩下的参数传入metamacro_head。

#define metamacro_head(...) \

metamacro_head_(__VA_ARGS__, 0)

#define metamacro_head_(FIRST, ...) FIRST

metamacro_head的作用返回第一个参数。返回到上一级metamacro_at20,如果我们从最源头的@weakify(self),传递进来,那么metamacro_at20(self,20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1),截取前20个参数,最后一个留给metamacro_head_(1),那么就应该返回1。

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self)) = metamacro_concat(metamacro_foreach_cxt, 1) 最终可以替换成metamacro_foreach_cxt1。

在源码中继续搜寻。

// metamacro_foreach_cxt expansions

#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \

metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \

SEP \

MACRO(1, CONTEXT, _1)

#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \

metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \

SEP \

MACRO(2, CONTEXT, _2)

#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \

metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \

SEP \

MACRO(3, CONTEXT, _3)

#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \

metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \

SEP \

MACRO(4, CONTEXT, _4)

#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \

metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \

SEP \

MACRO(5, CONTEXT, _5)

#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \

metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \

SEP \

MACRO(6, CONTEXT, _6)

#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \

metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \

SEP \

MACRO(7, CONTEXT, _7)

#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \

metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \

SEP \

MACRO(8, CONTEXT, _8)

#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \

metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \

SEP \

MACRO(9, CONTEXT, _9)

#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \

metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \

SEP \

MACRO(10, CONTEXT, _10)

#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \

metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \

SEP \

MACRO(11, CONTEXT, _11)

#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \

metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \

SEP \

MACRO(12, CONTEXT, _12)

#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \

metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \

SEP \

MACRO(13, CONTEXT, _13)

#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \

metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \

SEP \

MACRO(14, CONTEXT, _14)

#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \

metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \

SEP \

MACRO(15, CONTEXT, _15)

#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \

metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \

SEP \

MACRO(16, CONTEXT, _16)

#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \

metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \

SEP \

MACRO(17, CONTEXT, _17)

#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \

metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \

SEP \

MACRO(18, CONTEXT, _18)

#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \

metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \

SEP \

MACRO(19, CONTEXT, _19)

metamacro_foreach_cxt这个宏定义有点像递归,这里可以看到N 最大就是20,于是metamacro_foreach_cxt19就是最大,metamacro_foreach_cxt19会生成rac_weakify_(0,__weak,_18),然后再把前18个数传入metamacro_foreach_cxt18,并生成rac_weakify_(0,__weak,_17),依次类推,一直递推到metamacro_foreach_cxt0。

#define metamacro\_foreach\_cxt0(MACRO, SEP, CONTEXT)

metamacro_foreach_cxt0就是终止条件,不做任何操作了。

于是最初的@weakify就被替换成

autoreleasepool {}

metamacro_foreach_cxt1(rac_weakify_, , __weak, self)

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

代入参数

autoreleasepool {}

rac_weakify_(0,__weak,self)

最终需要解析的就是racweakify

#define rac_weakify_(INDEX, CONTEXT, VAR) \

CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

把(0,weak,self)的参数替换进来(INDEX, CONTEXT, VAR)。 INDEX = 0, CONTEXT = weak,VAR = self,

于是

CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

等效替换为

__weak __typeof__(self) self_weak_ = self;

最终@weakify(self) = weak typeof_(self) self_weak = self;

这里的selfweak 就完全等价于我们之前写的weakSelf。

2. strongify

再继续分析strongify(…)

rac_keywordify还是和weakify一样,是autoreleasepool {},只为了前面能加上@

_Pragma("clang diagnostic push") \

_Pragma("clang diagnostic ignored \"-Wshadow\"") \

_Pragma("clang diagnostic pop")

strongify比weakify多了这些_Pragma语句。

关键字_Pragma是C99里面引入的。_Pragma比#pragma(在设计上)更加合理,因而功能也有所增强。

上面的等效替换

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wshadow"

#pragma clang diagnostic pop

这里的clang语句的作用:忽略当一个局部变量或类型声明遮盖另一个变量的警告。

最初的

#define strongify(...) \

rac_keywordify \

_Pragma("clang diagnostic push") \

_Pragma("clang diagnostic ignored \"-Wshadow\"") \

metamacro_foreach(rac_strongify_,, __VA_ARGS__) \

_Pragma("clang diagnostic pop")

strongify里面需要弄清楚的就是metamacroforeach 和 rac_strongify。

#define metamacro_foreach(MACRO, SEP, ...) \

metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)

#define rac_strongify_(INDEX, VAR) \

__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

我们先替换一次,SEP = 空 , MACRO = racstrongify , VA_ARGS , 于是替换成这样。

metamacro_foreach_cxt(metamacro_foreach_iter,,rac_strongify_,self)

根据之前分析,metamacroforeach_cxt再次等效替换,metamacro_foreach_cxt##1(metamacro_foreach_iter,,rac_strongify,self)

根据

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

再次替换成metamacroforeach_iter(0, rac_strongify, self)

继续看看metamacro_foreach_iter的实现

#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)

最终替换成racstrongify(0,self)

#define rac_strongify_(INDEX, VAR) \

__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

INDEX = 0, VAR = self,于是@strongify(self)就等价于

__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

等价于

__strong __typeof__(self) self = self_weak_;

注意@strongify(self)只能使用在block中,如果用在block外面,会报错,因为这里会提示你Redefinition of ‘self’。

总结一下

@weakify(self) = @autoreleasepool{} weak typeof_ (self) self_weak = self;

@strongify(self) = @autoreleasepool{} strong typeof_(self) self = self_weak;

经过分析以后,其实@weakify(self) 和 @strongify(self) 就是比我们日常写的weakSelf、strongSelf多了一个@autoreleasepool{}而已,至于为何要用这些复杂的宏定义来做,目前我还没有理解。如果有大神指导其中的原因,还请多多指点。

更新

针对文章中给的例子3,大家都提出了疑问,为何没有检测出循环引用?其实这个例子有点不好。因为这个ViewController的引用计数一出来就是6,因为它被其他很多对象引用着。当然它是强引用了student,因为student的retainCount值是2。ViewController释放的时候才会把student的值减一。针对这个例子3,我重新抽取出中间的模型,重新举一个例子。

既然ViewController特殊,那我们就新建一个类。

#import

#import "Student.h"

@interface Teacher : NSObject

@property (copy , nonatomic) NSString *name;

@property (strong, nonatomic) Student *stu;

@end

#import "ViewController.h"

#import "Student.h"

#import "Teacher.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

Student *student = [[Student alloc]init];

Teacher *teacher = [[Teacher alloc]init];

teacher.name = @"i'm teacher";

teacher.stu = student;

student.name = @"halfrost";

student.study = ^{

NSLog(@"my name is = %@",teacher.name);

};

student.study();

}

如图所示,还是出现了循环引用,student的block强引用了teacher,teacher又强引用了student,导致两者都无法释放。

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(下)的更多相关文章

  1. 深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用(上)

    深入研究Block捕获外部变量和__block实现原理 前言 在上篇中,仔细分析了一下Block的实现原理以及__block捕获外部变量的原理.然而实际使用Block过程中,还是会遇到一些问题,比如R ...

  2. [HMLY]10.深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用

    前言 在上篇中,仔细分析了一下Block的实现原理以及__block捕获外部变量的原理.然而实际使用Block过程中,还是会遇到一些问题,比如Retain Circle的问题. 目录 1.Retain ...

  3. @weakify, @strongify ObjC的Block中使用weakSelf/strongSelf @weakify/@strongify

    首先要说说什么时候使用weakSelf和strongSelf. 下面引用一篇博客<到底什么时候才需要在ObjC的Block中使用weakSelf/strongSelf>的内容: Objec ...

  4. ObjC的Block中使用weakSelf/strongSelf @weakify/@strongify

    首先要说说什么时候使用weakSelf和strongSelf. 下面引用一篇博客<到底什么时候才需要在ObjC的Block中使用weakSelf/strongSelf>的内容: Objec ...

  5. block为什么用copy以及如何解决循环引用

    在完成项目期间,不可避免的会使用到block,因为block有着比delegate和notification可读性更高,而且看起来代码也会很简洁.于是在目前的项目中大量的使用block. 之前给大家介 ...

  6. ios block常见的错误(二)——循环引用

    这篇博文继续block的常见错误——循环引用. 循环引用是很多初学者不能察觉的,其产生的原因,是block中的代码会对对象进行强引用. 读者请阅读示例代码1,并思考示例代码1所创建的对象能否被正常销毁 ...

  7. ios 使用block中使用self可能产生的循环引用

    在block中调用 self,那么就会引起循环引用问题,那么这是为什么呢? 为什么self会对block进行强引用呢???? 这里推荐一篇关于block的专业文章,http://blog.csdn.n ...

  8. iOS中block的使用、实现底层、循环引用、存储位置

    一.整体介绍 定义:C语言的匿名函数,

  9. weakSelf 运用 strongSelf来解决block的循环引用

    SDWebImage 中有一段源码: #if SD_UIKIT Class UIApplicationClass = NSClassFromString(@"UIApplication&qu ...

随机推荐

  1. cocos2d-x 不能在android真机debug的问题

    最近在做cocos2d-x开发的时候,发现在android真机上不能调试C++代码,显示如下警告信息 Ignoring packet error, continuing... warning: unr ...

  2. [Hive - LanguageManual] VirtualColumns

    Virtual Columns Simple Examples Virtual Columns Hive 0.8.0 provides support for two virtual columns: ...

  3. mapreduce的调度算法和job调优

    调度算法: mapreduce当有很多的作业在执行的时候,是按照什么顺序去执行的? 调度算法顺序需要关注: 1.提高作业的吞吐量. 2.要考虑优先级. 三种调度器:如果作业跑不完,并且机器资源利用率比 ...

  4. 在WinForm编程中犯的一些错误

    1.一直以为,MouseClick事件在鼠标点击时发生,MouseDoubleClick事件在鼠标双击时发生.那么在单击鼠标时会调用MouseClick事件处理程序,双击鼠标时会调用MouseDoub ...

  5. HDU-4751 Divide Groups 染色问题

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4751 题意:有n个人,每个人都认识一些人,要求把他们分成两个集合,使得两个集合中的人都相符两两认识. ...

  6. SRM DIV1 500pt DP

    SRM 501 DIV1 500pt SRM 502 DIV1 500pt SRM 508 DIV1 500pt SRM 509 DIV1 500pt SRM 511 DIV1 500pt SRM 5 ...

  7. 能够提高开发效率的 Eclipse 实用操作

    工欲善其事,必先利其器.对于程序员来说,Eclipse便是其中的一个“器”.本文会从Eclipse快捷键和实用技巧这两个篇章展开介绍.Eclipse快捷键用熟后,不用鼠标,便可进行编程开发,避免鼠标分 ...

  8. 第三百天了 how can I 坚持

    郭娜还是不搭理我,或许是真没有遇到合适的.? 该咋办,好焦虑. 其实态度应该放低点,就这样是找不到对象的. 有必要这么死耗嘛,人生总是这么纠结. 周六年后,周日来吃火锅,这一年又过去了. 睡觉.

  9. VIM技巧(2)-删除匹配行

    VIM技巧(2)-删除匹配行 代码如下: * @Company:中国股份有限公司 * @author ymzhao (也有zyyang的) * @Date: Jan 22, 2014 11:25:29 ...

  10. Linux 的进程组、会话、守护进程

    一.进程组ID 每个进程都属于一个进程组.每个进程组有一个领头进程.进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号.每个进程组都有唯一的进程组ID(整数,也可以 ...