Zach的博客

这个人很懒,都不知道说些什么 :(


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签
Zach的博客

Hexo博客同步

| 阅读次数

Hexo在多设备间同步

最近使用的电脑在Mac和实验室的电脑之间不停的转换,就出现了想写博客,可是Mac不在身边没法写的情况,所以就折腾了一下如何让Hexo博客在多个设备间同步。

简单来说,在部署Hexo的时候,Github上的文件是编译过后的,而我们需要同步的往往是source目录下的源文件(markdown文件、图片等)。所以我们只要新创建一个仓库,然后把要备份的文件上传到仓库即可。
如果和我一样,不想太麻烦,直接备份整个hexo根目录下的文件即可。

1
chdir <Blog dir>
git init
git add --all
git commit -m 'first commit'
git remote add <remote name> <remote addr>
git push -u <remote name> master

然后在每次更新博客之后,即可更新远程仓库就可以了。

在一台新电脑上恢复博客也是很简单的

1
git clone <remote addr>
cd <Blog dir>
npm install hexo
npm install 
npm install hexo-deployer-git

这样就齐活儿了。

Zach的博客

Interaction Transition

| 阅读次数

Interaction Transition

为了自定义交互式的过渡动画,UIViewControllerTransitioningDelegate对象必须实现对应的协议方法,interactionControllerForPresentation(_:)和interactionControllerForDismissal(_:)。交互控制根据用户的手势输入来更新自定义的过渡动画,
而自定的过度动画是实现了UIViewControllerAnimatedTransitioning协议的对象,关于该协议这里就不再说明了。

Apple提供了一个交互式控制器的具体实现UIPercentDrivenInteractiveTransition,在大多数情况下,我们的交互式控制器只要继承它,然后加入自己的手势交互就好了。

在我们的手势交互逻辑中,根据用户的输入,我们利用以下几个方法来实时更新动画:

1
2
3
4
5
func updateInteractiveTransition(_ percentComplete: CGFloat)

func cancelInteractiveTransition()

func finishInteractiveTransition()

一个简单的例子如下:

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
import UIKit

class SwipeInteractionController: UIPercentDrivenInteractiveTransition {
var interactionInProgress : Bool = false
private var shouldCompleteTransition : Bool = false
private weak var viewController : UIViewController!

func wireToViewController(viewController : UIViewController!) {
self.viewController = viewController
prepareGestureRecognizerInView(viewController.view)
}


func handleGesture(gestureRecognizer : UIScreenEdgePanGestureRecognizer) {
let translation = gestureRecognizer.translationInView(gestureRecognizer.view!.superview)
var progress = translation.x / 200.0
progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0))

switch gestureRecognizer.state {
case .Began:
interactionInProgress = true
viewController.dismissViewControllerAnimated(true, completion: nil)
case .Changed:
shouldCompleteTransition = progress > 0.5
updateInteractiveTransition(progress)
case .Cancelled:
shouldCompleteTransition = false
interactionInProgress = false
cancelInteractiveTransition()
case .Ended:
interactionInProgress = false

if shouldCompleteTransition {
finishInteractiveTransition()
} else {
cancelInteractiveTransition()
}

default:
print("Unsupported")
}

}
// MARK: private
private func prepareGestureRecognizerInView(view : UIView) {
let edgeRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleGesture))
edgeRecognizer.edges = UIRectEdge.Left
view.addGestureRecognizer(edgeRecognizer)
}
}

在定义了这个Animator之后,只要在另外一个对象(实现了UIViewControllerTransitioningDelegate协议)的interactionControllerForDismissal(_:)返回这个类的实例即可。

1
2
3
func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return swipeAnimator.interactionInProgress ? swipeAnimator : nil
}
Zach的博客

Signal

| 阅读次数

Signal, Linux进程间通信

信号(Signal)是Linux的一种进程间通信机制,信号由内核或者其他进程发送给接收进程,接收到信号的进程相应地采取行动来处理信号。信号是一种软中断,即当一个进程收到一个信号时,它停止自己的执行流程,转而去处理信号。
值得注意的是信号是可重入的,即信号处理程序可以被其他信号中断。

信号的种类

