SDWebImage学习笔记(二)

缓存

这一小节主要研究下SDWebImage的cache功能是如何实现的,首先找到在SDWebImage中充当缓存功能的类,这一步很简单,直接定位到SDImageCache类。也就是说,接下来主要就研究这个类了。

阅读更多

SDWebImage学习笔记(一)

简介

SDWebImage是ios开发中,最常见的图片加载框架,它主要实现了图片异步加载、图片缓存,并提供了UIImageView、UIButton、MKAnnotationview的类目,使用体验很友好,也很方便,成为广大ios开发者加载网络图片的选择,今天我主要是来通过分析其源码来研究下,SDWebimage到底是如何进行设计的,架构的?

阅读更多

点点滴滴:HTTPS和HTTP的区别

从iOS9开始,苹果官方开始要求,上线的APP需要对HTTPs协议进行支持,虽然我们可以通过在项目的plist文件中设置属性的方式,暂时绕开这条限制, 但是我个人认为还是有必要了解下,苹果为什么要这么做?

阅读更多

点点滴滴:一个容易被忽视的数组和字典的方法

今天来讨论下平时大家熟悉的数组/字典,但是容易忽略的数组/字典的valueForKeyPath方法
可能大家对- (id)valueForKeyPath:(NSString *)keyPath方法不是很了解。
其实这个方法非常的强大,举个例子:

阅读更多

《CoreData》系列(二)

CoreData数据迁移以及版本升级

1 概述

为什么要有数据迁移?
由于CoreData可视化的特殊性,那么当数据模型发生变化时,相应的sqlite数据库的表由于不知道model发生了变化,表结构必须相应的做出调整,否则会导致程序Crash,CoreData的解决方案是通过创建新的sqlite表,然后将旧的数据迁移到新表上得方案来处理。下面分别介绍三种数据迁移的方式,并详细说明三种迁移方式的应用场景和注意事项。

1.轻量级的数据迁移方式
2.默认的迁移方式
3.使用迁移管理器

1.1 轻量级的数据迁移方式

轻量级的数据迁移,也就是说,并不需要程序员做很多事情就可以完成数据的迁移,是由系统默认进行的数据迁移。
那么如何进行轻量级的数据迁移呢,当model的表字段发生变化,且应用程序已经发布过版本时,此时千万不能单单修改原model来达到修改model的目的,如果这样做的话,程序会crash。正确的做法是,

1.新建一个model,并将model命名为model2,并将model2设置为当前model。
2.修改NSPersistentStoreCoordinator加载缓存区的配置。具体如下

1
2
3
4
5
6
7
8
9
NSDictionary *option = @{NSMigratePersistentStoresAutomaticallyOption:@(YES),  
NSInferMappingModelAutomaticallyOption:@(YES),
};

_store = [_coordinate addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeUrl]
options:option
error:&error];

tips:使用iCloud开发程序的app,只能使用这种迁移方式。

1.2 默认的迁移方式

正常情况下,使用轻量级的数据迁移已经足够了,但是如果由于开发需要,需要将某个Entity下面的某个Attribute迁移到新的Entity下的某个Attribute,那么轻量级的迁移方式就不能够满足需求,这个时候就需要使用默认的迁移方式来进行数据迁移。这里以一个例子代码来详细阐述如何进行默认的迁移

现在要将Model2里面的Measurement下面的name迁移到Account里面的下面的xyz属性下。
1.根据model2来创建一个新model,并命名为model3,然后将model3设置为currentmodel。
2.添加新的entity,并命名为Account,添加attribute xyz。
3.删除model2里面的Measurement,根据model3创建NSManagerObect的子类Account。
4.以model2为soureModel,model3为destinationModel添加一个MappingModel
5.按照下图所示设置映射model即可
6.最后记得将NSInferMappingModelAutomaticallyOption设置为Yes(coredata会优先读取映射model,如果没有就会自己推断),至此,默认的迁移方式就算是搞定了。


