通过cloudflare白嫖服务资源

cloudflare免费功能介绍

cloudflare pages之前和github pages一样, 都只是提供静态服务, 也有一个cloudflare workers提供的是类似aws lambda的功能; 现在pages也集成了workers, 就可以使用一个完整的功能了, 关键还是免费的

pages

静态站点容器, 类似自带CI功能的nginx; 缺点是需要绑定自己的域名, 因为开发测试域名已经被墙了

workers

函数计算的容器, 感觉不是那么好用, 而且强绑定运行环境, 但是白嫖嘛要求不要太多
支持所有可以被编译为webassambly的语言, 比如rust, python, c啥的

D1

关系型数据库, 配合worker使用的数据库
免费额度为5GB的总容量与 每天5m row读取与 100k写入
考虑到后期迁移的话推荐用这个方案, 毕竟关系型数据库到处都是, 但反过来说小应用的话也就够了

KV

kv数据库, 用着当session控制和一些不复杂的数据存储
免费额度总量为1G, 每天100k read, 1k write/delete

总结

如果简单功能的话, 还是非常不错的, 服务稳定, 架个博客啥的也不用担心掉线

其他相关免费产品

总的来说免费额度基本差不多, 都或多或少被墙, 相比之下cloudflare稳定性好很多

品牌 主要额度 被墙状态 功能性 推荐度
Vercel 100GB bandwidth
100 build hours
部分/间歇 SaaS 2/5
Netfliy 100GB bandwidth
300 build minutes
部分/间歇 SaaS 3/5
Github Pages 100GB bandwidth 部分/间歇 纯静态站点 3/5

Dart-Simple-DI

这是什么

需求描述

需求就是为了项目间解耦, 需要一个简单的DI工具
目标是可以直接通过代码直接能提示出目前已经注册好的类

当前情况

目前已有的方案是直接用一个类常驻内存作为一个中转站
使用效果为

1
2
3
4
5
6
7
8
9
// 注册一个名为[VideoRewardWrapper]的类
SharedStateStore().set<VideoRewardWrapper>(
PredefinedKeys.videoRewardManager, VideoRewardManager());

// 使用的时候指定具体的类型和key来获取之前在内存中注册的类
SharedStateStore().get<VideoRewardWrapper>(
PredefinedKeys.videoRewardManager)).ifPresent((v) {
/* using it do something */
});

这个方案最大的问题是需要手敲具体的类和key, 从一个黑盒里面拿东西感觉总是不太好

期望的形态

注册成功的依赖都直接是Provider的成员, 现在的形式如果不是自己写的话很可能就不知道是否有这个东西

1
DependenciesProvider.

调查的一些方案

Injector, 使用起来效果和SharedStateStore效果差不多, 只是这个的key是可选参数, 这个方案的好处就是简单, 但是因为和已有方案基本一致, 再引入这个依赖意思不大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Use this static instance
final injector = Injector.appInstance;
// Register a dependency
injector.registerDependency<Engine>(() => Engine());

injector.registerDependency<Car>(() {
final engine = injector.get<Engine>();
return CarImpl(engine: engine);
});

//Use custom Factories by extending [Factory]
injector.register(CustomFactory(() => Engine()));

// Maybe you want to register a class and you need it as a singleton
injector.registerSingleton<Database>(() => TikkrDatabase());

// Now you can easily get your dependencies / singletons with one line
database = injector.get<Database>();
customerCar = injector.get<Car>();

Qinject可以达到需求的目标, 它通过annotation来进行必要的标记说明, 然后通过builder runner根据之前的指示生成具体的代码. 这个库比较高级, 好多参数都能通过标记来解决, 有种Spring DI的感觉.
可是每次改代码都得重新生成一下代码, 感觉又没必要这么重. 同时这种方案如何处理潜在的循环依赖/依赖初始化顺序问题也没细看. 这个方案备选

最终选定的方案

基于之前调研的内容, 最后决定直接沿用SharedStateStore, 然后加extension. 这样既没有增加很多复杂度, 又达成了需求的目标; 但弊端是声明与实现分离了, 同时也没有隐藏填充的细节(Qinject是生成的所以也算隐藏了细节), 这就是这个方案所必须承受的代价了.

