iOS多个网络请求完成后执行下一步

在开发中,我们很容易遇到这样的需求,需要我们同时做多个网络请求,所有网络请求都完成后才能进行下一步的操作。如下载多个图片,下载完了才能展示。今天我们就来研究一下这个问题的解决方案。

1.首先,我们创建一个项目,然后做一般性的做法,不做任何处理去连续请求一个接口10次:

1
2
3
4
5
6
7
//1.无处理
UIButton *Btn1 = [UIButton buttonWithType:UIButtonTypeCustom];
Btn1.frame = CGRectMake(100, 100, 100, 40);
Btn1.backgroundColor = [UIColor grayColor];
[Btn1 setTitle:@"noConduct" forState:UIControlStateNormal];
[Btn1 addTarget:self action:@selector(Btn1) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:Btn1];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//1.noConduct
-(void)Btn1{
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
}];
[task resume];
}
NSLog(@"end");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2017-12-04 17:10:10.503 DownImage[3289:261033] end
2017-12-04 17:10:10.676 DownImage[3289:261080] 0---0
2017-12-04 17:10:10.704 DownImage[3289:261080] 1---1
2017-12-04 17:10:10.754 DownImage[3289:261096] 4---4
2017-12-04 17:10:10.760 DownImage[3289:261080] 2---2
2017-12-04 17:10:10.800 DownImage[3289:261096] 5---5
2017-12-04 17:10:10.840 DownImage[3289:261080] 7---7
2017-12-04 17:10:10.844 DownImage[3289:261082] 6---6
2017-12-04 17:10:10.846 DownImage[3289:261096] 3---3
2017-12-04 17:10:10.888 DownImage[3289:261096] 8---8
2017-12-04 17:10:10.945 DownImage[3289:261080] 9---9
作者:_清墨
链接:https://www.jianshu.com/p/fb4fb80aefb8
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

很明显,无任何处理情况下,end最先被打印出来,由于网络请求的异步回调,然后各个网络请求的回调顺序是无序的。

2.使用GCD的dispatch_group_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
25
//2.group--
-(void)Btn2{
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
dispatch_group_enter(downloadGroup);
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
dispatch_group_leave(downloadGroup);
}];
[task resume];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
}
1
2
3
4
5
6
7
8
9
10
11
2017-12-04 17:14:46.984 DownImage[3289:265374] 2---2
2017-12-04 17:14:46.987 DownImage[3289:265370] 1---1
2017-12-04 17:14:47.052 DownImage[3289:265383] 5---5
2017-12-04 17:14:47.065 DownImage[3289:265370] 4---4
2017-12-04 17:14:47.111 DownImage[3289:265379] 3---3
2017-12-04 17:14:47.121 DownImage[3289:265383] 6---6
2017-12-04 17:14:47.169 DownImage[3289:265383] 7---7
2017-12-04 17:14:47.192 DownImage[3289:265370] 9---9
2017-12-04 17:14:47.321 DownImage[3289:265383] 8---8
2017-12-04 17:14:47.747 DownImage[3289:265374] 0---0
2017-12-04 17:14:47.747 DownImage[3289:261033] end

从上两次输出可以看出,end确实是在所有网络请求之后才输出出来,这也是符合了我们的需求。

1
2
3
4
5
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup);
dispatch_group_leave(downloadGroup);
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
});

对以上4行代码可理解为:创建一个dispatch_group_t, 每次网络请求前先dispatch_group_enter,请求回调后再dispatch_group_leave,对于enter和leave必须配合使用,有几次enter就要有几次leave,否则group会一直存在。当所有enter的block都leave后,会执行dispatch_group_notify的block。

3.使用GCD的信号量dispatch_semaphore_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
25
26
27
28
29
//3.semaphore--
-(void)Btn3{
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
count++;
if (count==10) {
dispatch_semaphore_signal(sem);
count = 0;
}
}];
[task resume];
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
}
1
2
3
4
5
6
7
8
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_semaphore_signal(sem);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
对这三句代码可以这样理解:dispatch_semaphore信号量为基于计数器的一种多线程同步机制。如果semaphore计数大于等于1,计数-1,返回,程序继续运行。如果计数为0,则等待。dispatch_semaphore_signal(semaphore)为计数+1操作,dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)为设置等待时间,这里设置的等待时间是一直等待。
对于以上代码通俗一点就是,开始为0,等待,等10个网络请求都完成了,dispatch_semaphore_signal(semaphore)为计数+1,然后计数-1返回,程序继续执行。 (这里也就是为什么有个count变量的原因,记录网络回调的次数,回调10次之后再发信号量,使后面程序继续运行)。
4.考虑新需求,10个网络请求顺序回调。