常用的信号并不是很多,信号的名称在<signal.h>中定义,常用的信号及解释如下:

  • SIGALRM:由alarm()系统调用发送给接收进程,alarm()系统调用可以定时发送一个SIGALRM信号给进程,以触发某个定时操作。
  • SIGHUP:该信号表示某一个操作关闭了终端,运行在该终端下的程序会收到SIGHUP信号,默认的信号处理程序会结束收到的信号的程序。
  • SIGINT:用户输入了Ctrl-C就会发送一个SIGINT给前台应用。
  • SIGILL:这是一个异常信号,表示系统执行时遇到了一个非法程序,当加载动态链接一个被破坏的函数库时可能会产生该信号。注意,这个信号不能被捕获或者忽略,一般在shell中用于强制终止异常程序。
  • SIGABRT:程序中调用abort()系统调用会产生一个SIGABRT信号
  • SIGSEGV:这也是一个异常信号,当程序访问了一个不属于它的内存空间是内核会发送一个SIGSEGV信号给程序。
  • SIGPIPE:Broken Pipe,如果程序尝试往管道写入数据,而管道并没有与之对应的读数据的进程,就会产生SIGPIPE信号。
  • SIGTREM:这个信号告诉进程结束自身的运行,kill命令默认发送SIGTERM信号
  • SIGHLD:当一个子进程结束运行时,父进程就会收到一个SIGHLD信号。
  • SIGUSR1 & SIGUSR2:用户可自定义的信号。

注意SIGILL和SIGSTOP(用于调试)的处理行为不能被更改。

POSIX信号的语义

符合POSIX的系统上信号的处理总结为:

  • 一旦安装了信号处理函数,它就一直安装着
  • 在一个信号处理函数运行期间,正被递交的信号是阻塞的。而且安装处理函数时,POSIX保证传递给sigaction的sa_mask信号集中指定的任何信号都是被阻塞的。
  • 如果一个信号在被阻塞期间产生了一次或多次,那么该信号在被解阻塞之后通常只递交一次,也就是说,Unix信号默认是不排队的。
  • 利用sigprocmask函数选择性地阻塞或解阻塞一组信号是可能的,我们可以在一段临界代码执行期间,防止捕获某些信号,以此保护这段代码。

信号的处理

signal函数

signal函数是一个年代久远的信号处理函数,其函数原型如下:

1
void (*signal(int, void (*handler)(int)))(int);

可以看到,signal是一个函数,其返回值是一个函数指针,signal函数注册一个新的信号处理函数,并返回原先注册的信号处理函数。

一个signal函数的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void sig_handler(int signum) {
printf("Receive Signal : %d\n", signum);
}

int main() {
signal(SIGINT, sig_handler);
sleep(10);

return 0;
}

结果截图:
signal_test.png

可以看到当我们按下Ctrl-C时触发了自定义的信号处理函数。

sigaction函数

signal函数的行为根据UNIX版本的不同而不同,在不同的Linux版本中亦是如此。所以我们应该使用一个更加健壮的接口--sigaction函数。sigaction的原型为:

1
2
3
#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

sigaction的结构定义如下:

1
2
3
4
5
6
7
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};

sa_flags被设置成SA_SIGINFO时,信号处理函数是sa_sigaction函数,否则是sa_handler函数。sa_flags的其他值的解释如下:

  • SA_NOCLDSTOP:子进程停止时不产生SIGHLD信号
  • SA_RESETHAND:将对此信号的处理方式在信号处理函数入口处重置为SIG_DFL
  • SA_RESTART:重启可中断的函数而不是给出EINTER错误
  • SA_NODEFER:捕获到信号时不将它添加到屏蔽字中。

当我们使用sa_sigaction函数作为信号处理函数时,我们可以得到发送信号的进程的更多信息,信息存储在siginfo_t中,其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */

pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */

int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */

}

sa_mask中设置被进程屏蔽的信号。

一个使用sigaction的例子:

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
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

void sig_handler(int signum) {
printf("Receive signal : %d\n", signum);
}

int main() {
struct sigaction act;
memset(&act, 0, sizeof(act));

act.sa_handler = sig_handler;
act.sa_flags = SA_RESETHAND;
/* 清除屏蔽字 */
sigemptyset(&act.sa_mask);

sigaction(SIGINT, &act, 0);

while (1) {
printf("Test\n");
sleep(2);
}

return 0;
}

结果如下:

sigaction_test.png

可以看到,程序第一次收到SIGINT信号时执行的是自定义的信号处理函数,第二次就被重置为默认的信号处理函数了。