1
2
3
4
5
6
7
8
extension Privider on SharedStateStore {
// 只要在这边明确声明可获取的类型就好了
AdManager? get adManager {
// 尴尬的地方就是需要在别处set, 如果也聚合在这里的话就又比较臃肿
final m = get<Admanager>('ad_manager_key').orNull;
return m;
}
}

部分实现

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
/// SharedStateStore.dart
import 'dart:async';
import 'dart:collection';
import 'package:quiver/core.dart';
import 'package:tuple/tuple.dart';

class SharedStateStore {
static final SharedStateStore _singleton = SharedStateStore._internal();
final Map<String, dynamic> _store = <String, dynamic>{};
late StreamController<Tuple2<String, dynamic>> _streamController;

factory SharedStateStore() {
return _singleton;
}

SharedStateStore._internal() {
_streamController = StreamController.broadcast(sync: false);
}

void set<T>(String key, T value, {bool withNotify = true}) {
_store[key] = value;
if (withNotify) {
_streamController.add(Tuple2<String, dynamic>(key, value));
}
}

Optional<T> get<T>(String key) {
final obj = _store[key];
if (obj == null || obj is T) {
return Optional.fromNullable(_store[key]);
}
throw TypeError();
}

void invalid(String key) {
_store.remove(key);
_streamController.add(Tuple2<String, dynamic>(key, null));
}

void clear() {
_store.clear();
}

Stream<Optional<T>> onKeyChange<T>(String key) {
return _streamController.stream
.where((e) => e.item1 == key)
.map((e) => Optional.fromNullable(e.item2));
}

Map<String, dynamic> get store => UnmodifiableMapView(_store);
}

此模块已经发布在pub

CLUT-from-images

generate color lookup table from origin image and result images

颜色查找表(Color LookUp Table)用蓝色作为索引, 用来分块, 每个小块的蓝色是固定的, 其中x轴是红色, y轴是绿色

CLUT原理

在这里我们以18bit色作为样例说明, 使用18bit色深主要是出于存储空间考虑, 18bit = 2^18 = 262144就是通常说的26万色, 这26万色可以分解成2^18 = (2^9) ^ 2 = 512 * 512, 所以通常CLUT也是一个512 x 512的图片

从图片大小来看宽高512的总像素数是262144, 假设我们的图片是24位色–也就是每个通道用8bit, 总共3byte, 那么最后图片大小是262144 * 3 = 786432也就是768KB.
同样的如果使用24位色的话就需要使用宽高4096的图片, 算下来最后图片大小有48MB

虽然图片精度下降了一些, 但是带来的空间节省却是巨大的, 把48MB的图片加载进GPU也肯定比768KB耗时更长

18bit色深

RGB每个通道使用6bit来表示颜色, 每个通道的集合应该是[0, 255], 但是2^6 = 64, 范围就是[0, 64] 所以最后的结果需要乘4来变换到[0, 255]

枚举所有颜色的查找表

这是一个8x8的查找表, 总共有64个block, 所以范围是[0, 64], 又因为每个block中蓝色是固定的, 所以蓝色可以完全表示
其中每个block中从左到右是红色, 取值范围是[0, 64], 从上到下是绿色取值范围是[0, 64], 这样所有的颜色就都可以表示了

查找表

1
2
3
4
5
6
7
8
9
10
11
img = np.zeros((512, 512, 3), dtype=np.uint8)
for by in range(8): # block y索引
for bx in range(8): # block x索引
for g in range(64): # block内 y索引
for r in range(64): # block内 x索引
x = r + bx * 64 # 整张图的 x坐标
y = g + by * 64 # 整张图的 y坐标
img[y][x][0] = int(r * 255.0 / 63.0 + 0.5)
img[y][x][1] = int(g * 255.0 / 63.0 + 0.5)
img[y][x][2] = int((bx + by * 8.0) * 255.0 / 63.0 + 0.5)
# 每个点的(r, g, b)计算方法, 可以看出每个块内的蓝色都是一定的

使用查找表的过程

我们有了查找表之后要得到色彩C 对应查找表内的颜色 需使用如下方法:

  1. 分离色彩C的RGB通道为(Cr, Cg, Cb)
  2. 通过Cb计算block的位置, y = Cb // 4 // 8 x = Cb % 8
  3. block位置确定之后就在里面通过Cr, Cb计算具体的点P
  4. 取出点P的RGB通道(Pr, Pg, Pb)
  5. 目标的RGB值就是(Pr, Pg, Pb)

