BLE 数据包的解析与组装实践

BLE 低功耗蓝牙是现在很多智能硬件产品的必备模块,今天分享一个基于 BLE 4.0 协议的开发实战案例。

相关知识点

数据大小端

即数据在内存布局中的字节序问题,比如 0x1122,在大端模式中 0x11 在低字节位,0x22 在高字节位,小端模式中相反,大小端模式( Big Endian / Little Endian )也可记为大尾端模式/小尾端模式。

图1-Big/Little Endian

TCP/IP 协议规定了网络字节序为大端模式。

实战

数据包的大小

BLE 4.0 协议规定了 payload 最大 27 字节,L2CAP 的头占去了 4 个字节,剩下 23 字节的 MTU,其中 ATT 层会用掉 1 个字节作为 op code,2 个字节作为 attribute handle,最后剩下的 20 个字节就可以作为我们今天案例的用武之地了。

分片包组成结构

今天分享的案例中,假定我们已经和硬件模块约定好了分片包的组成结构,最长为 20 个字节,我们用内存地址 0-19 简单表示其结构如下图所示:

图2-分片包组成结构

具体的:

  1. 第 1 包 offset 为 0
  2. 第 1 包 endflag 为 0 表示本包为分片包,为 1 表示本包为非分片包,非第 1 包中,endflag 为 0 表示非结束包,为 1 表示最后的包
  3. 第 1 包中,taildata 为 7-19 bytes
  4. 非第 1 包中,taildata 为 5-len bytes

数据包解码

有时我们需要对原始数据做一些简单处理后再传输,这时就需要约定编/解码规则,比如:

  1. 第 1、2 字节为固定字符
  2. 剩余字节采用 Base64 编码
  3. 约定具体字段所在字节位
  4. 约定 crc 多项式、宽度、初始值等校验模型
  5. 处理多字节字段的网络字节序

相关资源:

  1. libcrc - Multi platform CRC library
  2. CRC(循环冗余校验)在线计算

最后

好的,今天就分享到这里吧!谢谢 各位看官辛苦了💦!如果喜欢请赏个雪糕🍦吃呗大爷,打个赏呗!

Promise/Future 模式在 iOS 上的实践 - 警惕回调地狱

关键概念:

  • 回调模式
  • 回调地狱
  • Promise/Future 模式
  • PromiseKit - Max Howell
  • Bolts/Tasks - Facebook

常见回调模式

一般是一个地方调用,然后需要在另一个地方等待回调,Objective-C 里有时会在 block 里等待回调。举个栗子🌰,我们来到兰州拉面馆,

  1. “老板给我拉个面”“大份小份”“大份大份”“好嘞,马上就好”(成功发布一个制作拉面的任务),
  2. 然后我们找个地方坐下,看看手(妹)机(纸),
  3. 过了会儿,
  4. “客官,您要的面”只见一个兰州妹纸端过来一大碗正是刚才我们点的兰州拉面(此时既是异步任务完成,来到了回调节点)。
1
2
3
4
5
6
// 发布任务(触发节点)
[LanZhouMianGuan makeLaMianWithCompletion:^(BOOL finished) {
if (finished) {
// 制作完成(回调节点)
}
}];

回调地狱

回调模式是常见处理异步任务的一种模式,但是面对具有复杂依赖关系的异步任务时,回调模式因为任务触发点和回调点不在一个地方,或者 block 嵌套层数过多,导致内部逻辑十分复杂难以维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 发布任务(触发节点)
[LanZhouMianGuan makeLaMianWithCompletion:^(BOOL finished) {
if (finished) {
// 拉面制作完成(回调节点)
[LanZhouMianGuan makeDaPanJiWithCompletion:^(BOOL finished) {
if (finished) {
// 大盘鸡制作完成(回调节点)
[LanZhouMianGuan makeXiHongShiDanWithCompletion:^(BOOL finished) {
if (finished) {
// 西红柿蛋制作完成(回调节点)
[LanZhouMianGuan makeTuDouSiWithCompletion:^(BOOL finished) {
if (finished) {
// 土豆丝制作完成(回调节点)
}
}];
}
}];
}
}];
}
}];

