iOS的事件有好几种:Touch Events(触摸事件)、Motion Events(运动事件,比如重力感应和摇一摇等)、Remote Events(远程事件,比如用耳机上得按键来控制手机),其中最常用的就是Touch Events了,基本存在于每个app的每个地方,本文主要讲Touch Events。

响应链

在app中,所有的视图都是按照一定的结构组织起来的,即树状层次结构,每个view都有自己的superView,包括controller的topmost view(controller的self.view)。当一个view被add到superView上的时候,他的nextResponder属性就会被指向它的superView,当controller被初始化的时候,self.view(topmost view)的nextResponder会被指向所在的controller,而controller的nextResponder会被指向self.view的superView,这样,整个app就通过nextResponder串成了一条链,也就是我们所说的响应链。

当事件触发时,如果第一响应者没有响应,那么会根据响应链,将事件传递给nextResponder,如果还是没响应, 传给下一个nextResponder,直到UIApplication。

Hit-TestingView

寻找响应事件的具体响应者,称为:Hit-Testing View。通过以下两个方法实现。

// 返回当前view中,响应事件的后代view,没有响应返回nil
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; 
// 判断当前view是否响应事件,通常就是判断point是否在self.bounds内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

每当手指接触屏幕,UIApplication接收到手指的事件之后,就会去调用UIWindow的hitTest:withEvent:,看看当前点击的点是不是在window内,如果是则继续依次调用subView的hitTest:withEvent:方法,直到找到最后需要的view。调用结束并且hit-test view确定之后,这个view和view上面依附的手势,都会和一个UITouch的对象关联起来,这个UITouch会作为事件传递的参数之一,UITouch头文件里面有一个view和gestureRecognizers的属性,就是hitTest view和它的手势。

hitTest:withEvent:工作流程示例代码

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 判断当前view能否响应事件
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    // 判断点击区域是否在当前view内
    if ([self pointInside:point withEvent:event]) {
        // 遍历子view,递归找出能响应的后代view
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        // 没有子view响应,返回self
        return self;
    }
    return nil;
}

应用

如何增大点击区域

重写 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGRect bounds = self.bounds;
    CGFloat widthDelta = MAX(0, 44.0 - bounds.size.width);
    CGFloat heightDelta = MAX(0, 44.0 - bounds.size.height);
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);    //注意这里是负数,扩大了之前的bounds的范围
    return CGRectContainsPoint(bounds, point);
}

参考资料

基于socket的sockopt是Linux中一种简单易用的内核态与用户态的通信方式。

应用层-系统调用

Linux中有两个系统调用,setsockopt和getsockopt,用于​设置、获取socket选项。它们的函数原型为:

int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);

其中s即socket_fd,套接字的文件描述符。

level指定在哪一层控制socket选项,常用的有以下三种:

  1. SOL_SOCKET(= 1),通用套接字选项;
  2. IPPROTO_IP(= 0),IP选项;
  3. IPPROTO_TCG(= 6),TCP选项;

optname为选项代号。

对于getsockopt,optval,optlen为返回的选项值的指针及长度。比如,一个字符串或结构体指针和它的长度。对于setsockopt,类似,optval,optlen为要设置的数据指针和数据长度。

如果函数执行成功,返回值为0,否则返回-1。错误代号保存在errno中。

举个例子:

int tmp = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));

以上代码设置套接字地址和端口可重用。

内核层-netfilter

Linux的netfilter提供了一组接口用于自定义socket选项。

数据结构:

struct nf_sockopt_ops
{
        struct list_head list;

        int pf; 

        /* Non-inclusive ranges: use 0/0/NULL to never get called. */
        /* setsockopt选项编号取值范围 */
        int set_optmin;
        int set_optmax;
        /* 自定义setsockopt函数, sk为套接字内核内部数据结构 */
        int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);
        int (*compat_set)(struct sock *sk, int optval,
                        void __user *user, unsigned int len);

        /* getsockopt选项编号取值范围 */
        int get_optmin;
        int get_optmax;
        /* 自定义setsockopt函数 */
        int (*get)(struct sock *sk, int optval, void __user *user, int *len);
        int (*compat_get)(struct sock *sk, int optval,
                        void __user *user, int *len);

        /* Number of users inside set() or get(). */
        unsigned int use;
        struct task_struct *cleanup_task;
};

我们只要新建一个nf_sockopt_ops结构,设置好选项取值范围,自定义set和get函数,然后注册到内核中。这样就可以在用户层调用setsockopt或getsockopt,和内核通信了。
注册卸载nf_sockopt_ops的接口为:

int nf_register_sockopt(struct nf_sockopt_ops *reg);
void nf_unregister_sockopt(struct nf_sockopt_ops *reg);

示例

示例一:互相发送字符串

内核模块代码

sockopt_kern.c

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/netfilter_ipv4.h>
#include <linux/init.h>
#include <asm/uaccess.h>

#define SOCKET_OPS_BASE 128
#define SOCKET_OPS_SET (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX (SOCKET_OPS_BASE + 1)

#define KMSG "a message from kernel"
#define KMSG_LEN sizeof("a message from kernel")

MODULE_LICENSE("GPL");

static int recv_msg(struct sock *sk, int cmd, void *user, unsigned int len)
{

int ret = 0;
printk(KERN_INFO "sockopt: recv_msg()\n");

if (cmd == SOCKET_OPS_SET)
{
char umsg[64];
int len = sizeof(char)*64;
memset(umsg, 0, len);
ret = copy_from_user(umsg, user, len);
printk("recv_msg: umsg = %s. ret = %d\n", umsg, ret);
}
return 0;
}

static int send_msg(struct sock *sk, int cmd, void *user, int *len)
{

int ret = 0;
printk(KERN_INFO "sockopt: send_msg()\n");
if (cmd == SOCKET_OPS_GET)
{
ret = copy_to_user(user, KMSG, KMSG_LEN);
printk("send_msg: umsg = %s. ret = %d. success\n", KMSG, ret);
}
return 0;
}

static struct nf_sockopt_ops test_sockops =
{
.pf = PF_INET,
.set_optmin = SOCKET_OPS_SET,
.set_optmax = SOCKET_OPS_MAX,
.set = recv_msg,
.get_optmin = SOCKET_OPS_GET,
.get_optmax = SOCKET_OPS_MAX,
.get = send_msg,
};

static int __init init_sockopt(void)
{

printk(KERN_INFO "sockopt: init_sockopt()\n");
return nf_register_sockopt(&test_sockops);
}

static void __exit fini_sockopt(void)
{

printk(KERN_INFO "sockopt: fini_sockopt()\n");
nf_unregister_sockopt(&test_sockops);
}

module_init(init_sockopt);
module_exit(fini_sockopt);

应用层代码

sockopt_usr.c

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
46
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>
#include <errno.h>

#define SOCKET_OPS_BASE 128
#define SOCKET_OPS_SET (SOCKET_OPS_BASE)
#define SOCKET_OPS_GET (SOCKET_OPS_BASE)
#define SOCKET_OPS_MAX (SOCKET_OPS_BASE + 1)

#define UMSG "a message from userspace"
#define UMSG_LEN sizeof("a message from userspace")

char kmsg[64];

int main()
{

int sockfd;
int len;
int ret;

sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if(sockfd < 0)
{
printf("can not create a socket\n");
return -1;
}

/*call function recv_msg()*/
ret = setsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_SET, UMSG, UMSG_LEN);
printf("setsockopt: ret = %d. msg = %s\n", ret, UMSG);
len = sizeof(char)*64;

/*call function send_msg()*/
ret = getsockopt(sockfd, IPPROTO_IP, SOCKET_OPS_GET, kmsg, &len);
printf("getsockopt: ret = %d. msg = %s\n", ret, kmsg);
if (ret != 0)
{
printf("getsockopt error: errno = %d, errstr = %s\n", errno, strerror(errno));
}

close(sockfd);
return 0;
}

示例二:用户层获取tcp连接对端窗口

内核模块代码

rcvwnd.c

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
46
47
48
49
50
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/tcp.h>
#include <linux/fs.h>
#include <linux/version.h>
#include <net/sock.h>
#include <linux/netfilter_ipv4.h>

#define SOCKET_OPS_RCVWND 128

int get_rcv_wnd(struct sock *sk, int cmd, void __user *user, int *len)
{
if (cmd == SOCKET_OPS_RCVWND) {
struct tcp_sock *tsk;

if (sk == NULL)
return -1;

if ((tsk = tcp_sk(sk)) == NULL)
return -1;

copy_to_user(user, &tsk->rcv_wnd, sizeof(tsk->rcv_wnd));
}
return 0;
}

static struct nf_sockopt_ops rcvwnd_sockops = {
.pf = PF_INET,
.set_optmin = 0,
.set_optmax = 0,
.set = NULL,
.get_optmin = SOCKET_OPS_RCVWND,
.get_optmax = SOCKET_OPS_RCVWND + 1,
.get = get_rcv_wnd,
};