需求需要顺序回调,即执行完第一个网络请求后,第二个网络请求回调才可被执行,简单来讲就是输出得是0,1,2,3…9这种方式的。

对于这个需求我也是根据自己最近做的项目来提的,因为网络请求回调的异步性,我们虽可以控制网络请求的顺序执行,却不能控制它的完成回调顺序。这就有点伤了,目前我项目是找到了解决方案,但这个问题还没有找到解决办法,提出来跟大家讨论一下。(请忽略网络请求执行,回调,在回调里请求下一个接口的办法,讨论还有没有别的方法,最好show the code).

最后,贴点NSOperation的代码,为了解决新需求所写,由于网络请求回调异步性不能满足需求,但若不是网络请求等异步回调的方式,这样的做法是可以的,大家可以试试.

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
//4.NSOperation
-(void)Btn4{
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableArray *operationArr = [[NSMutableArray alloc]init];
for (int i=0; i<10; i++) {
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
}];
[task resume];
//非网络请求
NSLog(@"noRequest-%d",i);
}];
[operationArr addObject:operation];
if (i0) {
NSBlockOperation *operation1 = operationArr[i-1];
NSBlockOperation *operation2 = operationArr[i];
[operation2 addDependency:operation1];
}
}
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperations:operationArr waitUntilFinished:NO]; //YES会阻塞当前线程
#warning - 绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。
}
2017-12-04 18:03:10.224 DownImage[3584:304363] noRequest-0
2017-12-04 18:03:10.226 DownImage[3584:304362] noRequest-1
2017-12-04 18:03:10.226 DownImage[3584:304363] noRequest-2
2017-12-04 18:03:10.231 DownImage[3584:304363] noRequest-3
2017-12-04 18:03:10.232 DownImage[3584:304362] noRequest-4
2017-12-04 18:03:10.233 DownImage[3584:304362] noRequest-5
2017-12-04 18:03:10.233 DownImage[3584:304363] noRequest-6
2017-12-04 18:03:10.234 DownImage[3584:304363] noRequest-7
2017-12-04 18:03:10.235 DownImage[3584:304363] noRequest-8
2017-12-04 18:03:10.236 DownImage[3584:304363] noRequest-9
2017-12-04 18:03:10.408 DownImage[3584:304597] 2---2
2017-12-04 18:03:10.408 DownImage[3584:304597] 0---0
2017-12-04 18:03:10.409 DownImage[3584:304597] 1---1
2017-12-04 18:03:10.461 DownImage[3584:304597] 5---5
2017-12-04 18:03:10.476 DownImage[3584:304363] 4---4
2017-12-04 18:03:10.477 DownImage[3584:304365] 6---6
2017-12-04 18:03:10.518 DownImage[3584:304365] 7---7
2017-12-04 18:03:10.537 DownImage[3584:304596] 8---8
2017-12-04 18:03:10.547 DownImage[3584:304362] 9---9
2017-12-04 18:03:11.837 DownImage[3584:304362] 3---3
5.还是使用信号量semaphore完成4的需求
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
//5.semaphore--order
-(void)Btn5{
NSString *str = @"http://www.jianshu.com/p/6930f335adba";
NSURL *url = [NSURL URLWithString:str];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%d---%d",i,i);
dispatch_semaphore_signal(sem);
}];
[task resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"end");
});
}
2017-12-05 10:17:28.175 DownImage[938:51296] 0---0
2017-12-05 10:17:28.331 DownImage[938:51289] 1---1
2017-12-05 10:17:28.506 DownImage[938:51289] 2---2
2017-12-05 10:17:28.563 DownImage[938:51289] 3---3
2017-12-05 10:17:28.662 DownImage[938:51289] 4---4
2017-12-05 10:17:28.733 DownImage[938:51296] 5---5
2017-12-05 10:17:28.792 DownImage[938:51296] 6---6
2017-12-05 10:17:28.856 DownImage[938:51286] 7---7
2017-12-05 10:17:29.574 DownImage[938:51289] 8---8
2017-12-05 10:17:29.652 DownImage[938:51286] 9---9
2017-12-05 10:17:29.653 DownImage[938:45252] end

我们对比 3 的代码,3 中我们只在最后也就是循环结束后调用dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),循环中当网络请求回调10次(也就是都回调完)后,使传入的信号量加1:( dispatch_semaphore_signal(sem) ),这时等待结束,然后进行后续的操作。

在这个方法里,我们每一次遍历,都让其dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),这个时候线程会等待,阻塞当前线程,直到dispatch_semaphore_signal(sem)调用之后,而我们dispatch_semaphore_signal(sem)是在网络请求的回调里调用的,所以这个方法的逻辑是:

遍历—发起任务—等待—任务完成信号量加1—等待结束,开始下一个任务
发起任务—等待—任务完成信号量加1—等待结束,开始下一个任务
发起任务—等待—任务完成信号量加1—等待结束,开始下一个任务

这样循环的模式,一个任务完成才能接着去做下面的任务,满足我们的需求。

但我们也要发现这样一个问题,我们使用这种方式,可以明显感觉出整个过程需要花费的时间大大增加了,不像我们 3 中同时(几乎)开启任务等待完成回调,这里是一个网络请求发出,等待,完成后发出第二个网络请求,等待,完成后再发出第三个,这样我们等待的时间是10个网络请求每一个回调时间的和,在时间上大大增加了消耗,而且对于dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER),它是会阻塞线程的,我们如果需要在网络请求完成后修改UI,那这种方式会影响我们的界面交互,接下来我们对比一下两者时间消耗:

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
3-------------3----------3-------
2017-12-05 10:29:51.178 DownImage[938:56971] 2---2
2017-12-05 10:29:51.193 DownImage[938:57200] 0---0
2017-12-05 10:29:51.202 DownImage[938:56631] 3---3
2017-12-05 10:29:51.248 DownImage[938:56971] 1---1
2017-12-05 10:29:51.262 DownImage[938:56971] 5---5
2017-12-05 10:29:51.291 DownImage[938:56631] 6---6
2017-12-05 10:29:51.375 DownImage[938:56631] 7---7
2017-12-05 10:29:51.384 DownImage[938:56631] 4---4
2017-12-05 10:29:51.434 DownImage[938:56971] 8---8
2017-12-05 10:29:51.487 DownImage[938:57199] 9---9
2017-12-05 10:29:51.488 DownImage[938:45252] end
5-------------5----------5-------
2017-12-05 10:29:52.190 DownImage[938:56631] 0---0
2017-12-05 10:29:52.304 DownImage[938:57199] 1---1
2017-12-05 10:29:52.432 DownImage[938:56971] 2---2
2017-12-05 10:29:52.520 DownImage[938:56971] 3---3
2017-12-05 10:29:52.576 DownImage[938:56631] 4---4
2017-12-05 10:29:52.628 DownImage[938:56971] 5---5
2017-12-05 10:29:52.706 DownImage[938:56631] 6---6
2017-12-05 10:29:52.764 DownImage[938:56971] 7---7
2017-12-05 10:29:52.853 DownImage[938:56631] 8---8
2017-12-05 10:29:52.925 DownImage[938:56971] 9---9
2017-12-05 10:29:52.926 DownImage[938:45252] end

看得出3花费时间为51.488 - 51.178约300多ms
— —5花费时间为52.926 - 52.190约700多ms

所以大家还请谨慎使用。

https://www.jianshu.com/p/fb4fb80aefb8

c的注意事项

注意事项

oc 调用 c c++ 需要注意的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef LearnC_h
#define LearnC_h
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
void learnCplus(int a, int b);
#ifdef __cplusplus
}
#endif
#endif /* LearnC_h */

hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef LearnC___hpp
#define LearnC___hpp
#include <string>
#include <stdio.h>
void learnCplusplus(int a, int b);
class cppObject
{
public:
void exampleMethod(const std::string& str);
};
#endif /* LearnC___hpp */

cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//
// LearnC++.cpp
// LearnOCandCandC++
//
// Created by 林伟池 on 16/2/23.
// Copyright © 2016年 林伟池. All rights reserved.
//
#include "LearnC++.hpp"
#include <iostream>
void learnCplusplus(int a, int b) {
printf("cplusplus%d\n", a + b);
}
void cppObject::exampleMethod(const std::string &str)
{
std::cout << str;
}
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
#import "LearnC.h"
#import "LearnC++.hpp"
#import "LearnOCinCPP.h"
@interface ViewController ()
@end
@implementation ViewController
{
cppObject* object;
}
- (void)viewDidLoad {
[super viewDidLoad];
learnCplus(1, 2); //在OC中使用c
learnCplusplus(1, 2); //在OC中用C++编译的C
//在OC中使用C++的类
object = new cppObject();
NSString* str = @"GAO高级\n";
std::string cpp_str([str UTF8String], [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
object->exampleMethod(cpp_str);
delete object;
object = NULL; //记得删除
//在OC使用的C++类中 使用OC
LY::OCinCPP* cpp = new LY::OCinCPP();
cpp->lyRun();
}

参考

Lede运行v2ray

LEDE 运行v2ray不完美的解决办法

从官网下载v2ray的运行文件放在/usr/bin/v2ray 目录下
v2ray release 下载

将config.json(或者你直接改名为v2ray.json)文件放在/etc/v2ray.json
将 /usr/bin/v2ray 和 /etc/v2ray.json 文件赋予权限 直接猛点的权限赋值方法 chomd 777 path

1
/usr/bin/v2ray --config /etc/v2ray.json

对应修改config.json文件后传到自己的lede的对应的目录中去
然后在网络设置中–代理—网页代理,安全网络代理中添加 你的路由器ip e.g. 192.168.1.111 加上对应的端口 1080;socks的代理配置同理

如果inbound 中的protocol: socks,需要在settings中增加 “auth”: “noauth”

1
2
3
4
5
6
"inbound": {
"port": 1080,
"protocol": "socks",
"settings": {
"auth": "noauth"
}
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
{
"log": {
"error": "/var/log/v2ray/error.log",
"loglevel": "warning"
},
"inbound": {
"port": 1080,
"protocol": "http",
"settings": {}
},
"outbound": {
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "v2ray.cool",
"port": 10086,
"users": [
{
"id": "a3482e88-686a-4a58-8126-99c9df64b7bf",
"alterId": 64,
"security": "auto"
}
]
}
]
},
"mux": {
"enabled": true
}
},
"outboundDetour": [
{
"protocol": "freedom",
"settings": {},
"tag": "direct"
}
],
"dns": {
"servers": [
"8.8.8.8",
"8.8.4.4",
"localhost"
]
},
"routing": {
"strategy": "rules",
"settings": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{
"type": "field",
"port": "1-52",
"outboundTag": "direct"
},
{
"type": "field",
"port": "54-79",
"outboundTag": "direct"
},
{
"type": "field",
"port": "81-442",
"outboundTag": "direct"
},
{
"type": "field",
"port": "444-65535",
"outboundTag": "direct"
},
{
"type": "chinasites",
"outboundTag": "direct"
},
{
"type": "field",
"ip": [
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"::1/128",
"fc00::/7",
"fe80::/10"
],
"outboundTag": "direct"
},
{
"type": "chinaip",
"outboundTag": "direct"
}
]
}
}
}

最新更新2017 8.16

https://github.com/v2ray/v2ray-core/issues/483
用otaku的一段代码实现了v2ray的进程守护

还是放在 /etc/init.d/v2ray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
START=90
USE_PROCD=1
start_service() {
mkdir /var/log/v2ray > /dev/null 2>&1
procd_open_instance
procd_set_param respawn
procd_set_param command /usr/bin/v2ray -config /etc/v2ray/config.json
procd_set_param file /etc/v2ray/config.json
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile /var/run/v2ray.pid
procd_close_instance
}

V2Ray 配置ss & vmess

V2Ray 配置文件设置

满足VMESS & SS协议

安装步骤

1 (以搬瓦工的Center OS 7 bbr)先用ssh登录

1
ssh root@162.219.121.23x -p 27530

2 Bash命令安装

1
bash <(curl -L -s https://install.direct/go.sh)

3 编辑服务端配置文件满足VMESS & SS协议

1
vi /etc/v2ray/config.json
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
{
"log" : {
"access": "/var/log/v2ray/access.log",
"error": "/var/log/v2ray/error.log",
"loglevel": "warning"
},
"inbound": {
"port": 21059,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "7ce007bc-47ea-45c1-9294-e37168186c4e",
"level": 1,
"alterId": 64
}
]
}
},
"outbound": {
"protocol": "freedom",
"settings": {}
},
"inboundDetour": [
{
"protocol": "shadowsocks",
"port": 21058,
"settings": {
"method": "aes-256-cfb",
"password": "v2ray",
"udp": false
}
}
],
"outboundDetour": [
{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}
],
"routing": {
"strategy": "rules",
"settings": {
"rules": [
{
"type": "field",
"ip": [
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"::1/128",
"fc00::/7",
"fe80::/10"
],
"outboundTag": "blocked"
}
]
}
}
}

配置文件设置好后,可以用命令测试

1
./v2ray --test --config v2ray-client.json

启动相关命令

  1. 编辑 /etc/v2ray/config.json 文件来配置你需要的代理方式;

  2. 运行 service v2ray start 来启动 V2Ray 进程;

  3. 之后可以使用 service v2ray start|stop|status|reload|restart|force-reload 控制 V2Ray 的运行

1
service v2ray status | start ...

注意事项:

以上配置适用于root权限下操作,若由于权限不足请自行搜索解决

如果遇到ssh无法登录的情况,可以先rm ~/.ssh/xxx 对应的文件然后再重新连接

uuid生成网站

路由追踪

配置生成器

这个配置生成只有一种协议,如果要支持多种协议,请参考上面贴出的code,分别为 inbound inboundDetour

在 iOS8以上工程创建通用的framework

在 iOS8以上工程创建通用的framework 以及如何适配 swift module

参考链接