Promise/Future 模式

Promise/Future 模式也可以用来处理异步任务协同,它有一个关键概念票据,票据里包含该异步任务完成程度的各种状态信息,发布命令时持有该票据,然后后续相关依赖的异步任务也都返回一个票据,然后只需要在一个地方根据持有的各种票据处理所有相关任务的依赖关系,既达到了在一个更高的抽象层单独维护这些依赖关系的效果,从而使得逻辑更清晰。

PromiseKit

这是一个开源库,作者 Max Howell,此人也是 Homebrew 的作者,这里有段故事,据说当年 Max 参加 Google 的面试,因不(拒)会(绝)写反转二叉树而没有拿到 offer,江湖中也算得上一号人物。PromiseKit 这个库还是蛮大的,据说为了完成这个库前后总共花费了 Max 数千小时的工作量,嗯,数千小时,假设1天2小时,一年365天的话,1000小时也得差不多一年半的时间,嗯,牛。PromiseKit 传送门

Bolts/Tasks

(咳咳,嗯,主角要出场了)今天我们要讲的这个 Bolts 乃是挂着互联网科技公司巨头 Facebook 的名号,嗯……听上去来头不小,那我们下面就看看它到底有神马能耐。Bolts-ObjC 俺们也有传送门

Bolts/Tasks 来者何人

  • 基于 Promise/Future 模式,用来处理复杂异步依赖关系
  • 能避免常见的代码回调地狱
  • Facebook 大厂出品
  • 基于 Objective-C,也很好地兼容到了 Swift
  • 并不是 NSOperation 或 GCD 的替代,而是作为补充可以一起配合使用
  • 简洁明了、易理解

嗯,嗯……其实我喜欢这个库是因为在 Objective-C 中,其使用场景的代码非常简洁、逻辑清晰,就一个字优雅,不对

Bolts/Tasks 内部原理

来来来,围观下这么神奇的功能到底是肿么实现的呢?

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
+ (instancetype)taskForCompletionOfAnyTask:(nullable NSArray<BFTask *> *)tasks
{
__block int32_t total = (int32_t)tasks.count;
if (total == 0) {
return [self taskWithResult:nil];
}
__block int completed = 0;
__block int32_t cancelled = 0;
NSObject *lock = [NSObject new];
NSMutableArray<NSError *> *errors = [NSMutableArray new];
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
for (BFTask *task in tasks) {
[task continueWithBlock:^id(BFTask *t) {
if (t.error != nil) {
@synchronized(lock) {
[errors addObject:t.error];
}
} else if (t.cancelled) {
OSAtomicIncrement32Barrier(&cancelled);
} else {
if(OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
[source setResult:t.result];
}
}
if (OSAtomicDecrement32Barrier(&total) == 0 &&
OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
if (cancelled > 0) {
[source cancel];
} else if (errors.count > 0) {
if (errors.count == 1) {
source.error = errors.firstObject;
} else {
NSError *error = [NSError errorWithDomain:BFTaskErrorDomain
code:kBFMultipleErrorsError
userInfo:@{ @"errors": errors }];
source.error = error;
}
}
}
// Abort execution of per tasks continuations
return nil;
}];
}
return source.task;
}

OSAtomic 原子操作,嗯……if else嗯……牛🐂!

Bolts/Tasks 怎么玩

举个栗子🌰,蓝牙中心模式中假如需要三个步骤来完成一次对特定外设的写入命令,首先要获取命令密钥,然后去扫描匹配并连接外设,最后才能写入命令,每一步我们都假定它是异步的,这三个异步操作的依赖关系相当于是串行发生的。

首先是三个异步任务,里面用延时技术模拟这种方式完成操作所需的时间。

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
+ (BFTask *) fetchCommandKeysAsync {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSLog(@"Fetch command keys starting...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Fetch command keys complete!");
[tcs setResult:nil];
});
return tcs.task;
}
+ (BFTask *) findBikeAsync {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSLog(@"Find bike starting...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Find bike complete!");
[tcs setResult:nil];
});
return tcs.task;
}
+ (BFTask *) writeCommandToBikeAsync {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSLog(@"Write command to bike starting...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Write command to bike complete!");
[tcs setResult:nil];
});
return tcs.task;
}