kill函数

kill系统调用函数的原型如下:

1
2
3
4
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

它的作用是发送一个信号(由sig参数指定)给一个特定进程。默认发送SIGTERM信号。

信号集处理函数

信号集处理函数主要用于处理sigset_t,从而让进程屏蔽某些信号。函数的原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <signal.h>

int sigemptyset(sigset_t *);

int sigfillset(sigset_t *);

int sigaddset(sigset_t *);

int sigdelset(sigset_t *);

int sigismember(sigset_t *sigset, int signal);

int sigpromask(int how, const sigset_t *set, sigset_t *osigset);

int sigpending(sigset_t *);

int sigsuspend(const sigset_t *);

sigpromask可以根据how指定的值来修改进程的信号屏蔽字。如果set参数为空,则how没有意义,但是如果此时osigset不为空,那么当前信号屏蔽字保存在osigset指向的地址中。

how的不同取值和对应的操作:

  • SIG_BLOCK:把参数set中的信号加入到进程的信号屏蔽字中
  • SIG_SETMASK:把进程的信号屏蔽字设置为set中的信号
  • SIG_UNBLOCK:从进程的信号屏蔽字中删除set中指定的信号

注意,调用这个函数才能修改进程的信号屏蔽字,它之前的函数都只是改变一个变量,对进程的信号屏蔽字并没有直接影响。

sigpending函数将被阻塞的信号中停留在待处理状态的信号写入参数指定的地址中。

sigsuspend函数首先将进程的屏蔽字换成参数指定的信号,然后挂起进程。

一个例子实验一下以上的函数:

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
#include <unistd.h>  
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
void handler(int sig)
{

printf("Handle the signal %d\n", sig);
}

int main()
{

sigset_t sigset;//用于记录屏蔽字
sigset_t ign;//用于记录被阻塞的信号集
struct sigaction act;
//清空信号集
sigemptyset(&sigset);
sigemptyset(&ign);
//向信号集中添加信号SIGINT
sigaddset(&sigset, SIGINT);

//设置处理函数和信号集
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);

printf("Wait the signal SIGINT...\n");
pause();//挂起进程,等待信号

//设置进程屏蔽字,在本例中为屏蔽SIGINT
sigprocmask(SIG_SETMASK, &sigset, 0);
printf("Please press Ctrl+c in 10 seconds...\n");
sleep(10);
//测试SIGINT是否被屏蔽
sigpending(&ign);
if(sigismember(&ign, SIGINT))
printf("The SIGINT signal has ignored\n");
//在信号集中删除信号SIGINT
sigdelset(&sigset, SIGINT);
printf("Wait the signal SIGINT...\n");
//将进程的屏蔽字重新设置,即取消对SIGINT的屏蔽
//并挂起进程
sigsuspend(&sigset);

printf("The app will exit in 5 seconds!\n");
sleep(5);
exit(0);
}

运行结果:

sigset_test.png

可以看到当我们屏蔽了SIGINT信号后,如果SIGINT发送给进程,那么信号就被阻塞编程待处理状态,当我们取消对信号的阻塞后该信号马上就被处理,于是程序就马上打印

1
The app will exit in 5 seconds!
Zach的博客

Unix套接字编程基础

| 阅读次数

数据结构

这里仅讨论IPv4相关的数据,IPv6与之类似,可以查阅相关的资料。

网络套接字的地址结构:

1
2
3
4
5
#include <netinet/in.h>

struct in_addr {
in_addr_t s_addr; // 32位IPv4地址
}
1
2
3
4
5
6
7
8
9
#include <netinet/in.h>

struct sockaddr_in {
uint8_t sin_len; // 结构的长度
sa_family_t sin_family; // AF_INET,地址族
in_port_t sin_port; // 端口号

char sin_zero[8]; // unused
}

网络协议规定必须指定一个网络字节序(大端还是小端),作为网络编程人员必须清楚不同字节之间的差异。比如,在每一个TCP分节中都有16位的端口号和32位的IPv4地址,发送协议栈和接收协议栈必须就这些多字节字段各个字节的传送顺序达成一致。网络协议使用大端字节序来传送这些多字节数据。

举例来说,如果有一个32位的IPv4地址,首先要转换字节序再使用。

1
uint32_t ip_addr = htonl(ori_ip_addr);

共有4个转换函数:

1
2
3
4
5
6
7
8
9
#include <netinet/in.h>

