极客班 iOS 应用开发实战(六)

段老师的视频教程,采用都是比较通用是 Demo,方便以后代码复用。也是从项目实践经验来教授相关知识如 FMDB,这几节课获益匪浅。

Demo 源码见 Github,并添加了相关注释。

28. 沙盒机制

介绍几种数据持久化的方案,选择合适项目的方案:

  • 沙盒机制
  • NSFileManager
  • Plist、Archive数据归档 * SQLite3应用
  • NSUserDefaults

沙盒目录结构 NSDocumentDirectory

  • Application Bundle/
  • Documents/
  • Library/Caches/
  • Library/Perferences/
  • tmp/

29. NSFileManager & Plist

存取文档的方法封装一个通用的类BLUtility.h/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
//
// BLUtility.m

/**
* 创建目录
*
* @param dirPath 待创建的目录
* @param isDir 结尾是否是目录
*
* @return 是否创建成功
*/
+ (NSString*) getPathWithinDocumentDir:(NSString*)aPath {
NSString *fullPath = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

if ([paths count] > 0) {
fullPath = (NSString *)[paths objectAtIndex:0];
if ([aPath length] > 0) {
fullPath = [fullPath stringByAppendingPathComponent:aPath];
}
}
return fullPath;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// BLArchiveViewController.m

/**
* 测试保存文档和 plist 的按钮关联的 Action 方法
*
* @param sender <#sender description#>
*/
- (void)writeFileButtonClicked:(id)sender {
// 1 - 测试字符串文档,保存到 Documents
// 1.1 - 生成并保存
NSString *string = @"ABC副经理";
NSString *stringFilePath = [BLUtility getPathWithinDocumentDir:@"string.text"];
[BLUtility createDirectory:stringFilePath lastComponentIsDirectory:NO];
[string writeToFile:stringFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}

30. Archive 数据归档和 NSUserDefaults

转化为 NSData,二进制的数据,利于存储和传输。

遵循 NSCoding 协议,即可实现编码和解码的类,进一步对其归档和解档。也用到了封装的类BLUtility.h/m。本例中文档保存到 NSUserDefaults:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//
// BLUser.m

/**
* 解码
*
* @param aDecoder 编码类型
*
* @return 解码得到的对象
*/
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.userName = [aDecoder decodeObjectForKey:@"userName"];
self.email = [aDecoder decodeObjectForKey:@"email"];
self.password = [aDecoder decodeObjectForKey:@"password"];
self.age = [aDecoder decodeIntForKey:@"age"];
}
return self;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
// BLUtility.m

/**
* 解档
*
* @param archivedData 待解档的 NSData
* @param key key
*
* @return 解档完成的对象
*/
+ (NSObject *) unarchiverObject:(NSData *)archivedData withKey:(NSString *)key {
if(archivedData == nil) {
return nil;
}

NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:archivedData];
NSObject *object = [unarchiver decodeObjectForKey:key];
[unarchiver finishDecoding];
return object;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//
// BLArchiveViewController.m

/**
* 读取按钮关联的 Action 方法
*
* @param sender <#sender description#>
*/
- (void) readButtonClicked:(id)sender {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSInteger test = [userDefaults integerForKey:@"integer"];
NSLog(@"test = %li", test);

NSString *userDataPath = [BLUtility getPathWithinDocumentDir:UserDataName];
if ([userDataPath length] > 0 && [[NSFileManager defaultManager] fileExistsAtPath:userDataPath]) {
NSData *userData = [NSData dataWithContentsOfFile:userDataPath];
BLUser *user = (BLUser *)[BLUtility unarchiverObject:userData withKey:@"UserData"];
_userNameTextField.text = user.userName;
_emailTextField.text = user.email;
_passwordTextField.text = user.password;
_ageTextField.text = [NSString stringWithFormat:@"%ld", user.age];
}
}

###延伸阅读:

31. SQLite3

通过 FMDB 封装,得以 OBjective-C 访问 SQLite,完成常用增删改查。从语法和数据体积实现轻量级存储方案。这个方案稍后用在习题“同学录”上实践一下。

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
//
// BLPoemDB.h


#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#import "BLPoem.h"

@interface BLPoemDB : NSObject {
/**
* 创建一个 FMDatabase 实例
*/
FMDatabase *_db;
}

/**
* 创建一个表
*
* @return 是否创建成功
*/
- (BOOL) createPoemTable;

/**
* 增加一首诗到表
*
* @param poem 待添加的诗
*
* @return 是否添加成功
*/
- (BOOL) addPoem:(BLPoem *)poem;

/**
* 查询所有诗
*
* @return 查询到的包含所有诗的数组
*/
- (NSMutableArray *) getAllPoems;

/**
* 查询被收藏的诗
*
* @return 查询到的包含所有收藏的诗的数组
*/
- (NSMutableArray *) getFavoritesPoems;

/**
* 更改是非收藏这一首诗
*
* @param favorite 是否收藏
* @param poemId 待更改的诗的 ID
*
* @return 是否更改收藏成功
*/
- (BOOL) setFavorite:(BOOL)favorite favoriteId:(NSInteger)poemId;

/**
* 删除指定 ID 的一首诗
*
* @param poemId 待删除的诗的 ID
*
* @return 是否删除成功
*/
- (BOOL) deletePoemWithPoemId:(NSInteger)poemId;

@end
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//
// BLPoemDB.m
// BLDataSaveDemo


#import "BLPoemDB.h"
#import "BLUtility.h"

#define BLPOEMDBNAME @"BLPoemDB.sqlite"

@implementation BLPoemDB

/**
* 初始化数据库 FMDatabase 实例
*
* @return 初始化得到的 FMDatabase 实例
*/
-(id) init {
self = [super init];
if (self) {
NSString *dbPath = [BLUtility getPathWithinDocumentDir:BLPOEMDBNAME];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL existFile = [fileManager fileExistsAtPath:dbPath];
if (existFile == NO) {
NSString *poemDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:BLPOEMDBNAME];
[fileManager copyItemAtPath:poemDBPath toPath:dbPath error:nil];
}
_db = [[FMDatabase alloc] initWithPath:dbPath];
if ([_db open] == NO) {
return nil;
}
}
return self;
}