对已经完成的 tcs 设置 result 或 error 会报异常,建议使用 try 的版本,如 trySetResult 和 trySetError,然后我们把这些异步服务组合成一个操作流程,

1
2
3
4
5
6
7
8
9
10
11
12
13
+ (void)bluetoothOpenLockFlow {
BFTask *task = [self fetchCommandKeysAsync];
[[[task continueWithBlock:^id _Nullable(BFTask *task) {
NSLog(@"Flow - Fetch command keys complete!");
return [self findBikeAsync];
}] continueWithBlock:^id _Nullable(BFTask *task) {
NSLog(@"Flow - Find bike complete!");
return [self writeCommandToBikeAsync];
}] continueWithBlock:^id _Nullable(BFTask *task) {
NSLog(@"Flow - Write command to bike complete!");
return nil;
}];
}

让我们看看调试日志,

1
2
3
4
5
6
7
8
9
19:28:16.57 Fetch command keys starting...
19:28:22.00 Fetch command keys complete!
19:28:22.00 Flow - Fetch command keys complete!
19:28:22.00 Find bike starting...
19:28:27.26 Find bike complete!
19:28:27.26 Flow - Find bike complete!
19:28:27.26 Write command to bike starting...
19:28:32.26 Write command to bike complete!
19:28:32.26 Flow - Write command to bike complete!

是不是很清晰,哈哈,现在假如我们有网络开锁、蓝牙开锁、面容解锁三种开锁方式,网络状况好的时候,第一种方式开锁最快,弱网断网时蓝牙开锁也不慢,蓝牙设备距离远信号弱时面容解锁也可以,具体的,我们只需发送一次开锁命令,不管哪种方式开锁成功了,我们就算开锁成功,为了完成这个任务,我们首先需要创建三只异步开锁函数,

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
- (BFTask *) networkOpenLockAsync {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSLog(@"Network open lock starting...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Network open lock complete!");
[tcs setResult:nil];
});
return tcs.task;
}
- (BFTask *) bluetoothOpenLockAsync {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSLog(@"Bluetooth open lock starting...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Bluetooth open lock complete!");
[tcs setResult:nil];
});
return tcs.task;
}
- (BFTask *) faceOpenLockAsync {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSLog(@"Face open lock starting...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"Face open lock complete!");
[tcs setResult:nil];
});
return tcs.task;
}

接着,我们需要用到 taskForCompletionOfAnyTask: 这个函数来完成这个逻辑,既任何一个 task 完成,就告诉发送开锁命令的人,我们已经开锁成功了。

1
2
3
4
5
6
7
8
9
10
11
- (void)openLockFlow {
NSMutableArray *tasks = [NSMutableArray array];
[tasks addObject: [self networkOpenLockAsync]];
[tasks addObject: [self bluetoothOpenLockAsync]];
[tasks addObject: [self faceOpenLockAsync]];
BFTask *task = [BFTask taskForCompletionOfAnyTask:tasks];
[task continueWithBlock:^id _Nullable(BFTask *task) {
NSLog(@"Open lock complete!");
return nil;
}];
}

Demo 的话可以这样放置命令入口:

1
2
3
4
5
- (void)viewDidLoad {
[super viewDidLoad];
[self openLockFlow];
}

我们可以看到这次面容解锁很给力,只用了5秒钟就开锁成功了,嗯……明明可以靠脸吃饭 却偏偏要靠才华!

1
2
3
4
5
6
7
12:48:59.60 Network open lock starting...
12:48:59.60 Bluetooth open lock starting...
12:48:59.60 Face open lock starting...
12:49:04.60 Face open lock complete!
12:49:04.60 Open lock complete!
12:49:10.59 Bluetooth open lock complete!
12:49:16.04 Network open lock complete!

好的,今天就分享到这里吧!谢谢 各位看官辛苦了💦!如果喜欢请赏个雪糕🍦吃呗大爷,打个赏呗!

iOS - Evil viewDidLayoutSubviews()