uint16_t htons(uint16_t);

uint32_t htonl(uint32_t);

uint16_t ntohs(uint16_t);

uint32_t ntohl(uint32_t);

h表示host,n表示network,s表示short,l表示long

通常来说我们习惯用点分十进制来表示IP地址,如192.168.1.1,因为这样易读易懂,但是在网络上传输时应该使用的32位的二进制来表示IP地址,于是就要进行转换,以下两个函数提供转换功能:

1
2
3
4
5
#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr);

const char *inet_ntop(int family, const char *addrptr, char *strptr, size_t len);

family即可以是AF_INET,也可以是AF_INET6。如果是不被支持的地址族作为family参数,返回一个错误,errno被设置为EAFNOSUPPORT。

这里n表示numeric,p表示presentation。

基本TCP套接字编程

sock函数

首先需要创建一个套接字,创建套接字的函数如下:

1
2
3
#include <sys/socket.h>

int socket(int family, int type, int protocol);

family指定协议族,type指定套接字类型,protocol指定某个协议类型常值,当设置为0时,程序会选择和给定的family和type组合的系统默认值。

connect函数

TCP客户端用connect函数来与TCP服务器建立连接。

1
2
3
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

客户不必在使用connect前调用bind函数,因为需要的话内核会分配一个临时的端口给客户进程。

出错返回的错误情况:

  1. TCP客户等待一段时间后没有收到服务器返回的TCP分节,返回ETIMEDOUT错误。
  2. 对客户的SYN响应是RST(复位),表明服务器上并没有进程在等待与之连接,这是一种硬错误,客户收到后马上返回ECONNREFUSED。
  3. 客户发出的SYN在某个路由上引发了一个”destination unreachable”的ICMP错误,则认为是一种软错误。客户机内核保存该消息,并持续发送SYN包,如果等待一段时间后未收到响应,就把保存的信息(ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。

bind函数

绑定一个本地协议地址给一个套接字,对于网际协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或者UDP端口号的组合。

函数原型:

1
2
3
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

listen函数

listen仅由TCP服务器调用,它把一个未连接的套接字转换成一个被动套接字,以接收客户的请求。

1
2
3
#include <sys/socket.h>

int listen(int sockfd, int backlog);

内核为任何一个给定的监听套接字维护两个队列:

  1. 未完成连接队列:客户已经发送一个SYN分节给服务器,而服务器正在等待完成对应的三次握手过程。
  2. 已完成连接队列:每个已完成TCP三次握手的客户对应启动一项。

backlog指定两个队列总和的最大值。

要注意的是,在三次握手完成之后,但在服务器调用accept之前到达的数据应由服务器TCP排队。

accept函数

accept函数用于TCP服务器调用,将从已完成队列返回队头的连接。如果已完成连接队列为空,那么服务器进程将被睡眠,直到下一个连接完成。

函数原型:

1
2
3
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t, *addrlen);

一个并发的服务器的例子

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
#include "unp.h"
#include <time.h>
#include <string.h>

int main(int argc, char **argv) {
int listenfd, connfd;
socklen_t len;

struct sockaddr_in servaddr, cliaddr;
char buf[MAXLINE];
time_t ticks;
pid_t pid;

listenfd = Socket(AF_INET, SOCK_STREAM, 0);
memset(servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13);

Bind(listenfd, (SA* )&servaddr, sizeof(servaddr));

Listen(listenfd, LISTENQ);

while (1) {
len = sizeof(connfd);
connfd = Accept(listenfd, (SA *)&cliaddr, &len);
if ((pid = fork()) == 0) {
// 子进程
Close(listenfd);
do_process(connfd);
Close(connfd);
exit(0);
}
Close(connfd);
}

return 0;
}
Zach的博客

自定Modal Transition

| 阅读次数

自定义Modal Transition

序

iOS 7提供了一套新的自定义转场动画的API,这套API可以大大降低了我们自定过渡动画的难度。下面用Ray Wenderlich的一张流程图说明API各个部分的作用。

process.jpg

可以看到一个ViewController被呈现或者消除的时候,UIKit会想Transition Delegate请求一个Animator,Animator负责转场动画的实现。可见我们需要自定义个Animator。

一个Animator应该遵循UIViewControllerAnimatedTransitioning协议,这个包含三个方法:

1
2
3
4
5
func transitionDuration(_ transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval

func animateTransition(_ transitionContext: UIViewControllerContextTransitioning)

optional func animationEnded(_ transitionCompleted: Bool)

这里出现了一个UIViewControllerContextTransitioning,它是转场动画的上下文对象,通过它可以获得将要被显示的view、将要被消除的view、将要显示的viewController等等相关对象。需要注意一定是,可能你自己持有了destinationController,但是,但是,但是,一定要通过transitionContext对象来获得desctinationController,以避免出现一些棘手的错误。

开始

下面我们就来着手写一个Modal Transition。这个转场动画参考了Bubble Transition

先看一下效果:

preview.gif

首先创建一个BubbleTransition类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BubbleTransition: NSObject {
var bubbleColor: UIColor = UIColor.whiteColor()
var startPoint: CGPoint = CGPointZero {
didSet {
bubble.center = startPoint
}
}

private(set) var bubble: UIView = UIView()
var duration: Double = 0.5

enum BubbleTransitionType {
case dismiss, present
}

var mode: BubbleTransitionType = .present
}

BubbleTransition现在只是定义了一个变量,其中最主要的是BubbleTransitionType,present表示显示viewController,dismiss表示消除viewController。startPoint是将要呈现的viewController.view的起始位置,后面我们将看到它的作用。

现在我们利用扩展来BubbleTransition实现UIViewControllerAnimatedTransitioning协议。

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
72
73
74
75
76
77
78
79
80
81
extension BubbleTransition: UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
// 1
return duration
}

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
guard let containerView = transitionContext.containerView() else {
return;
}
if mode == .present {
guard let presentedView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
return;
}
let originalSize = presentedView.frame.size
let originalCenter = presentedView.center

// 2
let bubbleFrame = bubbleFrameFrom(originalSize: originalSize, startPoint: startPoint)
bubble.backgroundColor = bubbleColor
bubble.frame = bubbleFrame
bubble.center = startPoint
bubble.layer.cornerRadius = bubble.frame.size.height / 2.0
bubble.transform = CGAffineTransformMakeScale(0.001, 0.001)
containerView.addSubview(bubble)

// 3
presentedView.center = bubble.center
presentedView.transform = CGAffineTransformMakeScale(0.001, 0.001);
presentedView.alpha = 0
containerView.addSubview(presentedView)

UIView.animateWithDuration(duration, animations: {() in
presentedView.center = originalCenter
presentedView.alpha = 1
presentedView.transform = CGAffineTransformIdentity

self.bubble.transform = CGAffineTransformIdentity
}) {(_) in
// 4
transitionContext.completeTransition(true)
}
} else {
guard let returningView = transitionContext.viewForKey(UITransitionContextFromViewKey) else {
return;
}
let originalSize = returningView.frame.size
let originalCenter = returningView.center
let bubbleFrame = bubbleFrameFrom(originalSize: originalSize, startPoint: startPoint)
bubble.frame = bubbleFrame
bubble.layer.cornerRadius = bubble.frame.size.height / 2.0
bubble.backgroundColor = bubbleColor
bubble.center = startPoint

UIView.animateWithDuration(duration, animations: {
self.bubble.transform = CGAffineTransformMakeScale(0.001, 0.001)
returningView.center = self.startPoint
returningView.alpha = 0.0
returningView.transform = CGAffineTransformMakeScale(0.001, 0.001)
}) {(_) in
// 5
returningView.center = originalCenter
self.bubble.transform = CGAffineTransformIdentity
self.bubble.removeFromSuperview()
returningView.removeFromSuperview()
transitionContext.completeTransition(true)
}
}
}
}

// 6
extension BubbleTransition {
func bubbleFrameFrom(originalSize size: CGSize, startPoint start: CGPoint) -> CGRect {
let lengthX = fmax(start.x, size.width-start.x)
let lengthY = fmax(start.y, size.height-start.y)
let radius = sqrt(lengthX*lengthX + lengthY*lengthY)

return CGRectMake(0, 0, radius*2, radius*2)
}
}

先解释一下整体的思路:

cordinator2.png

小圆圈表示button的位置,点击button会呈现我们要显示的view,就称它是modalView吧。在动画发生之前,我们在containerView中加入一个bubbleView,然后加入modalView(顺序很重要),bubbleView就作为modalView的背景,它们一起从0.001的比例缩放到1比例,这样看起来就像是一个气泡的效果。因为气泡要充满整个屏幕,所以我们就要像上述图示所示的那样得到最大的半径,然后将bubbleView的长宽设置为半径的两倍。