改良链接

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
#!/bin/sh
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/iOS"
# Step 1. Build Device and Simulator versions on iOS
xcodebuild -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${PROJECT_NAME}" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6' clean build
xcodebuild -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${PROJECT_NAME}" -sdk iphoneos clean build
# Step 2. Copy the framework structure (from iphoneos build) to the universal folder
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/iOS"
# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/iOS/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"
# Step 4. Convenience step to copy the framework to the project's directory
mkdir -p "${TMPDIR}/${PROJECT_NAME}/Frameworks/iOS"
cp -R "${UNIVERSAL_OUTPUTFOLDER}/iOS/${PROJECT_NAME}.framework" "${TMPDIR}/${PROJECT_NAME}/Frameworks/iOS"
# Step 6. Create .tar.gz file for posting on the binary repository
cd "${TMPDIR}"
# We nest the framework inside a Frameworks folder so that it unarchives correctly
tar -zcf "${PROJECT_NAME}.framework.tar.gz" "${PROJECT_NAME}/Frameworks/"
mv "${PROJECT_NAME}.framework.tar.gz" "${PROJECT_DIR}/"
# Step 7. Convenience step to open the project's directory in Finder
#open "${PROJECT_DIR}"

BS fastlane的简单配置

简要记录如何通过 fastlane 和 jenkins实现自动打包上传蒲公英

环境: 1.Xcode 8.3.x 2.Jenkins 3.Fastlane

初次使用使用

1
fastlane init

输入对应的 apple id 账号和密码

下面我将粘贴 fastlane 目录结构的对应文件的内容

1
2
3
4
5
Fastlane
--- Appfile
--- Fastfile
--- Pluginfile //新版本中貌似不这样使用了

Appfile

1
2
3
4
5
6
7
app_identifier "com.XXX.app.suza.beta" # The bundle identifier of your app
apple_id "hai.duan@XXX.com.cn" # Your Apple email address
team_id "XXX" # Developer Portal Team ID
# you can even provide different app identifiers, Apple IDs and team names per lane:
# More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md

Fastfile

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
# update_fastlane
# Update this, if you use features of a newer version
fastlane_version "2.23.0"
default_platform :ios
platform :ios do
before_all do
cocoapods
end
desc "Runs all the tests"
lane :test do
scan
end
desc "Submit a new Beta Build to Apple TestFlight"
desc "This will also make sure the profile is up to date"
lane :beta do
# match(type: "appstore") # more information: https://codesigning.guide
gym(scheme: "BeSquare Beta",export_method: "ad-hoc") # Build your app - more options available
#pilot
# sh "your_script.sh"
# You can also use other beta testing services here (run `fastlane actions`)
end
desc "Deploy a new version to the App Store"
lane :release do
# match(type: "appstore")
# snapshot
gym(scheme: "BeSquare Beta",export_method: "appstore") # Build your app - more options available
deliver(force: true)
# frameit
end
# You can define as many lanes as you want
after_all do |lane|
# This block is called, only if the executed lane was successful
# slack(
# message: "Successfully deployed new App Update."
# )
end
error do |lane, exception|
# slack(
# message: exception.message,
# success: false
# )
end
end

Jenkins
—- Execute shell

1
2
3
4
5
6
pwd
cd SUZA
fastlane beta
GIT_COMMIT_MSG=`git log -1 --pretty=%h:%an:%B`
curl -F "updateDescription=${GIT_COMMIT_MSG}" -F "file=@/Users/duanhai/.jenkins/workspace/XXX/XX22DD/XXX Beta.ipa" -F "uKey=27ee4291086a9ba71c2f890e3d0bdc20" -F "_api_key=729c278fe701cc64bcf9eb14d83fd2fa" https://qiniu-storage.pgyer.com/apiv1/app/upload

kiwiDoc

Kiwi人脸跟踪SDK快速集成指南(iOS)

kiwi人脸跟踪SDK,主要功能包括:

  • 静态图片的人脸以及关键点位置检测
  • 68个人脸关键点的实时检测与跟踪(单人脸/多人脸)
  • 美颜、哈哈镜等实时滤镜功能
  • 人脸Pose参数估计
  • 趣味2D贴纸

我们的SDK针对移动端,在算法效率、硬件占用、性能精度等方面进行了相关优化,使其适用于移动端直播,美颜相机,滤镜相机,趣味贴纸,虚拟美妆等应用场景。

流程图

这个文档将演示如何利用我们的demo快速入门,并详细描述具体集成步骤。

Demo快速入门

该demo基于七牛的直播SDK,实现了在直播的场景下实现人脸跟踪以及趣味贴纸。

准备环境

开发环境要求

软件

* XCode 6.0 或以上版本
* iOS 7.0 或以上版本

硬件

* 支持语音/视频的真机设备