- (BOOL) createPoemTable {
[_db beginTransaction];
BOOL success = [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS POEMTABLE ("
@"ID INTEGER PRIMARY KEY NOT NULL,"
@"POEMNAME TEXT NOT NULL,"
@"POETNAME TEXT NOT NULL,"
@"POEMCONTENT TEXT NOT NULL,"
@"WHETHERFAVORITE BOOL NOT NULL);"];
[_db commit];

if(!success || [_db hadError]) {
[_db rollback];
return NO;
}
return YES;
}

- (BOOL) addPoem:(BLPoem *)poem {
FMResultSet *rs = [_db executeQuery:@"SELECT ID FROM POEMTABLE WHERE ID=?", [NSNumber numberWithInteger:poem.poemId]];
if(rs && [rs next]) {
[rs close];
return YES;
}
[rs close];

[_db beginTransaction];
NSString *bookMark = nil;
BOOL success = [_db executeUpdate:@"INSERT OR IGNORE INTO POEMTABLE (ID,POEMNAME,POETNAME,POEMCONTENT,WHETHERFAVORITE) VALUES (?,?,?,?,?);",
[NSNumber numberWithInteger:poem.poemId],
poem.poemName,
poem.poetName,
poem.poemContent,
[NSNumber numberWithBool:poem.whetherFavorite],
bookMark];
[_db commit];

if(!success || [_db hadError]) {
[_db rollback];
return NO;
}
return YES;
}

- (NSMutableArray *) getAllPoems {
NSMutableArray *result = [[NSMutableArray alloc] init];
// FMResultSet *rs = [_db executeQuery:@"SELECT * FROM POEMTABLE WHERE ID=? AND WHETHERFAVORITE=?", [NSNumber numberWithInt:poem.poemId], [NSNumber numberWithBool:poem.whetherFavorite]];
FMResultSet *rs = [_db executeQuery:@"SELECT * FROM POEMTABLE"];
while([rs next]) {
BLPoem *poem = [[BLPoem alloc] init];
poem.poemId = [rs intForColumn:@"ID"];
poem.poemName = [rs stringForColumn:@"POEMNAME"];
poem.poetName = [rs stringForColumn:@"POETNAME"];
poem.poemContent = [rs stringForColumn:@"POEMCONTENT"];
poem.whetherFavorite = [rs boolForColumn:@"WHETHERFAVORITE"];
[result addObject:poem];
}
[rs close];
return result;
}

- (NSMutableArray *) getFavoritesPoems {
NSMutableArray *result = [[NSMutableArray alloc] init];
FMResultSet *rs = [_db executeQuery:@"SELECT * FROM POEMTABLE WHERE WHETHERFAVORITE=?", [NSNumber numberWithBool:YES]];
while([rs next]) {
BLPoem *poem = [[BLPoem alloc] init];
poem.poemId = [rs intForColumn:@"ID"];
poem.poemName = [rs stringForColumn:@"POEMNAME"];
poem.poetName = [rs stringForColumn:@"POETNAME"];
poem.poemContent = [rs stringForColumn:@"POEMCONTENT"];
poem.whetherFavorite = [rs boolForColumn:@"WHETHERFAVORITE"];
[result addObject:poem];
}
[rs close];
return result;
}

- (BOOL) setFavorite:(BOOL)favorite favoriteId:(NSInteger)poemId {
if (_db == nil) {
return NO;
}
[_db beginTransaction];
BOOL result = [_db executeUpdate:@"UPDATE POEMTABLE SET WHETHERFAVORITE=? WHERE ID=?", [NSNumber numberWithBool:favorite], [NSNumber numberWithInteger:poemId]];
[_db commit];
return result;
}

- (BOOL) deletePoemWithPoemId:(NSInteger)poemId {
[_db beginTransaction];
BOOL success = [_db executeUpdate:@"DELETE FROM POEMTABLE WHERE ID=?", [NSNumber numberWithInteger:poemId]];
[_db commit];
if(!success || [_db hadError]) {
[_db rollback];
return NO;
}
return YES;
}

- (void) dealloc {
[_db close];
_db = nil;
}

@end

延伸阅读:

32. 多线程和多媒体

简单介绍了常见多媒体的播放:

  • UIImagePickerViewController
  • UIActionSheet (👈名字谐音)
  • AVAudioPlayer
  • SystenSounID
  • MPMoviePlayerViewController
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 <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <MediaPlayer/MediaPlayer.h>

@interface BLOneViewController ()<UIAlertViewDelegate, UIActionSheetDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate> {
AVAudioPlayer *audioPlayer;
SystemSoundID systemSoundId;
}


#pragma mark - Play Music button action

- (IBAction)palyMusic:(UIButton *)sender {



if (!audioPlayer) {
NSString *mp3Path = [[NSBundle mainBundle] pathForResource:@"yishengsuoai" ofType:@"mp3"];
NSURL *mp3Url = [[NSURL alloc] initFileURLWithPath:mp3Path];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:mp3Url error:NULL];
audioPlayer.numberOfLoops = -1;
[audioPlayer prepareToPlay];
}
if ([audioPlayer isPlaying]) {
[audioPlayer stop];
sender.titleLabel.text = @"Play Music";

}else {
[audioPlayer play];
sender.titleLabel.text = @"Pasue Music";

}
}

