Kenny 肉桂的主页

记录自己的进步


  • 首页

  • 归档

  • 标签

  • 搜索
close

GCD实现线程同步的三种方式

发表于 2016-09-24   |   分类于 iOS Tips   |  

写这篇文章的原因是自己几天前的一个面试,当时面试官问这个线程同步的问题,感觉自己回答的不好,知识接触过,却不系统条理.所以这次特地整理一下

什么是线程同步

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
“同”字从字面上容易理解为一起动作
其实不是,“同”字应是指协同、协助、互相配合。
如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

以上内容来自百度百科

GCD中实现线程同步的方式

dispatch_group

dispatch_group是GCD中经常使用的线程同步方式,具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_queue_t queue = dispatch_queue_create("cc.imguiqing", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"task 1 on %@",[NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
NSLog(@"task 2 on %@",[NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
NSLog(@"task 3 on %@",[NSThread currentThread]);
});

可以看出,这个和普通的GCD任务相比,每个API都多了一个group参数.但是如果仅仅是像上面的方式使用,就没有什么必要了.我们使用Group的原因,更多是想要知道这个Group中的执行情况.借此来获得时机做一些逻辑操作.所以dispatch_group提供了两个API:

  1. 通知Group中的任务都执行完毕
1
2
3
4

dispatch_group_notify(group, queue, ^{
NSLog(@"all task done");
});
  1. 阻塞式的等待Group中的任务都执行完毕
1
2
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"since all done , I move on");

更常见的写法

上面的写法虽然简单,但是如果看过一些三方库的代码,发现那么用的并不多.更多的是利用dispatch_group_enter(group)和dispatch_group_leave(group)来包装任务,本质上两者没有区别,多说这些仅仅是让你别以后看代码的时候感到疑惑,
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dispatch_queue_t queue = dispatch_get_global_queue( 0, 0 );
dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
dispatch_async( queue, ^{
NSLog( @"task 1 --- %@", [NSThread currentThread] );
dispatch_group_leave(group);
} );
dispatch_group_enter(group);
dispatch_async( queue, ^{
NSLog( @"task 2 --- %@", [NSThread currentThread] );
dispatch_group_leave(group);
} );
dispatch_group_notify( group, queue, ^{
NSLog( @"all task done %@", [NSThread currentThread] );
} );

信号量

信号量可以理解为一个特殊的变量.程序对它的访问都是原子性的,我们通过PV操作来修改信号量.
使用代码简单说明:

1
2
3
4
5
6
dispatch_semaphore_t sem = 	dispatch_semaphore_create(0);
[networkManager requestWithDelay:5 completion:^{
dispatch_semaphore_signal(sem);//+1
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//-1
NSLog(@"five sectonds");

信号量创建的时候, 可以给他指定一个值.dispatch_semaphore_signal(sem)对信号进行+1操作.dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)对信号进行-1操作.当进行-1时,如果发现信号结果会小于0,那么线程进入阻塞状态.只有当信号>=0才能通过.

那么上面的代码段就容易明白了: 一直等到一个异步的网络请求结束,才继续执行NSLog(@"five sectonds");,也是就其他的逻辑

Barrier

相比上面两种方式,Barrier知道的人相对少一些.但是Barrier用起来相对上面两种更加简单.

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_queue_t queue = dispatch_queue_create("cc.imguiqing", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"task 1 on %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"task 2 on %@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier ==========");
});
dispatch_async(queue, ^{
NSLog(@"task 3 on %@",[NSThread currentThread]);
});

上面的代码,task 1和task 2会并发执行,然后执行barrier,最后是task 3,用图来说明:
20160924147468717647516.jpg

这个 barrier就相当于一个栅栏,将不同的任务区分开来.从代码中也不难看出,这个barrier函数不需要依赖其它的变量,没有侵入性.所以非常好用.和Group也是非常好搭配.例如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
dispatch_queue_t queue = dispatch_queue_create("cc.imguiqing", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 500000000; i++) {
}
NSLog(@"task 1 on %@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"======");
});
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 500000000; i++) {
}
NSLog(@"task 2 on %@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"======");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task 3 on %@",[NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"all task done");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"since all done , I move on");

能保证 task 1 2 3顺序执行,同时,由于使用了Group,也能知道执行结束的时机. 但是仅仅是为了说明问题,如果要顺序执行,那么还是使用GCD中同步队列更加合适.