编译代码示例

  1. 用XCode打开demo工程文件(PLMediaStreamingKitDemo)。代码包含以下目录结构

  2. 选中项目,如下图所示,点击 “Build and Run” 按钮进行编译

    示例截图

| |

p.s. 该示例只支持在真机上实现功能,不支持模拟器。编译完成后,即可运行。

具体集成步骤

第一步:准备环境

软件

  • XCode 6.0 或以上版本
  • iOS 7.0 或以上版本

硬件

  • 支持语音/视频的真机设备

第二步:项目所需库

  • kiwi提供(请从sdk文件夹中获取)
    • libfaceTrackerSDK.a(人脸捕捉SDK)
    • libKiwiFaceSDK.a(UI+视频帧渲染SDK)
  • 系统库 (xcode自带)
    • UIKit.framework
    • Foundation.framework

第三步:部署工程

1. 导入 libKiwiFaceSDK.a 文件和所有包含的头文件、实现文件、资源文件。(我们提供了两种libKiwiFaceSDK.a文件。libKiwiFaceSDK.a同时支持模拟器与真机,供开发调试使用。libKiwiFaceSDK_release.a仅支持真机,供发布使用。)

  ![](images/KiwiFaceSDK.jpg)

2. 导入Tracker 人脸捕捉的SDK包和StickerManager。(我们提供了两种libfaceTrackerSDK.a文件。libfaceTrackerSDK_lic.a同时支持模拟器与真机,供开发调试使用。libfaceTrackerSDK_release.a仅支持真机,供发布使用。)

  ![](images/Tracker.jpg)

  ![](images/StickerManager.jpg)

  这里注意tracker包的models文件夹和StickerManager文件夹下的stickers贴纸文件夹,必须只是导入引用,不要包含到项目工程里面来(文件夹图标为蓝色)。如下图设置:

  ![](images/target-settings.png)

3. 导入GPUImage,用于视频渲染(我们提供了两种libGPUImage.a文件。libGPUImage.a同时支持模拟器与真机,供开发调试使用。libGPUImage_release.a仅支持真机,供发布使用。)

4. 如有需要,导入libyuv

  sdk视频帧的渲染暂时只支持NV21格式的传入 如果应用视频帧是YUV或者其他视频流类型 需要导入视频流格式的转换类。

5. 导入
opencv3.framework

  注意,从官网下载的包有可能被错误的命名为opencv2.framework。

第四步:贴纸配置

如果有需要,请配置贴纸。贴纸相关文件存放在stickers目录下,一套贴纸对应一个目录,每套贴纸包含一个config.json文件,其中配置了音效文件名及每个item参数等信息。其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|--[sticker_1] (贴纸1)
| |--config.json (贴纸配置文件)
| |--[audio](音频文件)
| |--[preview](贴纸预览图)
| |--[item_1](贴纸序列图文件夹1)
| | |--[frame_1](贴纸序列图1)
| | |--[frame_2](贴纸序列图2)
| | |--...
| | |--[frame_n](贴纸序列图n)
| |--[item_2](贴纸序列图文件夹2)
| |--...
| |--[item_n](贴纸序列图文件夹n)
|--[sticker_2](贴纸2)
|--...
|--[sticker_n](贴纸n)
|—StickerConfig.json(总配置文件)

程序靠读取在stickers文件夹下的StickerConfig.json显示相应的贴纸和图标。

注意,使用贴纸云,需要在Info.plist中加入App Transport Security Settings字段,并将Allow Arbitrary Loads设置为YES。
具体的json文件格式如下:

StickerConfig.json

参数名称 意义
name 贴纸的名称(UI显示和贴纸的识别)
dir 贴纸存放路径文件夹名称
category 类别(贴纸类型的区分或分组)
thumb 贴纸图标的文件名(与声音在同一文件夹下)
voiced true(有声音)false(没有声音播放)
downloaded 是否已经下载。如果没有下载,程序则可以去下载到指定目录后更改该状态

config.json

参数名称 意义
type 贴纸显示的位置类型(脸部、全屏)
facePos 贴纸在脸部的位置
scaleWidthOffset 贴纸宽度缩放系数
scaleHeightOffset 贴纸高度缩放系数
scaleXOffset 贴纸在脸部水平方向偏移系数
scaleYOffset 贴纸在脸部垂直方向偏移系数
alignPos 边缘item参数
alignX 边缘水平方向偏移系数
alignY 边缘垂直方向系数
frameFolder 贴纸资源目录(包括一组图片序列帧)
frameNum 帧数(一组序列帧组成一个动画效果)
frameDuration 每帧的间隔(秒)
frameWidth 图片的宽
frameHeight 图片的高
trigerType 触发条件,默认0,始终显示

编写config.json文件可使用我司提供的工具 https://apps.kiwiapp.mobi/sticker.html 进行调试生成。