1.3 迁移管理器

简单概述下何为迁移管理器,迁移管理器,就是不再使用系统的NSPersistentCoordinator进行数据迁移,而是使用NSMigrationManager进行数据缓存区的迁移。并配合一个数据迁移视图控制器提供优雅的迁移等待界面。等待界面如下,是不是感觉很丑呢,哈哈。那么使用迁移管理器的好处又是什么呢?可以实现更加精细化的数据操作,此外还能向用户报告迁移进度。有这俩点,还不够我们去研究下它么?Let’s go!

准备工作
何时启用迁移管理器,即迁移的时机?
迁移工作如何进行?
迁移完成如何善后?

下面对上面的问题一一来做解答
迁移的时机,迁移工作需要在载入数据库的时候进行,即上节所讲的 loadStore:的时候进行,但是呢?还需要做一些判断工作。具体代码如下

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
- (void)loadStore  
{
if (debug) {
NSLog(@"Running %@ ,'%@'",[self class], NSStringFromSelector(_cmd));
}

if (_store) {
return;
}

BOOL useMigrateManager = MigrationMode;

if (useMigrateManager && [self isMigrationNecessaryForStore:[self storeUrl]]) {
[self performBackgroundManagedMigrationForStore:[self storeUrl]];
}else{
NSError *error;

//NSMigratePersistentStoresAutomaticallyOption coreData尝试将低版本的数据模型向高版本进行迁移
//NSInferMappingModelAutomaticallyOption coredata会自动创建迁移模型,会去自动尝试
NSDictionary *option = @{NSMigratePersistentStoresAutomaticallyOption:@(YES),
NSInferMappingModelAutomaticallyOption:@(YES),
NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}};

_store = [_coordinate addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeUrl]
options:option
error:&error];
if (!_store) {
if (debug) {
NSLog(@"failed load store,error = %@",error);
abort();
}
}
else/**/{
NSLog(@"successfully add store : %@",_store);
}
}
}

其中有开关,用来控制是否使用迁移管理器,以及系统是否需要进行迁移的判断。系统是否需要迁移的判断代码如下

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
- (BOOL)isMigrationNecessaryForStore:(NSURL *)storeUrl  
{
if (debug) {
NSLog(@"Running %@ '%@'",[self class],NSStringFromSelector(_cmd));
}

//文件是否存在,如果不存在认为是用户设备上并没有持久化存储区,自然不需要迁移
if (![[NSFileManager defaultManager]fileExistsAtPath:[self storeUrl].path isDirectory:nil]) {
if (debug) {
NSLog(@"Skipped Migration, source database missing");
}
return NO;
}

NSError *error = nil;
NSDictionary *sourceMetaData = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeUrl
error:&error];
NSManagedObjectModel *destinationModel = _coordinate.managedObjectModel;

//比较当前对象模型是否与用户之前安装的应用持久化存储区是否兼容。如果兼容,不需要迁移
if ([destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetaData]) {
if (debug) {
NSLog(@"Skipped Migration, source database is already compatible");
return NO;
}
}

//所有情况都尝试了,发现还是需要进行数据迁移
return YES;
}

迁移工作如何进行,众所周知,迁移工作是一项比较耗时间的工作,尤其是在数据库比较大的情况下,那么肯定不能放在前台进行,必须放在后台进行,前台展示加载进度,代码如下

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
- (void)performBackgroundManagedMigrationForStore:(NSURL *)store  
{
if (debug) {
NSLog(@"Running %@ '%@'",[self class],NSStringFromSelector(_cmd));
}

UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
self.migrationVC = [sb instantiateViewControllerWithIdentifier:@"migration"];

UIApplication *app = [UIApplication sharedApplication];
UINavigationController *navigationCtl = (UINavigationController *)[app keyWindow].rootViewController;

[navigationCtl presentViewController:self.migrationVC
animated:YES
completion:nil];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{

BOOL done = [self migrateStore:[self storeUrl]];
if (done) {
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;

NSDictionary *configuration = @{NSMigratePersistentStoresAutomaticallyOption:@(YES),
NSInferMappingModelAutomaticallyOption:@(YES),
NSSQLitePragmasOption:@{@"journal_mode":@"DELETE"}};

_store = [_coordinate addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeUrl]
options:configuration
error:&error];
if (_store) {
if (debug) {
NSLog(@"success create store");
}
}else {
if (debug) {
NSLog(@"failed, error = %@",error);
}
abort();
}

[self.migrationVC dismissViewControllerAnimated:YES
completion:nil];

self.migrationVC = nil;
});
}

});
}