新查找表生成方法

因为这就是一个查表过程, 所以得到新滤镜的方法就很简单了

  1. 准备一张完整的查找表图片, 这里称为identity
  2. 然后用滤镜软件处理identity, 得到一张新的图片D

D就是我们新的查找表了, 因为本质上我们是用原集合(identity), 经过函数f(滤镜软件x)得到了新的集合, 所以就可以直接使用D了

因为identity包含了所有可能的颜色, 并且都在对应的位置上, 所以目标查找表B上面的位置都是正确的, 只要取出对应的RGB通道就是滤镜后的效果了

总结

其实颜色查找表是很聪明的一个构造方法, 把原色彩的RGB分别作为坐标轴, 对应像素的内容才是真正的值

色彩查找表构造Demo

Native应用集成ReactNative实践

ReactNative

去年Facebook发布了ReactNative之后, 各大厂也都反应积极, 今天就来抛抛砖。
ReactNative介绍在这里就不赘述了, 这里是官方介绍, 民间版在这里.
大家一般肯定有是有现成项目的, 完全新开一个项目可能性比较小, 所以集成就是重中之重了。

目标

在已有项目中我们使用ReactNative实现一个功能模块, 使其可以与Native代码进行通讯, 使其可以从ReactNative跳到Native

实践

Facebook官方教程里面有关于如何集成的教程, 安卓版, iOS版
我们主要描述一下iOS版的过程, 必要条件请移步这里
大概熟悉一下之后, 进行如下步骤

  • 首先建一个文件夹, 我们就叫ReactNativeDemo, 之后

    Read More

通过git hook实现hexo博客自动发布

不觉间这个博客已经弄了小半年了,确实更新频率太低,而且每次发布的时候比较麻烦,虽说hexo提供了好多部署方式方法,试了下rsync方案感觉是特别好用, 另外这个博客作者不止一个,还有权限问题。
hexo自带git部署不过好像和我的需求不太一样

现在的方案

现在傻傻的方案是这样的,有个git仓库,每次写完了先push一下,然后到hexo的工程里面hexo generate一下,生成HTML之后手动rsync到web服务器上

虽然不复杂,但是步骤比较多,每次都得重复,本着DRY原则, 还是要把过程自动化

切入点

很明显,希望自动化整个过程的话就是在push之后来操作了,那么这就又有两条路,客户端和服务端,客户端就是现有方案了,所以瞄到服务端去,在服务端收到提交的代码之后来操作,于是git hook就是顺理成章了。

Read More

MKMapView setRegion crash

Invalid Region <center:+0.00000000, +0.00000000 span:+180.00000000, +361.00000000>

今天来解决MKMapView setRegion程序奔溃的问题,具体提示错误信息为:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid Region <center:+0.00000000, +0.00000000 span:+180.00000000, +361.00000000>'

我们设置mapView的region时,会创建一个region对象,region包含两块信息,一个是coordinate2D,一个是coordinateSpan,coordinate2D包含经纬度信息,coordinateSpan包含经纬度跨度信息:

1
2
3
4
let coordinate2D = CLLocationCoordinate2D(latitude: -31, longitude: 179)
let span = MKCoordinateSpan(latitudeDelta: 180, longitudeDelta: 360)
let region = MKCoordinateRegion(center: coordinate2D, span: span)
mapView.setRegion(region, animated: true)

先来简单说下iOS中的经纬度范围:

经度,以本初子午线为分界线(longitude为0),向东为东经,向西为西经,东经为正数(0180),西经为负数(-1800),所以经度的范围是:-180~180;

纬度,以赤道为分界线(latitude为0),向北为北纬,向南为南纬,北纬为正数(090),南纬为负数(-900),所以纬度的范围是:-90~90;

再来看一下经纬度跨度范围:

MKCoordinateSpan是mapView的展示区域范围,由于经度范围是-180180,纬度范围是-9090,所以loatitudeDelta范围是0360,latitudeDelta范围是0180,也就是mapView显示最大经度范围是360度,最大纬度范围是180度

注意

在iOS中,经纬度数值与显示跨度范围必须在上面提到的范围之内,不然就会出现上面的错误信息。