对应标签位置的解释:

  1. 转场动画的时间长度
  2. 得到bubbl视图的frame,将它的缩放比列设为0.001,然后添加到containerView中
  3. 添加将要呈现的视图,将它的比例也设置为0.001,以便和bubble视图一起放到
  4. 在动画结束之后一定要调用transitionContext.completeTransition(true)以表示这次转场动画结束。
  5. dismiss基本上是present的逆过程,需要注意的是在动画完成后要把bubble视图和modalView从containerView中移除。
  6. 计算最大半径,并返回bubbleView的Frame。

最后,为了让我们的BubbleTransition成为转场动画的Animator,我们要在prepareForSegue中设置destinationController的transitioningDelegate,并实现协议对应的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationController = segue.destinationViewController

destinationController.modalPresentationStyle = .Custom // 自定义modalPresentationStyle
destinationController.transitioningDelegate = self; // 设置delegate
}

// UIViewControllerTransitioningDelegate
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.bubbleColor = bubbleBtn.backgroundColor!
transition.startPoint = bubbleBtn.center
transition.mode = .present

return transition
}

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.bubbleColor = bubbleBtn.backgroundColor!
transition.startPoint = bubbleBtn.center
transition.mode = .dismiss

return transition
}
Zach的博客

Swift Collections

| 阅读次数

Swift Collections

在Swift中自定义一个CollectionType或者是SequenceType类型的数据结构需要实现对应的协议。这里首先给出一个自定义的SequenceType类型的数据结构,然后我们再来逐步分解代码解析之。

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
extension Array {
subscript(safe index: Int) -> Element? {
return index>=0 && index<count ? self[index] : nil
}
}

struct ArrayGenerator<T>: GeneratorType {
let array: [T]
var currentIndex = 0

init(_ array:[T]) {
self.array = array
}

mutating func next() -> T? {
return self.array[safe: currentIndex++]
}
}

struct Section<T> : SequenceType {
let title: String
let elements: [T]

subscript(index: Int) -> T? {
return elements[safe: index]
}

func generate() -> ArrayGenerator<T> {
return ArrayGenerator(elements)
}
}

Sequence在Swift中的定义:

A type that can be ietrated in a `for...in` loop

要成为一个Sequence,必须实现SequenceType协议,该协议要求实现一个方法以返回一个Generator,而一个Generator又实现了一个GeneratorType的协议,它封装了我们自定的Sequence类型里面的数据,并且保留了迭代的过程。

在上述代码中,我们首先定一个一个ArrayGenerator作为我们的Generator,然后在我们自定的Section类型中实现generate方法,返回我们的Generator,这样我们就可以在for…in中使用我们的Section了。

1
2
3
4
5
6
7
8
9
10
11
let elements = ["hello", "world", "haha"]
let section = Section(title: "Test", elements: elements)

for e in section {
print(e)
}

var generator = section.generate()
while let e = generator.next() {
print(e)
}

上述代码中,for…in和while循环实际上是一样的。

在实现SequenceType以后,我们还自动获得了许多方法,比如:

1
2
3
section.sort() // ["haha", "hello", "world"]
section.maxElement() // "world"
section.minElement() // "haha"

因为字符串是可比较的,所以上述三个方法均可返回正确的数值。

在讨论SequenceType之后,我们来看看CollectionType的定义:

1
2
3
protocol CollectionType: Indexable, SequenceType {

}

所以要定义一个CollectionType类型的数据结构,只要我们的Section再实现Indexable协议即可。我们改写Section的代码。

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
struct Section<T> : CollectionType {
let title: String
let elements: [T]

subscript(safe index: Int) -> T? {
return index>=0 && index<elements.count ? elements[index] : nil
}

func generate() -> ArrayGenerator<T> {
return ArrayGenerator(elements)
}

var startIndex: Int {
return 0
}

var endIndex: Int {
return elements.count
}

subscript(index: Int) -> T {
return elements[index]
}

}

由于遵守Indexable协议的类型必须实现一个下标脚本,其返回值是一个non-optinal的值,所以我们需要改写Section为上述形式。

至此我们就创建了一个CollectionType类型的Section。