Never put complex layout code or complex layout animation in viewDidLayoutSubviews(), layoutSubviews(), if you did, you’ll fall in kinds of weird crashes in some old os version devices. Good practice is put them into some refresh function such as refreshView() after networking data received or view model just updated.

If you still have to do something in those functions, don’t forget to call super implement.

Git - Use separate commit when refactoring

Merge code will be harder when there were some foundation module refactorings in both branches.

For an simplest example, you do module A refactoring A for feature A in branch feature/A, but for some reason you can not merge them back to develop branch right now, and you do another module A refactoring B for feature B in branch feature/B, which should be merge back to develop right now. Now, when you need merge feature/A back to develop, it’s very likely that there will be a large number of conflicts need resolve and by the way it’s also very likely that you can not use git tools such like Cherry Pick any more. In this case you can only do manually merge code by code, which is painful and error prone.

So, the better way to avoid these things happen is to use separate commit or branch when doing refactoring, and in this way you can merge some module refactorings at an earlier time easily, and make life easy.:D

iOS – Rescale the image

Sometimes you got a image from cache (memory or disk), but with the wrong scale, right now, I’ll share a method to resolve that.

1
2
3
4
5
6
7
8
9
// ... Get the cachedImage from somewhere
// Rescale the image
if (cachedImage.scale != [UIScreen mainScreen].scale) {
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:cachedImage.CGImage scale:[UIScreen mainScreen].scale orientation:cachedImage.imageOrientation];
cachedImage = scaledImage;
}
return cachedImage;

Another case is the remote image from backend, they keep only @3 version, and the client’s UI layout want use the origin size of the image, then we can just rescale it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Backend remote image resource scale
**/
static const CGFloat ResourceScale = 3.0;
/**
* Make a rescaled image
**/
- (UIImage *)cm_rescaledImage {
if (CMFloatEqual(self.scale, ResourceScale)) {
return [self copy];
}
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:self.CGImage
scale:ResourceScale
orientation:self.imageOrientation];
return scaledImage;
}

Pretty cool, isn’t it:D

Non-zero-sum game (非零和博弈)

在非零和博弈中,对局各方不再是完全对立的,一个局中人的所得并不一定意味着其他局中人要遭受同样数量的损失。参与者之间可能存在某种共同的利益,在充分信任、信息共享等条件下可以达到“双赢”或者“多赢”。

人类命运的昌盛必然要懂得从零和年代走向非零和年代。 – [美]罗伯特·赖特 《非零年代——人类命运的逻辑》

iOS – Share an iOS 8.3 issue (flickering on banner image view)

Share an iOS 8.3 issue with UIImageView, which will cause flickering when scrolling inside the container view.

Background

1
2
3
4
5
6
7
8
9
10
11
12
#pragma mark - Offset Change
- (void)offsetChange:(CGPoint) offset {
float offsetY = offset.y;
if (offsetY < 0) {
/*******************
* Scalable header
******************/
CGFloat totalOffset = TableHeaderViewHeight + ABS(offsetY);
CGFloat f = totalOffset / TableHeaderViewHeight;
self.bannerView.frame = CGRectMake(-MainScreenWidth * (f - 1.0) * 0.5, offsetY, MainScreenWidth * f, totalOffset);
}
}

Solution

1
2
3
4
5
6
7
- (CGSize)intrinsicContentSize {
if (CGSizeEqualToSize(self.image.size, CGSizeZero)) {
return CGSizeZero;
} else {
return CGSizeMake(MainScreenWidth, TableHeaderViewHeight);
}
}

The simple solution which will work fine is just overwrite the intrinsicContentSize() method of UIView, and return a fixed size. enjoy the code.

Git Flow Memo

Git Flow Memo

This chart is a redraw version, made by myself:D

GitFlow

Vincent Driessen‘s chart is good enough, but if you just redraw it yourself with some tools like keynote, you’ll get the essence of this model!

Key points

  1. master and develop are the history branches, should not be deleted.
  2. whether to delete the features branches is not important, but when you merge it please use command git merge --no-ff not git merge.
  3. master should only keep the production code, which can be build to production environment at any time.
  1. A successful Git branching model