注意点: 这个barrier函数只能用于并发队列,且不能是global queue.

Xcode 8 适配

发表于 2016-09-23   |   分类于 Xcode&环境配置   |  

相信很多小伙伴都升级了Xcode 8 ,但是发现很多恶心的地方.下面是自己这几天的积累,解决了一部分问题.希望能帮到大家.

注释快捷键失效

我们常用的cmd+/失效了了.

解决方案

终端输入:

1
sudo /usr/libexec/xpccachectl

然后重启mac

去掉多余打印

当你开开心心的想去控制台看Log的时候,发现这样:
2016092374983截图 2016-09-23 14时52分35秒.jpg

解决方法

到Target中添加如下键值对:
OS_ACTIVITY_MODE disable
20160923147461373922375.jpg

插件失效

每次升级,都会面临插件失效的情况. 以前比较简单的可以通过修改插件plist的方法来完成修复.
1.打开终端,输入以下代码获取到DVTPlugInCompatibilityUUID

1
defaults read /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID

2.然后输入如下命令 【最后一项是获取到的DVTPlugInCompatibilityUUID】

1
find ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins -name Info.plist -maxdepth 3 | xargs -I{} defaults write {} DVTPlugInCompatibilityUUIDs -array-add 9F75337B-21B4-4ADC-B558-F9CADF7073A7

来源网址
但是Xcode 8 却不行了,甚至明确说支持Xcode8的插件也不正常工作.这是因为Xcode8要求code sign

解决方法

因为我一直很依赖xvim这个插件,所以一直关注等着更新.今天意外发现有个适配Xcode8的说明:

1.打开Keychain Access,在左边栏中选择login这个条目
2.选择Create a Certificate
20160923147461457298237.jpg
3.输入名字,然后选择Code Signing这个类型
20160923147461461158577.jpg
4.退出Xode,然后终端中输入:

1
$ sudo codesign -f -s XcodeSigner /Applications/Xcode.app

具体Xcode路径看自己放哪.
5.然后可以依靠旧的方法去使用,或者去获取最新版本.

来源网址

切换Swift版本

目前Xcode8中支持Swift3.0 ,但是不幸的是,很多三方库还不支持. 我们可以通过配置,切换为Swift的2.3版本.

修改配置

配置如下图,设置为NO表示使用 Swift 3.0. YES表示使用Swift2.3
20160923147461514664286.jpg

一份自己满意的ClangFormat配置

发表于 2016-08-18   |   分类于 环境、配置相关   |  

对于一个团队来说,有共同的代码格式规范是非常重要的.但是,却不能保证每个细节,使用代码格式化工具可以极大的提高效率.下面是自己积累出来的一份配置,每一行都有注释,可以自己比对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#基于那个配置文件
BasedOnStyle: LLVM
#指针的*的挨着哪边
PointerAlignment: Right
#缩进宽度
IndentWidth: 4
# 连续的空行保留几行
MaxEmptyLinesToKeep: 1
# 在 @property 后面添加空格, \@property (readonly) 而不是 \@property(readonly).
ObjCSpaceAfterProperty: true
# OC block后面的缩进
ObjCBlockIndentWidth: 4
# 是否允许短方法单行
AllowShortFunctionsOnASingleLine: true
# 是否允许短if单行 If true, if (a) return; 可以放到同一行
AllowShortIfStatementsOnASingleLine: true
#注释对齐
AlignTrailingComments: true
# 换行的时候对齐操作符
#AlignOperands: true
# 中括号两边空格 []
SpacesInSquareBrackets: true
# 小括号两边添加空格
SpacesInParentheses : true
#多行声明语句按照=对齐
AlignConsecutiveDeclarations: true
#连续的赋值语句以 = 为中心对齐
AlignConsecutiveAssignments: true
#等号两边的空格
SpaceBeforeAssignmentOperators: true
# 容器类的空格 例如 OC的字典
SpacesInContainerLiterals: true
#缩进
IndentWrappedFunctionNames: true
#在block从空行开始
KeepEmptyLinesAtTheStartOfBlocks: true
#在构造函数初始化时按逗号断行,并以冒号对齐
BreakConstructorInitializersBeforeComma: true
#函数参数换行
AllowAllParametersOfDeclarationOnNextLine: true
#括号后添加空格
SpaceAfterCStyleCast: true
#tab键盘的宽度
TabWidth: 4
UseTab: Never