在这里需要额外说一点:在我们用下标语法来取值的时候,swift中的array和dictionary的行为是不一致的,举个栗子:

1
2
3
4
5
let arr = ["h", "e", "l"];
let dict = ["hello":1, "world":2]

dict["haha"] // nil
arr[3] // error

字典和数组都是实现了CollectionType协议,换言之,它们都实现了返回一个non-optional的下表脚本,那为什么上述代码中字典返回nil呢?我们看字典的下表语法:

1
2
3
4
struct Dictionary<Key : Hashable, Value> {
subscript(key: Key) -> Value?
subscript(position: DictionaryIndex<Key, Value>) -> (Key, Value)
}

实际上在字典中返回non-optional的是第二个脚本。所以当我们按照以下方法来操作字典时就会报错。

1
2
var startIndex = dict.startIndex
dict[startIndex.advancedBy(3)] // error

Indexable要求返回一个non-optional的值仅仅为了性能,毕竟直接crash比检查一个index的有效性快。

Zach的博客

FBKVOController源码阅读笔记

| 阅读次数

FBKVOController 源码阅读笔记

FBKVOContoller是Facebook推出的KVO的库,目的在于简化KVO的代码编写,它提供了干净的block回调,避免了散落到各处的处理逻辑。

结构分析

FBKVOController主要由以下三个部分组成:

  1. FBKVOInfo
  2. FBKVOSharedController
  3. FBKVOControlller

FBKVOInfo

FBKVOInfo用来作为KVO中的Context,它的定义如下:

1
@implementation _FBKVOInfo
{
@public
  __weak FBKVOController *_controller;
  NSString *_keyPath;
  NSKeyValueObservingOptions _options;
  SEL _action;
  void *_context;
  FBKVONotificationBlock _block;
  _FBKVOInfoState _state;
}

_FBKVOInfoState的定义如下:

1
typedef NS_ENUM(uint8_t, _FBKVOInfoState) {
  _FBKVOInfoStateInitial = 0,

  // whether the observer registration in Foundation has completed
  _FBKVOInfoStateObserving,

  // whether `unobserve` was called before observer registration in Foundation has completed
  // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions
  _FBKVOInfoStateNotObserving,
};

以上的定义基本可以后名字推断,就不再赘述。

FBKVOSharedController

FBKVOSharedController是一个单例,所有的观察信息以FBKVOInfo的形式交由其处理,FBKVOSharedController内部实现了KVO机制,当FBKVOInfo中的keypath对应的属性发生改变,那么对应的回调就会被执行。

具体的代码如下:

1
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary<NSString *, id> *)change
                       context:(nullable void *)context
{
  NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
  
  _FBKVOInfo *info;
  
  {
    // lookup context in registered infos, taking out a strong reference only if it exists
    OSSpinLockLock(&_lock);
    info = [_infos member:(__bridge id)context];
    OSSpinLockUnlock(&_lock);
  }
  
  if (nil != info) {
    
    // take strong reference to controller
    FBKVOController *controller = info->_controller;
    if (nil != controller) {
      
      // take strong reference to observer
      id observer = controller.observer;
      if (nil != observer) {
        
        // dispatch custom block or action, fall back to default action
        if (info->_block) {
          info->_block(observer, object, change);
        } else if (info->_action) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
          [observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
        } else {
          [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
        }
      }
    }
  }
}

FBKVOSharedController还定义了以下三个方法,用于添加删除FBKVOInfo到其维护的一个NSHashTabe<_fbkvoinfo *="">中。

代码如下:

1
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info

- (void)unobserve:(id)object info:(nullable _FBKVOInfo *)info

- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos

FBKVOController

FBKVOController内部维护了一个NSMapTable *>用于建立一个被观察对象和被观察对象的KVOInfos(一个被观察对象可以又许多keypath被观察)的映射。

除此之外,FBKVOController只是简单的地调用FBKVOSharedController提供的方法来添加/删除观察对象。

Zach的博客

Web.xml标签记录

| 阅读次数

Web.xml 标签记录

初始化过程

  1. 在启动Web项目时,WEB容器会去读取它的配置文件web.xml,读取和两个节点。
  2. 紧接着,容器创建一个ServletContext实例,这个web项目共享这个servletContext实例
  3. 容器将转换维键值对,并交给servletContext。
  4. 容器创建中的类实例,创建监听器

加载的顺序为和标签在文件中出现的顺序是没有关系的(对不同类标签而言),加载的顺序为

1
context-param --> listener --> filter --> servlet

对某类配置节点而言,它们的加载顺序和出现顺序有关。例如若若多个都映射到了同一个url,那么按照出现的顺序依次做doFilter

Context-param

<Context-param>用来声明应用范围内的上下文初始化参数,一个例子如下

1
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>contextConfgiLocationValue</param-value>
</context-param>
  1. <param-name> 用于声明参数名
  2. <param-value> 用于声明参数值

而后在Servlet中可以通过以下方式得到初始化参数

1
String paramValue = getServletContext().getInitParameter("contextConfigLocation")

listener

<listener>用于注册一个监听器类,当一个事件(在建立、修改和删除会话或者servlet环境)发生时得到通知,可以对时间做出相应的响应。其常与<context-param>一起使用。

示例如下:

1
<!--初始化日志配置文件 -->  
<listener>  
    <listener-class>  
        com.myapp.LogbackConfigListener  
    </listener-class>  
</listener>  
<context-param>  
    <param-name>logbackConfigLocation</param-name>  
    <param-value>WEB-INF/logback.xml</param-value>  
</context-param>
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

public class LogbackConfigListener implements ServletContextListener {

private static final Logger logger = LoggerFactory.getLogger(LogbackConfigListener.class);
private static final String CONFIG_LOCATION = "logbackConfigLocation";

public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub

}
public void contextInitialized(ServletContextEvent event) {
// TODO Auto-generated method stub
String logbackConfigLocation = event.getServletContext().getInitParameter(CONFIG_LOCATION);
String fn = event.getServletContext().getRealPath(logbackConfigLocation);
try {
LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
loggerContext.reset();
JoranConfigurator joranConfigurator = new JoranConfigurator();
joranConfigurator.setContext(loggerContext);
joranConfigurator.doConfigure(fn);
logger.debug("loaded slf4j configure file from {}", fn);
}
catch (JoranException e) {
logger.error("can loading slf4j configure file from " + fn, e);
}

}

}

filter

<filter>用于在请求和响应对象被servlet处理之前或者之后,使用过滤器对象对这两个对象进行操作。<filter-class>中指定的过滤器必须继承以下三种方法:

  • init(FilterConfig filterConfig)
  • doFilter(…): 用于对request,response进行处理,并能通过chain.doFilter(…)将控制权交给下一个过滤器
  • destroy(): 资源销毁

一个用于设置编码的filter配置示例:

1
<filter>
  <filter-name>setCharacterEncoding</filter-name>
  <filter-class>coreservlet.javaworld.CH11.SetCharacterEncodingFilter</filter-class>
  <init-param>
     <param-name>encoding</param-name>
     <param-value>GB2312</param-value>
  </init-param>
</filter>
<filter-mapping>
   <filter-name>setCharacterEncoding</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

servlet

用于配置servlet所对应的URL

session-config

<session-config>用于定义session的有效期限,单位为分钟

mime-mapping

<mime-mapping>用于定于扩展名(extension)与MIME类型的对映,如:

1
<mime-mapping>  
   <extension>doc</extension>  
   <mime-type>application/vnd.ms-word</mime-type>  
</mime-mapping>

welcome-file-list与error-page

<welcome-file-list>用于指定WEB应用首页,<erro-page>用于指定对应的错误代码(如404)或者java异常(如java.lang.Exception)对应的页面

jsp-config

<jsp-config>主要用于设定JSP页面的相关配置。

一个较为完整的配置如下:

1
<jsp-config>
    <taglib>
        <taglib-uri>http://mytaglib.uri</taglib-uri>
        <taglib-location>/WEB-INF/tlds/MyTaglib.tld</taglib-location>
    </taglib>
    <jsp-property-group>
        <description>Special property group for JSP Configuration JSP example.</description>
        <display-name>JSPConfiguration</display-name>
        <url-pattern>/jsp/* </url-pattern>
        <el-ignored>true</el-ignored>
        <page-encoding>GB2312</page-encoding>
        <scripting-invalid>true</scripting-invalid>
        <include-prelude>/include/prelude.jspf</include-prelude>
        <include-coda>/include/coda.jspf</include-coda>
    </jsp-property-group>
</jsp-config>
1…34
Zach

Zach

38 日志
17 分类
47 标签
© 2017 Zach