At last

Have fun with this flow model, remember everyday is a gift from the god:D

iOS – Memory leak when subclass "CALayer"

Calling CALayer.presentationLayer will trigger the method “initWithLayer” automatically, so, make sure you overwrite that init method and do some thing necessary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (instancetype)initWithLayer:(id)layer {
self = [super initWithLayer: layer];
if (self == nil) {
return nil;
}
MLog(@"[CMCircleLayer] --> initWithLayer");
/***************************
* Do some thing necessary
**************************/
return self;
}

Another trick for free, set a fast speed to main window’s layer can ignore all animations on auto test.

1
2
// After app launched
self.window.layer.speed = 100;

Write the code. Change the world.

iOS – Multi thread environment debug trick share

For multi thread environment, some times it is difficult to find out the order of command execution, I’m going to share a little trick.

1
2
3
4
if (self.lastLoadBookingFilterSettingsResult == nil) {
DLog(@"[003 Called]");//TEST
[self loadFilterSettings];
}
1
2
3
4
// Only refresh current day's classes
DLog(@"[001 Called]");//TEST
[weakSelf loadClasses:weakSelf.pickerView.selectedItem withPullDownGesture:NO clearOldData:NO];
return;
1
2
3
[weakSelf updateDatePickerView:[weakSelf.settingOptions valueForKey:@"dates"]];
DLog(@"[002 Called]");//TEST
[weakSelf loadClasses:0 withPullDownGesture:NO clearOldData:YES];

Just a single line for debug log info, have fun.

iOS – UIViewController PopGesture NavigationBarHidden

Share an issue on my project.

Case:

  1. Use native navigationbar
  2. Use native popgesture
  3. Some page have navigation bar, and some not

Problem

When use pop gesture from a “bar page” to a “no bar page”, The animation is weird.

Solution

1
2
3
4
5
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:_navigationBarHidden animated:animated];
}

Remarks

Also, animated:YES can work too, but some times, we don’t want page floating at all times when switch a “bar page” to a “no bar page”.

Game – Checkers

Checkerboard

西洋跳棋游戏在一副黑白格交替的10x10的棋盘中进行。

Game – 蛇和梯子(SnakesAndLadders)

SnakesAndLadders

蛇和梯子的小游戏,也叫做滑道和梯子。

游戏的规则如下:

  1. 游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格;
  2. 每一轮,你通过掷一个 6 边的骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示;
  3. 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去;
  4. 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。

CentOS7 – 端口相关配置问题整理总结

测试端口

nc -v host port nc -v 123.127.137.101 22
用nmap扫描:nmap -p 80 123.127.137.101 nmap -p 1-200 123.127.137.101 (1-200为端口范围)

开放端口

Centos7使用firewalld代替了原来的iptables:

  1. firewall-cmd –zone=public –add-port=80/tcp –permanent 开放端口
  2. firewall-cmd –reload 重启防火墙

小伙伴们记住了嘛:D

iOS – UIKit Dynamics 与 Sprite Kit 的区别

虽然这两个框架里都包含了物理引擎,但是侧重点还是不一样的。

UIKit Dynamics

主要是为了非游戏类的应用而设计的,比如电子邮箱、新闻类的app。

Sprite Kit

主要是为了制作 2D 游戏而设计的,它是一个整个的框架,它包含了自己的一整套物理引擎。

iOS – 界面布局适配到iPhone6及iPhone6Plus的方案总结

先上参数:

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
iPhone4
-------
3.5"
640*960 pixels
320*480 points
iPhone5
-------
4"
326 ppi
640*1136 pixels
320*568 points
iPhone6
--------
4.7"
326 ppi
750*1334 pixels
375*667 points
iPhone6Plus
------------
5.5"
401 ppi
1080*1920 pixels
414*736 points

总结:

可以看出iphone5/5s/6/6plus的长宽比均为9:16(至于横版显示的适配,则需要重新设计应用的界面布局)。
现在我们只要使用”1080*1920 pixels”的分辨率来设计出资源图片 imgname@3x.png 这样的图,然后iOS会自动在运行时为不同的设备缩放生成对应的版本。
当然,如果我们想要节省CPU时钟,应该在出完@3x版的图片后,批量缩放2/3做一套@2x的图片资源加到工程中,这样运行时iOS会直接加载对应设备所需版本的图片资源。

iOS – UITableViewCell上的UIButton的使用总结

在button action中,获取button的所属cell的indexPath:

1
2
3
4
5
6
7
- (IBAction)buttonAction:(id)sender {
UIButton *button = (UIButton *)sender;
CGPoint buttonOriginInTableView = [button convertPoint:CGPointZero toView:tableView];
NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:buttonOriginInTableView];
// do something
}

UIButton的”Touch Down”事件的延迟

这是因为UIScrollView有一个delaysContentTouches的属性。

cell里经常会包含一些scroll view,iOS7中UITableViewCells也包含一个自己的scroll view,我们要把这些所有的scroll view的delaysContentTouches属性都设置一下。

我们要做的是:

1. 设置tableView

1
self.tableView.delaysContentTouches = NO;

2. 为了支持iOS7,在我们初始化UITableViewCell的时候,如在“initWithStyle:reuseIdentifier:”消息中:

1
2
3
4
5
6
7
8
for (UIView *currentView in self.subviews)
{
if([currentView isKindOfClass:[UIScrollView class]])
{
((UIScrollView *)currentView).delaysContentTouches = NO;
break;
}
}

很不幸,这不是100%永久可用的方案,因为苹果将来可能会更改cell内部的view的层级(也许我们还有其它的办法,比如可以把scroll view的层级调到下面等等),但是在苹果没有以某种方式向开发者提供cell内部类的布局管理的方案之前,这个应该是我们所能做到的最好的方案了。

iOS – 联网更新本地数据库总结

步骤

将原版db等数据文件以资源文件形式加入到工程目录中。
判断 NSCachesDirectory 目录是否已有该文件,木有则从 mainBundle 资源包拷贝一份过去。
用 NSFileManager、SQLiteManager 等对相应文件进行读/写操作。

栗子

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
//NSCachesDirectory
#define kCAPathDir @"/DB"
#define kCAPathCollegeDB @"/DB/college.db"
//mainBundle
#define kMBPathCollegeDB @"/college.db"
//member var
NWSQLiteManager *_collegesDBMgr;
//init
//NOTICE : DIR"NSCachesDirectory"
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDir = [paths objectAtIndex:0];
NSFileManager *file_mgr = [NSFileManager defaultManager];
[file_mgr createDirectoryAtPath:[cachesDir stringByAppendingString:kCAPathDir] withIntermediateDirectories:YES attributes:nil error:nil];//create dir.
NSString *file_path = [cachesDir stringByAppendingString:kCAPathCollegeDB];
if(![file_mgr fileExistsAtPath:file_path]) {
//DLog(@"file is not exist");
NSString *dataPath = [[[NSBundle mainBundle]bundlePath]stringByAppendingString:kMBPathCollegeDB];
NSError *error;
if([file_mgr copyItemAtPath:dataPath toPath:file_path error:&error]) {
//DLog(@"copy file success");
} else {
//DLog(@"%@",error);
}
}
_collegesDBMgr = [[NWSQLiteManager alloc]initWithDatabaseNamed:file_path];
//update database records.
//NOTICE : "replace into"
NSString *sql = [NSString stringWithFormat:@"replace into `ex_colleges` (`id`, `college_name`, `CITY_CODE`, `PROVINCE_CODE`, `modifiedTime`) values(%d, '%@', '%@', '%@', '%@');",
item.collegeid.intValue,
item.college_name,
item.city_code,
item.province_code,
item.modified_time
];
[_collegesDBMgr getRowsForQuery:sql];

小结

使用了目录 NSCachesDirectory
使用了sqlite特有语句 replace into
Enjoy the code :D

狗狗和猫猫是一对儿好基友

iOS – UIPickerView使用总结

UIPickerView是SDK中的一个选择器。

高度

默认情况下,UIPickerView只支持三个有效的高度:162.0180.0216.0