通过Swift closure让GCD调用变得更优雅

刚才看到一个博客
讲了开源的应用Swiflytics制作的一些内容,其中对于dispatch_async的处理很漂亮,原本我们肯定这样写

1
2
3
dispatch_async(dispatch_get_main_queue(), { _ in
// blah blah …
})

作者通过写接一个closure的函数实现了像代码块儿一样的优雅调用

1
2
3
4
5
6
7
8
func onMainThread(closure: () -> ()) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
closure()
})
}

// 调用方
onMainThread { completion(onlineUsers) }

因为swift里面最后一个参数如果是closure的话可以直接通过大括号包裹起来这么写所以使上面的写法成为可能
顺便说下完整调用应该是这样的

1
2
3
onMainThread(closure: { () -> () in
completion(onlineUsers)
})

但是因为swift编译器很聪明所以可以省略成这样子。

在知道了这些以后,

Read More

Enum驱动的ViewControllers

最近看到这么一个博客
讲到了通过Enum来把ViewController变成一个状态机,通过状态的变化来确定或者执行对应的操作

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
class ProductListViewController: UIViewController {
enum State {
case Loading
case Empty
case Loaded([Product])
case Error(NSError)
}
var state = State.Loading {
didSet {
... update views ...
}
}
...
func loadProducts() {
state = .Loading
ProductManager.sharedManager.requestProducts(success: { products in
if products.count > 0 {
self.state = .Loaded(products)
} else {
self.state = .Empty
}
}, failure: { error in
self.state = .Error(error)
})
}
}

以上是博客中的一段代码,

Read More

银行卡格式化

昨天有这么个需求, 在输入或显示银行卡号码的时候添加空格进行分段, 可读性明显就提升了, 用户友好了,
但是在输入的时候怎么加呢? 今天就来解决一下.
因为是iOS的项目要求, 所以我们用swift实现(写这个文章的时候是swift 2.1)

1
2
3
4
// 给出银行卡号如下
4292xxxxxxxxxxxx
// 输出为
4392 xxxx xxxx xxxx

实现过程

过程其实感觉很简单, 只要每4个字符添加一个空格就好, 我觉得比较方便的做法就是先把字符串转换成字符数组

1
2
3
4
5
6
let cardNumber = "4292xxxxxxxxxxxx"
let cardNumberArray = Array<Character>(cardNumber.characters) // 指定Array的元素为 Character
print(cardNumberArray)

// output
["4", "2", "9", "2", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x"]

变成了这样之后就该往里面加空格了, 很自然的就想着从前面往后面加了, 但是这有个问题

Read More

Map, Filter, Reduce in iOS

这三个函数在编程语言中非常的实用,所以今天非常有必要跟大家探讨一下这三个函数的使用方法,我们拿iOS中的使用为例。

在正式介绍这三个函数之前,我们先提供一个资源:一个班级所有学生的一次考试成绩。

首先,我们先创建学生这个类型:
Student.h

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
@interface Student : NSObject
@property (nonatomic, strong) NSString * name;
@property (nonatomic, assign) NSInteger math;
@property (nonatomic, assign) NSInteger chinese;
@property (nonatomic, assign) NSInteger english;

+ (instancetype)newStudentWithName:(NSString *)name
math:(NSInteger)math
chinese:(NSInteger)chinese
english:(NSInteger)english;
@end

@implementation Student

+ (instancetype)newStudentWithName:(NSString *)name
math:(NSInteger)math
chinese:(NSInteger)chinese
english:(NSInteger)english
{
Student * student = [[Student alloc] init];
student.name = name;
student.math = math;
student.chinese = chinese;
student.english = english;
return student;
}
1
2
3
4
5
6
struct Student {
let name: String
let math: Int
let chinese: Int
let english: Int
}

然后,我们创建一个班级的所有学生成绩信息(这里只提供5位同学):

1
2
3
4
5
6
7
NSArray * students = @[
[Student newStudentWithName:@"Helen" math:79 chinese:98 english:89],
[Student newStudentWithName:@"Jack" math:90 chinese:80 english:76],
[Student newStudentWithName:@"Bomb" math:98 chinese:43 english:79],
[Student newStudentWithName:@"Joke" math:56 chinese:87 english:30],
[Student newStudentWithName:@"Marry" math:99 chinese:60 english:32]
];
1
2
3
4
5
6
7
let students = [
Student(name: "Helen", math: 79, chinese: 98, english: 89),
Student(name: "Jack", math: 90, chinese: 80, english: 76),
Student(name: "Bomb", math: 98, chinese: 43, english: 79),
Student(name: "Joke", math: 56, chinese: 87, english: 30),
Student(name: "Marry", math: 99, chinese: 60, english: 32)
]

接下来,正式介绍这三个函数的作用:

Map

Map:一个数组到另一个数组的映射。

俗称将一个数组通过一定的运算法则转化为另一个数组,这两个数组里的元素个数是一样的,但是元素类型是可以不一样的。

[a] -> [b]

以上面的例子,要想取出所有同学的数学成绩,这个时候就可以考虑到map:

1
2
3
let maths = students.map { (student) -> Int in
return student.math
}

其中students为原数组,maths为目标数组,map后面的大括号为转换法则。

这里用了Swift里的Closures

遗憾的是,在Objective-C里,没有map,没有。。。
当然,我们也可以使用原始的方法for去遍历,也可以采用Objective-C里的enumerate系列方法,但是,当爱上了map,filter,reduce以后,我希望我直接调用的还是这几个函数,于是,我们来扩展NSArray类:

1
2
3
4
5
6
// NSArray+Founctional.h
@interface NSArray (Functional)

- (NSArray *)map:(id (^) (id obj))block;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// NSArray+Founctional.m
@implementation NSArray (Functional)
- (NSArray *)map:(id (^) (id obj))block {
NSMutableArray *array = [[NSMutableArray alloc] init];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id element = block(obj);
if (element) {
[array addObject:element];
}
}];
return array;
}