int minit(void)
{
return nf_register_sockopt(&rcvwnd_sockops);
}

void mexit(void)
{
nf_unregister_sockopt(&rcvwnd_sockops);
}

module_init(minit);
module_exit(mexit);
MODULE_LICENSE("GPL");

应用层用法

建立连接后,对tcp连接的文件描述符sock_fd调用getsockopt。

u32 rcvwnd = 0;
int len = 0;
getsockopt(sock_fd, IPPROTO, SOCKET_OPS_RCVWND, &rcvwnd, &len); 

使用ssh登录服务器时,每次都输入帐号密码时很麻烦的。可以通过使用密钥的方式登录,不用输帐号密码也可以登录。

首先生成密钥, 如果已有密钥文件(~/.ssh/id_rsa)则不用执行

ssh-keygen -t rsa

在客户端执行

scp ~/.ssh/id_rsa.pub remote:

在服务端执行

cd ~/.ssh
cat ~/id_rsa.pub >> authorized_keys

也可以将上面两步合并为一步,在客户端执行

cat ~/.ssh/id_rsa.pub | ssh root@remote 'cat >> .ssh/authorized_keys'

将公钥放到服务器后,下一次输入ssh root@remote就能直接登录了。

  • atomic 默认关键字,表示属性是线程同步的,多线程访问时保证正确性。在移动端一般不使用,性能差。
  • nonatomic 非线程同步。
  • readwrite 默认关键字,可读可写。
  • readonly 表示属性只能读。有 getter 没有 setter。
  • writeonly 只能写不能读。
  • copy 拷贝一个新的对象,新对象的引用计数+1,原对象不用。默认会在 setter 中调用 copy 方法执行。不能对可变类型使用。
  • retain 对象的引用计数+1,arc 下不使用,用 strong 代替。
  • strong 强引用,引用计数+1,释放时-1,引用计数为0时释放对象
  • assign weak 弱引用,不希望持有对象时使用,如代理,UI 控件。对象释放时,weak 会自动变为 nil,assign 不会。
  • nonnull, nullable, null_resettable 与 swift 中的?和!类似。nonnull 表示属性不可为 nil,必须有值。nullable 表示可以为 nil。null_resettable 表示 setter 是 nullable, getter 是 nonnull。

最近想在 centOS 运行 selenium 抓数据,但是装图形界面太占资源了,于是就找了下无界面运行 selenium 的方法。

Google 之后找到了 Xvfb。Xvfb 是什么呢,他的名称是 virtual framebuffer X server for X Version 11, Xvfb 新建虚拟的X窗口,并且不会把图像输出到屏幕上,也就是说,就算你的电脑沒有启动 Xwindow , 你仍然可以执行任何图形程序。

1
# 安装 Xvfb
yum install xorg-x11-server-Xvfb

# 安装 firefox 和 selenium
yum install firefox
pip install selenium

# 运行 Xvfb
Xvfb :0 -screen 0 800x600x24 >> /tmp/Xvfb.out 2>&1 &
export DISPLAY=:0

安装设置好 Xvfb 后,只要像在图形界面上一样使用 selenium 就可以了。

示例代码 (python)

1
2
3
4
5
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('http://www.baidu.com')
print browser.title
browser.quit()

安装路径

/var/opt/gitlab/

gitlab 自带 nginx, postgresql, redis,如果需要修改这些组件的配置,要到安装路径下相应子目录配置。

仓库路径

/var/opt/gitlab/git-data/repositories/

重启命令

gitlab-ctl restart

重新配置

# 修改配置文件,重新配置 gitlab
vim /etc/gitlab/gitlab.rb
gitlab-ctl reconfigure

gitlab.rb 里包含了域名,端口,邮箱等配置,修改配置请使用以上命令。/var/opt/gitlab下的 gitlab-rails 等目录也可以修改配置,但是执行 gitlab-ctl reconfigure 命令后会被覆盖。

某些配置不正确或者目录文件权限不正确会导致运行不正常,这个时候也可以重新配置 gitlab。

备份与恢复

备份

gitlab-rake gitlab:backup:create

默认备份路径为/var/opt/gitlab/backups。
文件名格式如1461464351_gitlab_backup.tar

恢复

# 从1393513186编号备份中恢复
gitlab-rake gitlab:backup:restore BACKUP=1393513186

迁移