接下来是,真正的迁移过程

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
- (BOOL)migrateStore:(NSURL *)store  
{
if (debug) {
NSLog(@"Running %@ '%@'",[self class],NSStringFromSelector(_cmd));
}


NSDictionary *sourceMeta = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:store
error:nil];

NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil
forStoreMetadata:sourceMeta];

NSManagedObjectModel *destinationModel = _model;
NSMappingModel *mappingModel = [NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:destinationModel];
if (mappingModel) {
NSError *error = nil;

NSMigrationManager *migrationManager = [[NSMigrationManager alloc]initWithSourceModel:sourceModel
destinationModel:destinationModel];

[migrationManager addObserver:self
forKeyPath:@"migrationProgress"
options:NSKeyValueObservingOptionNew
context:nil];

NSURL *destinationStore = [[self applicationStoreDirectory]URLByAppendingPathComponent:@"temp.sqlite"];
BOOL success = NO;
success = [migrationManager migrateStoreFromURL:store
type:NSSQLiteStoreType
options:nil
withMappingModel:mappingModel
toDestinationURL:destinationStore
destinationType:NSSQLiteStoreType
destinationOptions:nil
error:&error];
if (success) {
if (debug) {
NSLog(@"Migration Successfully!");
}
if ([self replaceStore:store withStore:destinationStore]) {
[migrationManager removeObserver:self forKeyPath:@"migrationProgress" context:NULL];
[[NSNotificationCenter defaultCenter]postNotificationName:someThingChangedNotification object:nil];
}
}else{
if (debug) {
NSLog(@"Migration Failed");
}
}
}else{
if (debug) {
NSLog(@"Mapping model is NULL");
}
}
return YES;
}

最后附上俩个辅助方法,用来观察迁移过程和替换数据库的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (BOOL)replaceStore:(NSURL *)old withStore:(NSURL *)new  
{
BOOL success = NO;
NSError *error = nil;
if ([[NSFileManager defaultManager]removeItemAtURL:old error:&error]) {
error = nil;
if ([[NSFileManager defaultManager]moveItemAtURL:new toURL:old error:&error]) {
success = YES;
}else {
if (debug) {
NSLog(@"failed move new store to old");
}
}
}else{
if (debug) {
NSLog(@"failed remove old store");
}
}
return success;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(voidvoid *)context  
{
if ([keyPath isEqualToString:@"migrationProgress"]) {
dispatch_async(dispatch_get_main_queue(), ^{
float progress = [[change objectForKey:NSKeyValueChangeNewKey]floatValue];
self.migrationVC.progressView.progress = progress;

int percenttage = progress * 100;
NSString *string = [NSString stringWithFormat:@"Migration Progress %i%%",percenttage];
self.migrationVC.progressLabel.text = string;
});
}
}

至此,三种数据迁移的方式,都已叙述完毕。
[2 小结](#1)
三种迁移方式,各有各的好处,轻量级的迁移可以配套icloud实现云端存储,默认的数据迁移,支持将属性级别的数据进行任意迁移。迁移管理器,可以管理文件存储路径,并能够报告迁移进度,我们在开发过程中,应该按照自己的需求合理选择迁移方式,下一小节结合NSFetchedResultController进行数据的实际应用。