@end

于是,还是上面的那个例子,在Objective-C中的实现为:

1
2
3
NSArray * maths = [students map:^id(Student * obj) {
return @(obj.math);
}];

当然,上面的原数组是创建的一些假数据,如果你有一些真实的数据,也可以通过map来将数据源转化为Student模型。

Filter

Filter:过滤。

一个数组通过一定的运算法则过滤掉相应的元素,成为另一个数组,这两个数组里的元素个数是不一样的,但是元素类型是一样的。

[a] -> [a]
eg.我们用filter来取出所有语文成绩及格的同学:

1
2
3
let passStudents = students.filter { (student) -> Bool in
return student.chinese >= 60
}

同样的,students为原数组,passStudents为目标数组,filter后面的大括号为过滤法则。

在Objective-C中还是没有filter函数,我们还是以同样的方法来扩展NSArray:
在上面的NSArray+Founctional.h中添加:

1
- (NSArray *)filter:(BOOL (^) (id obj))block;

在NSArray+Founctional.m中添加:

1
2
3
4
5
6
7
8
9
10
- (NSArray *)filter:(BOOL (^) (id obj))block {
NSMutableArray *array = [[NSMutableArray alloc] init];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL element = block(obj);
if (element) {
[array addObject:obj];
}
}];
return array;
}

上面的例子在oc中样式:

1
2
3
NSArray * passStudents = [students filter:^BOOL(Student * obj) {
return obj.chinese >= 60;
}];

Reduce

Reduce:以一个数组为数据源,通过一定的运算法则,最后合成另一种类型,在有些编程语言中起名foldr.

[a] -> b

eg.我们用reduce来算出所有学生的英语成绩之和:

1
2
3
let totalScore = students.reduce(0) { (sum, student) -> Int in
return sum + student.english
}

标:reduce后面的0为初始值,sum为每次循环之前算出的总和,student为本次循环的学生模型。

组合使用

当然,这三个函数也可以组合起来使用,来完成稍微复杂点的需求。
eg.我们要获取语文成绩及格的的所有学生姓名:

1
2
3
4
5
students.filter { (student) -> Bool in
return student.chinese >= 60
}.map { (student) -> String in
return student.name
}

eg.三门成绩都及格的所有同学的数学成绩之和:

1
2
3
4
5
6
7
students.filter { (student) -> Bool in
return student.math >= 60
&& student.chinese >= 60
&& student.english >= 60
}.reduce(0) { (sum, student) -> Int in
return sum + student.english
}