第五步:调用API

使用SDK内置UI

如果直接使用我们SDK内置的UI, 可以在页面的viewDidload里面初始化SDK。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 获得SDK操作类的实例对象 */
self.kwSdkUI = [KiwiFaceSDK_UI shareManagerUI];
/* 设置SDK内置UI的 ViewController 如果不用内置UI 不用设置 */
[self.kwSdkUI setViewDelegate:self];
/* 初始化SDK 一些渲染对象以及初始参数 */
[self.kwSdkUI.kwSdk initSdk];
/* 如果使用内置UI 该属性是判断是否清除原有项目的页面UI 如果原有UI功能少 可以用内置UI 替代 一般来说用不到 */
self.kwSdkUI.isClearOldUI = NO;
/* 初始化内置UI */
[self.kwSdkUI initSDKUI];
/* 渲染视频帧,在每一帧视频代理函数中调用 */
self.kwSdkUI.kwSdk = [KiwiFaceSDK sharedManager];
/*
对每一帧的视频图像进行人脸捕捉 并对当前选择的滤镜进行视频帧渲染
pixelBuffer:每一帧的像素流
cvMobileRotate :屏幕方向(横竖屏)
mirrored: 是否是镜像
*/
[kwSdk.renderer processPixelBuffer:pixelBuffer withRotation:cvMobileRotate mirrored:mirrored];
使用sdk自带功能
  • 初始化具体功能:

    • 初始化普通滤镜或贴纸集合

      1
      2
      3
      4
      5
      6
      self.filters = @[
      //描点
      [FTPointsRenderer new],
      //贴纸
      [FTStickerRenderer new]
      ];
    • 初始化哈哈镜滤镜集合

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      self.distortionFilters = @[
      //方脸
      [GPUImageSquareFaceFilter new],
      //ET脸
      [ETnewFilter new],
      //胖脸
      [FatFaceFilter new],
      //蛇精脸
      [SlimFaceFilter new],
      //梨脸
      [PearFaceDistortionFilter new]
      ];
    • 初始化美颜滤镜集合

      1
      2
      3
      4
      5
      6
      7
      8
      9
      self.beautifyFilters = @[
      //大眼
      [SmallFaceBigEyeFilter new]
      ];
      self.beautifyNewFilters = @[
      //美颜
      [GPUImageBeautifyFilter new]
      ];
    • 初始化全局滤镜集合

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      self.lookupFilters = @[
      //Nature
      [[FTLookupFilter alloc] initWithType:FTLookupTypeNature],
      //Sweety
      [[FTLookupFilter alloc] initWithType:FTLookupTypeSweety],
      //Clean
      [[FTLookupFilter alloc] initWithType:FTLookupTypeClean],
      //Peach
      [[FTLookupFilter alloc] initWithType:FTLookupTypePeach],
      //Rosy
      [[FTLookupFilter alloc] initWithType:FTLookupTypeRosy],
      //Urban
      [[FTLookupFilter alloc] initWithType:FTLookupTypeUrban]
      ];
  • 调用具体功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //调用美颜
    [self.renderer addFilter:self.beautifyNewFilters[0]];
    //调用描点
    [self.renderer addFilter:self.filters[0]];
    //调用哈哈镜
    [self.renderer addFilter:self.currentDistortionFilter];
    //调用滤镜
    [self.renderer addFilter:self.currentLookupFilter];
    //调用贴纸
    [self.renderer addFilter:self.filters[1]];
  • 去除具体功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //去除美颜
    [self.renderer removeFilter:self.beautifyNewFilters[0]];
    //去除描点
    [self.renderer removeFilter:self.filters[0]];
    //去除哈哈镜
    [self.renderer removeFilter:self.currentDistortionFilter];
    //去除滤镜
    [self.renderer removeFilter:self.currentLookupFilter];
    //去除贴纸
    [(FTStickerRenderer *)self.filters[1] setSticker:nil];
自定义功能扩展
  • 增加特定滤镜,进行渲染:

    在 sdk入口类中,有一个类型为KWRenderer的渲染类,由他来控制滤镜的增加。

    1
    [KiwiFaceSDK.KWRenderer addFilter: GPUImageOutput<GPUImageInput, KWRenderProtocol> *];

    滤镜对象必须遵守GPUImageInput和KWRenderProtocol两个协议才能正常被人脸捕捉和渲染。

  • 删除特定滤镜,停止渲染:

    1
    [KiwiFaceSDK.KWRenderer removeFilter: GPUImageOutput<GPUImageInput, KWRenderProtocol> *];
  • 人脸捕捉之后,在渲染视频帧之前可以对每一帧图像做自定义处理的回调block:

    1
    2
    3
    typedef void (^RenderAndGetFacePointsBlock)(unsigned char *pixels, int format, int width, int height,result_68_t *p_result, int rstNum, int orientation,int faceNum);
    //block属性
    @property (nonatomic, copy)RenderAndGetFacePointsBlock kwRenderBlock;

    block 回调用于在人脸捕捉之后,渲染之前,可以对视频帧进行自定已处理的接口。Block的3个参数可供处理和使用:

    • result :人脸坐标集合 (可能是多张人脸 是二维数组)
    • faceNum :捕捉到的人脸数量
    • pixelBuffer:单帧的像素流