33. 硬件访问、CALayer & Core Graphics

33.1. gravity and user acceleration

Useage: shaking device to feedback or 360° image

1
2
3
4
5
6
7
8
9
10
11
12
13
// 4 - Start update divice motion and print them
// The accelerometer measures the sum of two acceleration vectors: gravity and user acceleration.
NSOperationQueue *diviceMotionQueue = [[NSOperationQueue alloc] init];
[self.motionManager startDeviceMotionUpdatesToQueue:diviceMotionQueue
withHandler:^(CMDeviceMotion *motion, NSError *error) {

NSLog(@"\n \n-------------------------------------------------------------------------->\n \n 1 - attitude: %@ \n 2 - rotation rate x: %f, y: %f, z: %f,\n \n 3 - gravity x: %f, y: %f, z: %f \n \n 4 - user acceleration x: %f, y: %f, z: %f \n \n 5 - magnetic field accuracy: %d, x: %f, y: %f, z: %f \n \n <--------------------------------------------------------------------------\n \n" ,
motion.attitude,
motion.rotationRate.x, motion.rotationRate.y, motion.rotationRate.z,
motion.gravity.x, motion.gravity.y, motion.gravity.z,
motion.userAcceleration.x, motion.userAcceleration.y, motion.userAcceleration.z,
motion.magneticField.accuracy, motion.magneticField.field.x, motion.magneticField.field.y, motion.magneticField.field.z);
}];

Search the definiton of each new word, really 😄😄😄.

Do you believe that, At least I don’t think so.

Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
-------------------------------------------------------------------------->

1 - attitude: CMAttitude Pitch: 0.751292, Roll: 0.555726, Yaw: 22.198534

2 - rotation rate x: 0.001511, y: -0.000795, z: 0.002454,

3 - gravity x: 0.009698, y: -0.013112, z: -0.999867

4 - user acceleration x: -0.002603, y: 0.000783, z: -0.014095

5 - magnetic field accuracy: -1, x: 0.000000, y: 0.000000, z: 0.000000

<--------------------------------------------------------------------------

33.2. CALayer

Modifying the Layer’s Appearance Properties.
Lots of properties really similar to Sketch.

33.3. Core Graphics

CGContextRef: An opaque type that represents a Quartz 2D drawing environment.

Path on CGContextRef vs Vector on Artboard in Sketch, by learn Sketch really make sense of those abstract concepts.

34. 自定义手势、Block & GCD

34.1. 手势

尽量设计使用常用的手势

设计一个小 Demo,实现:Shake Your iPhone To Send Feedback Email To Me😄

34.2. Block

比较简洁的函数,就在当前就可以调用。

34.3. GCD

Group queue:并行执行多任务比较牛。

35. APNS、Core Date、URL Scheme、单元测试、APP发布流

35.1. APNS

Remote Notification

1
2
3
4
// 1 - Register Remote Notification
// 2 - Register deviceToken
// 3 - Receive Remote Notification
// 4 - Update Icon Badge Number

稍后添加到 Classmates

35.2. Core Data

核心概念:

1
2
3
4
5
6
7
8
// 程序员和管理对象模式之间的桥梁,做数据库的增删改查等操作
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

// 数据库里的不同管理对象类型
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;

// 数据库文件和程序之间的联系桥梁
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

稍后把 Classmate 整个 Core Data 的版本

35.3. URL Scheme

就是一个通过个地址访问系统软件或第三方软件,如地图和 Launch Center Pro。iOS 9之后估计有更多可能性。

35.4. 单元测试或断点测试

35.5. App 发布流程