使用方法

  1. 安装Clang-Format Xcode插件, 地址
  2. 在用户根目录下创建 .clang-format文件,将上面配置拷贝进去
  3. 在Xocde中的选中以下菜单即可:
    82417QQ20160818-1@2x.png
  4. 可以设置在保存文件的时候自动格式化,也可以自己配置
    67391QQ20160818-2@2x.png

Vim实践Tips(六)

发表于 2016-08-03   |   分类于 其他技术   |  

Tip 27 遇见Vim的命令行

当我们按下:,Vim就切换到了底行模式.这个模式和shell的命令行有些类似.我们只要输入点命令,然后按下<CR>就能执行.使用<Esc>可以从底行模式切换到命令行模式.

由于一些历史原因,我们执行的命令叫做 Ex Commands .当我们按下/之后,或者使用<C-r>=访问表达式寄存器的时候,也都会进入底行模式.这章中提到的一些小技巧也都适用于不同的情况,但是在这个章节,我们先讨论Ex commands

1
2
3
4
5
6
7
8
9
10
11
12
  命令                     效果
:[range]delete[x] 删除特定的行(放到寄存器x中)
:[range]yank[x] 复制特定的行(放到寄存器x中)
:[line]put[x] 把寄存器x中的内容放到特定的行后面
:[range]copy{address} 复制特定的行到通过地址指定的行的下面
:[range]move{address} 移动特定的行到通过地址指定的行的下面
:[range]join 连接指定的行
:[range]normal {commands} 对特定的范围执行命令模式下的命令
:[range]substitute/{pattern}
/{string}/{flags}
在特定的行里面,把符合条件的匹配使用字符串替换掉.
:[range]global/{pattern}/[cmd] 在所有匹配到pattern的行中,执行Ex命令

我们可以Ex命令读写文件(:read和:write),或者使用:tabnew命令创建Tab页,或者使用:split命令创建窗口.

Tip 28 在一到多个连续行中执行命令

许多Ex命令都可以接受一个{range},我们可以通过行号,标记,或者是一个Pattern来提供范围.
假设有下面一段代码:

1
2
3
4
<html>
<head><title>Practical Vim</title></head>
<body><h1>Practical Vim</h1></body>
</html>

为了说明,我们使用:print命令,这个命令可以简单的通过这个命令将特定的行打印到Vim的底行之下.
这个命令没有什么特定的功能.但是可以用来做说明.当然,你也可以通过:delete,:join,:substitude,:normal这几个命令来测试.通过测试,你就可以知道怎么使用Ex命令了.

使用行号作为地址

如果我们输入一个仅由数字组成的Ex命令.Vim就会把这个命令当做地址,然后将光标移动到那一行.所以,我们就能通过下面的这个命令跳转到文件顶部:

1
2
:1
:print

你也可以使用:p来打印,这个是:print的缩写形式.你也可以将两个命令合并到一起.

1
:3p

这个命令移动到第3行,然后打印该行的内容.我们只是通过:p命令来说明问题,下面你可以试试这个命令.

1
:3d

这个命令跳转到第3行,然后执行删除命令.它相当于在命令模式下执行:3Gdd.这个命令比命令行模式下的要快点.

通过地址指定一个范围

1
:2,5p

这个命令可以打印从第2行到第5行,并且最后光标停留在第5行.通常来说,范围可以表现为这个形式:

1
:{start},{end}

注意,这个start和end都是地址,目前我们知道的地址是行号.在后面的我们将见到通过匹配和标记指定的地址.

.可以用来表达为当前行,所以,我们可以通过下面的命令打印从当前行到文件末尾

1
2
:2
:.,$p

%这个符号也有特别的意义,它代表了当前文件中的所有行.

1
:%p

它相当于:1,$p这个命令.

通过在可视化模式下的选择指定范围

首先通过命令2G跳转到第2行,然后VG可以选择从2行到文件结尾.此时,我们按下:.这时候,底行上出现了:'<,'>,看起来有点怪怪的,但是你可以简单的认为,它就代表了可视化模式的选区.然后,我们就能指定Ex命令.例如:

1
:'<,'>p

通过匹配指定范围

1
:/<html>/,/<\/html>/p

打印结果

1
2
3
4
<html>
<head><title>Practical Vim</title></head>
<body><h1>Practical Vim</h1></body>
</html>