迁移如同备份与恢复的步骤一样, 只需要将老服务器/var/opt/gitlab/backups目录下的备份文件拷贝到新服务器上的/var/opt/gitlab/backups即可(如果你没修改过默认备份目录的话). 但是需要注意的是新服务器上的Gitlab的版本必须与创建备份时的Gitlab版本号相同. 比如新服务器安装的是最新的7.60版本的Gitlab, 那么迁移之前, 最好将老服务器的Gitlab 升级为7.60在进行备份.

bash 的 date 命令用于显示时间,以下是使用 date 显示各种格式时间的参数

Format/result Command Output
YY-MM-DD_hh:mm:ss date +%F_%T 2013-05-17_10:16:09
YYMMDD_hhmmss date +%Y%m%d_%H%M%S 20130517_101609
YYMMDD_hhmmss (UTC version) date –utc +%Y%m%d_%H%M%SZ 20130517_011609Z
YYMMDD_hhmmss (with local TZ) date +%Y%m%d_%H%M%S%Z 20130517_101609JST
YYMMSShhmmss date +%Y%m%d%H%M%S 20130517101609
YYMMSShhmmssnnnnnnnnn date +%Y%m%d%H%M%S%N 20130517101609418928482
Seconds since UNIX epoch: date +%s 1368753369
Nanoseconds only: date +%N 427187053
Nanoseconds since UNIX epoch: date +%s%N 1368753369431083605
ISO8601 UTC timestamp date –utc +%FT%TZ 2013-05-17T01:16:09Z
ISO8601 Local TZ timestamp date +%FT%T%Z 2013-05-17T10:16:09JST

问题的产生

在开发过程中有时会遇到这样的需求:并发执行一组任务,任务全部结束后,对执行结果进行某些操作。

比如类似下面这样。

1
2
3
4
for (id obj in array) {
[self doSomethingIntensiveWith:obj];
}
[self doSomethingWithArray:array];

直接使用以上代码会导致阻塞,等待任务组全部都执行完才执行 doSomethingWithArray: 。

简单地加入 dispatch_async 也是不行的,这样 doSomethingWithArray: 不能在正确的时机运行。

不使用 dispatch_group 的解决方案

在知道 dispatch_group 之前 ,我的解决方法是加入一个原子计数器,等所有任务都执行完了,再执行结果列表的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
__block volatile int32_t remainCount = (int32_t)array.count;
void (^checkRemainCountBlock)(void) = ^(void) {
if (remainCount == 0) {
[self doSomeThingWithArray:array];
}
OSAtomicDecrement32Barrier(&remainCount);
};
checkRemainCountBlock();

for (id obj in array) {
[self doSomethingIntensiveWith:obj];
checkRemainCountBlock();
}

使用 dispatch_group 的解决方案

可以看到,上面的方法不太优雅。事实上,Apple 为我们准备了专门用来处理这种问题的 dispatch_group。

一个 dispatch group 可以用来将多个 block 组成一组以监测这些 block 全部完成或者等待全部完成时发出的消息。所以我们现在可以重写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for(id obj in array) {
dispatch_group_async(group, queue, ^{
[self doSomethingIntensiveWith:obj];
});
}

/*
* 等group里的task都执行完后执行notify方法里的内容,
* 相当于把wait方法及之后要执行的代码合到一起了
*/

dispatch_group_notify(group, queue, ^{
[self doSomethingWithArray:array];
});

在不能使用 dispatch_group_async 的情况下,使用 dispatch_group_enter/dispatch_group_leave。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for(id obj in array) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
[self doSomethingIntensiveWith:obj];
dispatch_group_leave(group);
});
}

/*
* 等group里的task都执行完后执行notify方法里的内容,
* 相当于把wait方法及之后要执行的代码合到一起了
*/

dispatch_group_notify(group, queue, ^{
[self doSomethingWithArray:array];
});

dispatch_group 相关方法

  • dispatch_group_create
    创建一个调度任务组

  • dispatch_group_async
    把一个任务异步提交到任务组里

  • dispatch_group_enter/dispatch_group_leave
    这两个方法显示的讲任务组中的任务未执行完毕的任务数目加减1,这种方式用在不使用 dispatch_group_async 提交任务的情况下。注意:这两个函数要配合使用,有 enter 要有 leave,这样才能保证功能完整实现。也可以用这对函数来让一个闭包关联多个 group

  • dispatch_group_notify
    用来监听任务组事件的执行完毕

  • dispatch_group_wait
    设置等待时间,在等待时间结束后,如果还没有执行完任务组,则返回。返回0代表执行成功,非0则执行失败