释放内存

我们建议在离开页面的时候释放内存

1
[KiwiFaceSDK releaseManager];

Dispatch Semaphore & NSCondition

dispatch_semaphore 使用 http://www.cnblogs.com/snailHL/p/3906112.html

两个作用

加锁/数据同步

dispatch_semaphore_create

dispatch_semaphore_signal

dispatch_semaphore_wait

1
2
3
- (void)methodWithABlock:(DHBLK)blk{
blk(@"hahaha");
}

测试代码1 单个block

1
2
3
4
5
6
7
8
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self methodWithABlock:^(id result){
//写block中做的事情
//结束等待
dispatch_semaphore_signal(sem);
}];
//等待信号
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

测试代码2 多个 blcok

xxx.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[self methodWithABlock:^(id result){
//写block中做的事情
dispatch_semaphore_signal(sem);
[self methodWithABlock:^(id result){
//写block中做的事情
dispatch_semaphore_signal(sem);
}];
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

测试代码3

1
2
3
4
5
6
7
8
9
10
11
12
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int j = 0;
dispatch_async(queue, ^{
j = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%d",j);
// j = 100

测试代码 4 加锁测试 ,反注释掉//

1
2
3
4
5
6
7
8
for (int i = 0; i < 100; i++) {
dispatch_async(queue, ^{
// 相当于加锁
// dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"i = %zd semaphore = %@", i, semaphore);
// 相当于解锁
// dispatch_semaphore_signal(semaphore);
});

NSCondition 的使用

NSCondition 的对象实际上作为一个锁和一个线程检查器:锁主要为了当检测条件时保护数据源,执行条件引发的任务;线程检查器主要是根据条件决定是否继续运行线程,即线程是否被阻塞。
http://www.jianshu.com/p/5d20c15ae690

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
NSCondition *condition = [[NSCondition alloc] init];
__block NSMutableArray *products = [[NSMutableArray alloc] initWithCapacity:10];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[condition lock];
while ([products count] == 0) {
NSLog(@"wait for products ");
[condition wait];
}
[products removeObjectAtIndex:0];
NSLog(@"custome a product");
[condition unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[condition lock];
products = [NSMutableArray array];
[products addObject:@"oo"];
NSLog(@"produce a obj");
[condition signal];
[condition unlock];
});

异步下载图片完成后同步完成数据库再通知UI刷新的机制

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
- (void)receiveMessage:(MessageEntity *)message
{
NSCondition *condition = [[NSCondition alloc]init];
[condition lock];
NSInteger imageCount ;
void(^unlock)=^(){
while(!imageCount)
[condition signal];
}
imageCount++;
dowanloadImageFinished:^(UIImage *image){ //异步下载图片,下载完成的回调中
imageCount--;
unlock();
}
while(imageCount){
[condition wait];
}
[condition unLock];
}

以GPUImage 实现的延时摄影

用 semaphore 来筛选(关断)出时间间隔的图像 buffter.筛选出来的buffer 再筛入 WriterInputPixelBufferAdaptor.

https://github.com/yangfangxue/GPUImageRecord.git

1.dispatch timer 的用法

1
2
3
4
5
6
7
8
9
10
__weak typeof(self) ws =self;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 0.5 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
[ws defaultTimerHandel];
});
_seam = dispatch_semaphore_create(0);

//使用 timer 和销毁

1
2
3
dispatch_resume(_timer);
dispatch_cancel(_timer);

GPUImageVideoCamera 中回调方法CMSampleBufferRef的 lock

1
2
3
4
5
6
7
[_camera addTarget:self.preview];
//获取buffer
CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer, 0);
_imageBuffer = CVPixelBufferRetain(imageBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);

录制配置路径的时候需要检测是否存在,清除历史文件

完成录制时候加入condition 保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[writerInput markAsFinished];
if(videoWriter.status == AVAssetWriterStatusWriting){
NSCondition *cond = [[NSCondition alloc] init];
[cond lock];
[videoWriter finishWritingWithCompletionHandler:^{
[cond lock];
[cond signal];
[cond unlock];
}];
[cond wait];
[cond unlock];
[self savePhotoCmare:self.url];
}