看起来有点复杂,但是,它还是遵循了:{start},{end}的形式.这个start和刚刚的/<html>/相对应.而end和/<\/html>/相对应.

在这个例子中,我们可以通过:2,5指定范围,这个方式更简洁.而通过标签形式的方式,可以直接匹配标签,不论中间有多少行.

通过偏移来指定地址

假设我们有需求:打印所有的<html>标签内的内容,但是不打印包含<html>的行.
我们可以指定偏移:

1
:/<html>/+1,/<\/html>/-1p

打印结果

1
2
<head><title>Practical Vim</title></head>
<body><h1>Practical Vim</h1></body>

这个语法的一般形式:

1
{address}+n

如果n省略,那么它默认是1.这个address可以是行号或者是标记或者是模式匹配
那么我们可以实现,从当前行打印后面3行

1
:.,.+3p

讨论

指定范围的语法非常灵活.我们可以混合使用行号 标记 模式匹配,也可以通过应用偏移来修改范围.
下面的表格可以作为一个参考:

1
2
3
4
5
6
7
8
9
符号      代表的地址
1 文件的第一行
$ 文件的最后一行
. 光标所在行
'm 标记m所在的行
'< 可视化选区的开始
'> 可视化选区的结尾
% 文件所有行 ( :1,$ 的快捷方式)
0 第0行

第0行并不真实存在,但是在特定的情况下,这个地址还是很有用的.例如,在:copy {address}和:move {address}中,我们想复制或者移动从文件开头的范围.在后面的两个Tip中,我们将看到具体的例子.

当我们指定一个[range],它总是表示一些连续的行.我们也能通过模式匹配应用Ex命令到一系列非连续的行,通过:global这个命令就能做到.

Tip 29 通过t和m复制和移动行

:copy命令(快捷方式是:t)让我们可以复制1到多行,而:move命令可以移动1到多行.

为了说明,我们使用下面的代码:

1
2
3
4
5
6
Shopping list
Hardware Store
Buy new hammer
Beauty Parlor
Buy nail polish remover
Buy nails

通过:t命令复制行

我们的购物清单还不完整.假设我们也需要在Hardware Store买nails.为了修正这个清单,我们重用文件的最后一行.我们可以简单的使用:copy命令完成

1
2
3
4
5
6
7
Shopping list
Hardware Store
Buy nails
Buy new hammer
Beauty Parlor
Buy nail polish remover
Buy nails

上述命令是通过:160copy.完成的(在我编写这个文档的时候,Buy nails 是第160行`)

copy命令的一般格式是:

1
:[range]copy {address}

在我们的例子中,[range]是160行.{address}在这个例子中是.,代表了当前行.所以这个:160copy.的意义是:复制160行,并且放置到当前行的下面.

我们也可以简写:copy成:co,也可以更简洁的写成:t.为了辅助记忆,你可以理解t为:copy To.下面的表格中列举了一些:t的用法:

1
2
3
4
5
6
命令             效果
:6t. 复制第6行到当前行的下面
:t6 复制当前行到第6行下面
:t. 复制当前行(相当于命令模式下的yyp)
:t$ 复制当前行到文件结尾
:'<,'>t0 复制可视化选区到文件开头

注意: :t.复制当前行.作为选择,我们也可以通过命令模式下的yyp实现同样的效果.一个值得注意的区别就是yyp使用寄存器,而:t.不这样.有时候,为了避免覆盖默认寄存器的内容,我使用:t.复制当前行.

在这个例子中,我们使用yyp的变体复制我们想要的行,但是,它需要一些额外的移动.我们需要先跳转到到我们想复制的行6G,复制yy,回到我们开始的地方<C-o>,然后p粘贴.所以,在这种比较远的复制操作,:t这种命令更高效.

通过:m命令移动行

语法是:

1
:[range]move {address}

我们可以简写成:m
如果已经选择好了分区.我们可以直接运行命令:'<,'>m$,作为选择,我们也可以使用dGp命令.这个命令可以分解为:d删除,同时复制到寄存器,G跳转到文件结尾,p粘贴内容.

重复上个Ex命令: @:

Tip 30 在一个范围上使用命令模式的命令

在Tip 2 中,我们在每行的后面追加一个分号,当时我们使用的.命令做的重复.当时只有几行,那么做可以,但是如果有2000行需要追加呢? 显然,用j.的方式就不靠谱了.

使用normal命令可以对一个范围使用命令模式下的命令.

我们使用下面的代码来说明问题:

1
2
3
4
5
var foo = 1
var bar = 'a'
var baz = 'z'
var foobar = foo + bar
var foobarbaz = foo + bar + baz

  1. A;<Esc> 跳转到行结尾,输入;,退出插入模式
  2. jVG 选中除了第一行的的后面所有行
  3. '<,'>normal. normal执行命令模式,.重复
1
2
3
4
5
var foo = 1;
var bar = 'a';
var baz = 'z';
var foobar = foo + bar;
var foobarbaz = foo + bar + baz;

使用了上面的步骤,不管是5行还是5000行,都能正常工作.
其实不仅仅是.这个命令,使用了normal这个标记之后,我们可以执行任何命令模式下命令.

在这个例子中,我们可以通过一个命令完成操作.

1
:%normal A;

%这个命令表示整个文件范围的行.所以上面的命令的意思是:在文件的每一行后面都追加一个;,而且Vim会在完成之后,自动切换到命令模式

既然可以通过:normal这个命令使用所有的命令模式命令.那么下面这个命令也很同意理解.

1
:%normal i//

在所有行的开始加入//.

Vim实践Tips(五)

发表于 2016-08-03   |   分类于 其他技术   |  

Tip 20 体验可视化模式

在可视化模式下,很多命令的作用和它们在命令模式时相同.我们仍旧可以使用hjkl来移动光标.使用f{char}跳转到当前行的某个字符.然后使用;重复跳转,或者使用,反向跳转.我们甚至可以使用查找命令(包括n和N)跳转到匹配的地方.在可视化模式下,移动光标,将改变选择的范围.

虽然大多数命令一样,但是也有一些细微的差别,例如c这个命令.在命令模式下.我们使用c{motion}删除内容并进入插入模式.而在可视化模式下,在选中了部分内容时,只需要一个c就能进入插入模式,删除的内容是高亮部分的内容.这时候,这个c的作用更直观了.

下面看一个例子:

1
March is a month

如果要把March改为April,假设我们已经把光标放在了March这个单词上的任意位置了.那么可以通过viw选择整个单词.这时候,我们不能直接输入April,因为A会触发Vim命令.然后只会输入剩下的pril.所以,我们使用c这个命令,删除当前选择并进入插入模式.然后输入April.

遇见选择模式

在一般的编辑器中,当我们选中想要删除的文字时候,只要输入点内容,就能覆盖原来的文本,但是可视化模式没有遵循这个惯例.而选择模式是这样的.

我们可以通过<C-g>切换可视化模式和选择模式.唯一可以看到的区别就是在屏幕的底部,可视化模式是VISUAL而选择模式是SELECT.当在选择模式下输入任意可打印的字符时候,将替换,并自动进入插入模式.当然,这个功能也能通过在可视化模式下,按c来实现.Vim做这个东西的作用,应该为了更符合用户的习惯.

Tip21 定义可视化选区

可视化模式有三个不同的子模式,他们用来处理不同的可视化模式.

在字符可视化模式(character-wise Visual mode),我们可以以字符为单位选取内容.它可以是一个字符,也可以是多行.它适合于对独立的单词或者段落.
在行可视化模式(line-wise Visual mode),处理的单位是行
在块可视化模式(block-wise Visual mode),我们可以选择柱形区域

这里只是粗略一说,后面有更详细的内容.

进入可视化模式

使用v键,可以进入可视化模式.按v键,可以从命令模式,进入字符可视化模式.通过V(Shift-v),可以进入行可视化模式.通过<C-v>可以进入块可视化模式.
下面是简单的一个列举:

命令 作用
v 进入字符可视化模式
V 进入行可视化模式

进入块可视化模式
gv 重新选择最后一次的选区

gv是个很好用的快捷命令.无论是什么可视化模式,gv都能应对自如.唯一可能有点问题的是:你已经删除了最后一次选区的内容.

在可视化模式中切换

下面是命令参考表
命令 作用

切换到命令模式

切换到命令模式
v 在命令模式和字符可视化模式切换
V 在命令模式和行可视化模式切换

在命令模式和字符块视化模式切换
o 把光标在选区的两端切换.

切换选区的可变端

默认选择选区的时候,一端是固定的,另一端是可以通过各种移动命令进行移动的.可以通过o这个命令切换移动端.

补充小知识:

e移动到下个单词的结尾.

Tip 22 重复行可视化模式命令

当我们在可视化模式下执行一个命令之后,我们就会进入命令模式.在可视化模式下选中的文字将取消选中.那么如果我们想对刚刚选中的文字重新执行一个可视化模式的命令该怎么办?

假设我们有下面一段python:

1
2
3
4
5
6
def fib(n):
a, b = 0, 1
while a < n:
print a,
a, b = b, a+b
fib(42)

准备工作

为了让<和>这两个命令正常工作,我们应该做下面的设置:

1
:set shiftwidth=4 softtabstop=4 expandtab

缩进一次,然后重复

对于刚刚的python代码,缩进有问题,我们应该在可视化模式下选择.然后使用>这个命令缩进.但是缩进要超过两次.而执行一次刚刚的命令之后,我们就会进入命令模式.

有个解决方案是通过gv命令,然后再次执行缩进命令.但是如果你对Vim已经有了感觉,那你就应该知道这个方式是不好的.

当我们需要重复,.这个命令是一个非常好的选择.

1
2
3
4
5
6
def fib(n):
a, b = 0, 1
while a < n:
print a,
a, b = b, a+b
fib(42)

上面是通过 Vj选中两行,然后>.完成的

如果你喜欢计算,那么你可能更喜欢2>这个命令.但是我更喜欢使用.这个命令.因为这个命令可以给我可视化的反馈.我也能享受到自己键盘的反馈的乐趣.之前我们也讨论过计数和重复的取舍.你可以重新看看.

Tip 23 在可能的时候,在可视化模式下,使用操作符

可视化模式更直观,但是它有个缺点:.这个命令不能完全发挥作用.我们可以通过命令模式下的命令来搞定这个缺点.

假设我们有下面一段文字.我们想把下面链接设置为大写

1
2
3
<a href="#">one</a>
<a href="#">two</a>
<a href="#">three</a>

我们可以通过vit命令选中标签内部的文字,它理解为:可视化选中标签之间的文字.it命令是一种特别的{motion}命令.我们将在Tip 51进行更深入讨论.

使用可视化操作符号

在可视化模式下,我们选择一段文字,然后对它进行操作.在这个案例下,我们可以使用U命令来让选中的文字变成大写.
完成了第一行之后,如果想对第二行,第三行进行操作.应该怎么办呢?.命令可以吗?

1
2
3
<a href="#">ONE</a>   "vit
<a href="#">TWO</a> "j.
<a href="#">THRee</a> "j.

可以看到,.命令仅仅重复了三个字符.造成了最后一行的样子.这并不是我们想要的结果.

使用命令模式下的操作符

U这个可视化模式的操作符对应了一个命令模式下的版本:gU{motion},但是在语义上有很大不同.
在可视化模式命令U的案例中,我们做了两件事:

  1. vit选中文本
  2. U操作文本变成大写
    在命令模式的案例中,我们弄了一个命令,这个命令由:gU操作和it作为{motion}.
1
2
3
<a href="#">ONE</a> "gUit
<a href="#">TWO</a> "j.
<a href="#">THREE</a> "j.

确实,可视化模式有局限性.但是它也很有用处.因为并不是所有编辑工作都需要重复.所以可视化模式是非常适合单次的编辑.

Tip 24 在块可视化模式下编辑表格化数据

所有编辑器都可以以行为单位编辑数据.在Vim中,块可视化模式,提供给我们以列为单位编辑数据.

假设有下面的文本

1
2
3
4
Chapter       Page
Normal mode 15
Insert mode 31
Visual mode 44

我们要加点东西,让上面的文字看起来更像表格.

1
2
3
4
5
Chapter     | Page
==================
Normal mode | 15
Insert mode | 31
Visual mode | 44

上面的实现步骤是:

  1. <C-v>3j进入块可视化模式,并向下选择3行
  2. r|将选中范围替换为 |
  3. 同理,复制一行,然后替换为=

Tip 25 改变多列文本

假设有下面一段css代码

1
2
3
li.one   a{ background-image: url('/images/sprite.png'); }
li.two a{ background-image: url('/images/sprite.png'); }
li.three a{ background-image: url('/images/sprite.png'); }

假设sprite.png 已经从images文件夹移动到了components文件夹.我们需要修改三行来改变目录.这时候,我们就能使用块可视化模式了.

1
2
3
li.one   a{ background-image: url('/components/sprite.png'); }
li.two a{ background-image: url('/components/sprite.png'); }
li.three a{ background-image: url('/components/sprite.png'); }
  1. 光标定位到images这个单词的开头.
  2. <C-v>jje选中三列的images
  3. c删除单词,并进入插入模式, 输入components,然后<Esc>.

唯一一个可能造成疑惑的是,当第三步输入单词之后,只改变了第一行.但是,当按了<Esc>之后,三行都改变了.
确实Vim的这个功能有点不太人性化.但是,最终结果没有什么区别.习惯就好了

Tip 26 在参差不齐的块选区后面追加内容

假设有下面一段js代码

1
2
3
var foo = 1
var bar = 'a'
var foobar = foo + bar

这三行不是一样长的,假设我们想为每一行的后面添加一个, 在Tip2我们通过.完成了这个操作.其实通过块可视化区域也能完成这个任务.

1
2
3
var foo = 1;
var bar = 'a';
var foobar = foo + bar;
  1. <C-v>jj$选中全部
  2. A;移动到结尾,进入插入模式,并输入
  3. <Esc>完成

细心可以看出,这个操作和Tip 25差不多意思.
主要是通过$改变了矩形的结尾.

Vim关于 i 和 a 的惯例

Vim有很多从命令模式切换到插入模式的按键.i和a都能完成这个操作.i在光标前进入插入模式,a在光标后进入插入模式.I和A功能类似,是在行开头和行结尾进入插入模式.

在块可视化模式中,A和I的功能和在命令模式下类似.那么a和i呢?

在可视化模式和操作符悬停模式下,a和i有不同的意义.这个在Tip 51会有更深入的讨论.现在记得,使用A和I替代

Vim实践Tips(四)

发表于 2016-07-22   |   分类于 其他技术   |  

Tip 13 在插入模式下做修改

如果我们在插入模式下输入错误,没有必要先切换模式然后做修改.除了退格键,我们还有一些插入模式下的命令可以用.

凭借触感打字不仅仅是不看键盘.而是凭借感觉输入.当输入错误的时候,甚至他们不用看到屏幕上的错误就知道自己输入错误了.因为他们指头已经知道自己刚刚按错地方了.

当输入的错误在单词结尾的时候,退格键是有效的纠正方式,但是当错误出现在单词开头的时候呢?

出色的打字员推荐这样一个方式: 当出现错误的时候,删除整个单词,然后重新输入.如果你一分钟能输入60个单词,那么重新输入总会比你单个删除字符更快.当然,如果你打字慢,就可以当成一个练习打字速度的练习.多输入正确的单词,有利于你养成输入的感觉.

当然,你也可以切换到命令模式.把光标跳转到单词开头,修改错误,然后A跳转到句子结尾.这个行为肯定是呀看大于1秒了.重要的是,这个不会提高你对输入的感觉

在插入模式下,就像你预料到的,它删除光标前的字符.下面的快捷键也是挺好的:

  • <C-h> 删除前面的一个字符,等于退格键
  • <C-w> 删除前面的一个单词
  • <C-u> 删除到本行开始

上面的命令不是插入模式特有,甚至不是Vim特有的,你也可以在Bash Shell中使用它们.

Tip14 回到命令模式

插入模式是特定用于文本输入的模式.命令模式才是才是我们花费时间最多的模式,它的名字是normal mode,这个名字说明了一切.所以,会在这两者之前切换就很重要了.

经典的返回命令模式的方式是按<Esc>键,但是对于大多数键盘,这个键有点远.所以,作为备选方案,你可以按Ctrl+[,这个组合件和<Esc>等价.

  • <Esc> 切换到命令模式
  • <C-[> 切换到命令模
  • <C-o> 在插入和命令模式下切换

遇见插入命令模式

插入命令模式是命令模式的一个特别版本.我们可以出发一个单独命令,之后我们就会进入插入模式.在插入模式中,我们可以通过<C-o>切换到命令模式.

我们会通过zz命令将当前行滚动到屏幕中间.而我经常做的是在插入模式下<C-o>zz,这样会把当前行滚动到屏幕中间,并且可以继续输入.

Tip 15 在不离开插入模式下前提下,从寄存器粘贴

Vim的复制粘贴命令多数情况下是在命令模式下执行的.但是,有时候我们想在插入模式进行.

映射CapsLock键.

对于Vim的使用者来说,CapsLock键简直就是个威胁.例如本来j是移动.可是当CapsLock之后,它成为了J是联合两行.很多Vimer都把CapsLock映射成了其他键位.例如<Esc>或者Ctrl.我推荐你对它进行映射.

下面一段文字

1
2
Practical Vim, by Drew Neil
Read Drew Neil's

我们想通过插入本书书名的方式完成第二行.本书的名字已经在第一行了.

1
2
Practical Vim, by Drew Neil  " yt,
Read Drew Neil's Practical Vim "按键顺序jA <C-r>0.<ESc>

yt,复制,之前的单词到寄存器中
<C-r>0 粘贴寄存器中的内容到当前光标我再的位置.

通用的格式是<C-r>{register}这个register是我们想要插入的寄存器.

<C-r>{register} 这个命令对于少数的字符来说,是比较好的.但是如果粘贴大量的文本,你就会感觉到了延迟.因为这个命令从寄存器中粘贴,相当于一个一个的字符进行输入.如果textwidth或者autoindent选项开启了.那么你可能会得到一些不想要的断行或者额外的缩进.

<C-r><C-p>{register}命令更智能一些.它真实的插入文字,并且能够修正缩进问题.所以,如果要从寄存器中粘贴多行文本.我推荐你使用这个命令.

Tip16 在输入处进行计算

表达式寄存器允许我们执行计算并插入到我们的文档中.

多数Vim的寄存器既可以包含字符组成的字符串,也可以包含一整行文字.我们通过删除或者复制命令设置寄存器的内容.

表达式寄存器有所不同.它可以评估一段Vim脚本代码,然后返回结果.所以,我们可以把它当做一个计算器.它的结果可以和其它的文本寄存器一样使用.

在插入模式下输入<C-r>=,这样,在底行下会有提示.然后就能直接输入计算表达式了.当计算完成.<CR>一下,表达式结果就能直接插入到文档中了.

假设有下面的文本

1
6 chairs, each costing $35, totals $

使用寄存器:

1
6 chairs, each costing $35, totals $210   "A<C-r>6*35<CR>

Tip17 通过字符编码插入字符

Vim可以通过字符编码插入任意字符,通过这个特性,我们可以输入键盘上找不到的字符

在插入模式下,输入<C-v>{code}即可,code是字符的编码.Vim要求code是三位数.所以,如果我们要输入A这个字符,它的字符编码是65,那么我们需要输入<C-v>065.

但是如果我们想要插入大于三位的字符编码怎么办呢?我们可以告诉Vim,要使用十六进制输入<C-v>u{1234}.例如,我们要插入一个倒着的问号.(字符码是00bf) 那么我们输入<C-v>u00bf

使用ga命令可以查看文档中字符的编码.命令模式下将光标停留在想要查看的字符上,然后ga,文档的底部将会显示它的信息,包括字符编码,十六进制和十进制表示.

另外一种场景,如果<C-v命令后面跟了非数字键,那么它插入那个键的文字表示,例如我按下<C-v>退格键,那么输入的将是<BS>

Tip18 通过连字插入字符.

通过字符编码插入字符虽好,但是字符编码难记.通过连字插入会简单一些

在插入模式下,输入<C-k>{char1}{char2}即可

连字通常是有一定的意义的.例如常见的1/2,1/3,我们可以输入<C-k>12,<C-k>13得到.倒置的问号可以通过<C-k>?I得到.

更多的连字可以通过:digraphs查看.

使用替换模式覆盖文字

有下面一段文字

1
2
Typing in Insert mode extends the line. But in Replace mode
the line length doesn't change.

我们想把两句话合并成一句话.意味着:

  1. 将.换成,
  2. 把B改成b
    1
    2
    Typing in Insert mode extends the line, but in Replace mode
    the line length doesn't change.

我们使用f.定位光标到.的位置.然后R进入替换模式.然后输入, b替换原来的字符.完成替换之后,我们可以按<Esc>返回命令模式.

使用可视化替换模式

一些字符会给替换模式带来麻烦,例如<Tab>产生的缩进.如果要替换,需要输入很多字符(根据你对tab的设置),这时候,使用gR命令是更好的选择.

123…13
桂庆

桂庆

Kenny 肉桂的主页 记录自己的进步

75 日志
17 分类
23 标签
RSS
微博
© 2013 - 2017 桂庆
由 Hexo 强力驱动
主题 - NexT.Mist