我们接着上一篇 深入浅出MagicalRecord-01

这一节我们一起粗略的了解下 CoreData 中的一些核心概念以及 MagicalRecord 的入门准备。只有对 CoreData 理解深入了,才能更轻松的使用 MagicalRecord。

1. CoreData 的核心概念

先上两幅关键的概念图

概念图1

概念图2

(1)NSManagedObjectModel 托管对象模型(MOM)

托管对象的数据模型
MOM

这个MOM由实体描述对象,即NSEntityDescription实例的集合组成,实体描述对象介绍见下面第7条。

作用:添加实体的属性,建立属性之间的关系

(2)NSManagedObjectContext 托管对象上下文(MOC)

托管对象上下文

在概念图2中,托管对象上下文(MOC)通过持久化存储协调器(PSC)从持久化存储(NSPersistentStore)中获取对象时,这些对象会形成一个临时副本在MOC中形成一个对象集合,该对象集合包含了对象以及对象彼此之间的一些关系。我们可以对这些副本进行修改,然后进行保存,然后MOC会通过 PSC 对 NSPersistentStore 进行操作,持久化存储就会发生变化。

CoreData中的NSManagedObjectModel 托管对象的数据模型(MOM),通过 MOC 进行注册。MOC有插入、删除以及更新数据模型等操作,并提供了撤销和重做的支持。

作用:插入数据,更新数据,删除数据

(3)NSPersistentStoreCoordinator 持久化存储协调器(PSC)

持久化存储协调器
持久化堆栈

在应用程序和外部数据存储的对象之间提供访问通道的框架对象集合统称为持久化堆栈(persistence stack)。在堆栈顶部的是托管对象上下文(MOC),在堆栈底部的是持久化对象存储(persistent object stores)。在托管对象上下文和持久化对象存储之间便是持久化存储协调器(PSC)。应用程序通过类NSPersistentStoreCoordinator的实例访问持久化对象存储。

持久化存储协调器为一或多个托管对象上下文提供一个访问接口,使其下层的多个持久化存储可以表现为单一一个聚合存储。一个托管对象上下文可以基于持久化存储协调器下的所有数据存储来创建一个对象图。持久化存储协调器只能与一个托管对象模型(MOM)相关联。

(4)NSManagedObject 托管对象(MO)

托管对象数据记录

托管对象必须继承自NSManagedObject或者NSManagedObject的子类。NSManagedObject能够表述任何实体。它使用一个私有的内部存储,以维护其属性,并实现托管对象所需的所有基本行为。托管对象有一个指向实体描述的引用。NSEntityDescription 实体描述表述了实体的元数据,包括实体的名称,实体的属性和实体之间的关系。

(5)Controller 控制器

概念图1中绿色的 Array Controller,Object Controller,Tree Controller 这些控制器,一般都是通过 control+drag 将 Managed Object Context 绑定到它们,这样我们就可以在 nib 中可视化地操作数据

(6)NSFetchRequest 获取数据请求

使用托管对象上下文来检索数据时,会创建一个获取请求(fetch request)。类似Sql查询语句的功能。

(7)NSEntityDescription 实体描述

实体描述

实体描述对象提供了一个实体的元数据,包括实体名(Name),类名(ClassName),属性(Properties)以及实体属性与其他实体的一些关系(Relationships)等。

(8).xcdatamodeld


里面是.xcdatamodeld文件,用数据模型编辑器编辑,编译后为.momd或.mom文件。

我们可以选中我们的应用程序(路径类似为/Users/Childhood/Library/Application Support/iPhone Simulator/7.1/Applications/005D926F-5763-4305-97FE-AE55FE7281A4),右键显示包内容,我们看到是这样的。

我们着重理解下他们之间的协同工作关系。

这里有一个简单的demoCoreDataDemo

CoreDataDemoSnippetGist地址
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
#pragma mark - CoreDataAbout

- (void)saveContext
{
NSError* error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
NSLog(@"Unresolved error :%@, %@", error, [error userInfo]);
abort();
}else{
NSLog(@"save success!");
}
}
}

- (NSURL*)applicationDocDir
{
NSURL* url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]lastObject];
NSLog(@"%@", [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]);

return url;
}

// 后缀为.xcdatamodeld的包,里面是.xcdatamodel文件,用数据模型编辑器编辑
// 编译后为.momd或.mom文件
- (NSManagedObjectModel*)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}

NSURL* modelURL = [[NSBundle mainBundle] URLForResource:@"Journal" withExtension:@"momd"];
NSLog(@"model url: %@", [modelURL path]);
self.managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

return self.managedObjectModel;
}

- (NSManagedObjectContext*)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}

NSPersistentStoreCoordinator* coordinator = [self persistentStoreCoordinator];

if (coordinator != nil) {
self.managedObjectContext = [[NSManagedObjectContext alloc]init];
[self.managedObjectContext setPersistentStoreCoordinator:coordinator];
}

return self.managedObjectContext;
}

- (NSPersistentStoreCoordinator*)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}

NSURL* storeUrl = [[self applicationDocDir] URLByAppendingPathComponent:@"Journal.sqlite"];

NSError* error = nil;
self.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

if (![self.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
NSLog(@"Error: %@, %@", error, [error userInfo]);
}

return self.persistentStoreCoordinator;
}

结合图示和代码,我们来看下他们的运作机制(下面参考罗朝辉(飘飘白云)[Cocoa]深入浅出 Cocoa 之 Core Data(1)- 框架详解)

  • 应用程序先创建或读取模型文件(后缀为xcdatamodeld)生成 NSManagedObjectModel 对象。
  • 生成 NSManagedObjectContext 和 NSPersistentStoreCoordinator 对象,MOC会设置自身的持久化存储协调器(PSC),通过PSC来对数据文件进行读写。
  • NSPersistentStoreCoordinator 负责从数据文件(xml,sqlite,二进制文件等)中读取数据生成 Managed Object,或保存 Managed Object 写入数据文件。
  • NSManagedObjectContext 参与对数据进行各种操作的整个过程,它可以持有多个 Managed Object。我们通过它来监测 Managed Object。监测数据对象有两个作用:支持 undo/redo 以及数据绑定。
  • Array Controller,Object Controller,Tree Controller 这些控制器一般与 NSManagedObjectContext 关联,因此我们可以通过它们在 nib 中可视化地操作数据对象

2. MagicalRecord的入门准备

如上篇提到的,在工程的PCH预编译头文件中导入CoreData+MagicalRecord.h文件。因为该头文件包括了所有需要的MagicalRecord头文件。

在我们的app delegate中,或者在awakeFromNib中都可以,我们可以使用下列的方法来设置CoreData堆栈。

setup系列方法
1
2
3
4
5
+ (void) setupCoreDataStack;
+ (void) setupAutoMigratingCoreDataStack;
+ (void) setupCoreDataStackWithInMemoryStore;
+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName;
+ (void) setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;

通过调用上面的方法,我们就可以实例化一块CoreData堆栈,并且为该实例提供 getter 和 setter 方法。

需要注意的一点是,当我们的编译器在 DEBUG 模式下(DEBUG的flag为1),如果改变了定义的数据模型而没有创建新的数据模型,那么 MagicalRecord 则会删除老的存储并且会自动创建一份新的,不用在每次改变的时候进行卸载/重新安装。

在我们的app退出时,我们可以使用下面这个方法来做清理工作。

cleanUp
1
[MagicalRecord cleanUp];

3. 参考学习

下一篇:深入浅出MagicalRecord-03

《深入浅出MagicalRecord》我准备做成一个系列,记录我从0开始学习这个框架的所有心得、记录。希望能和大家一起探讨交流。

  1. 深入浅出MagicalRecord-01
  2. 深入浅出MagicalRecord-02
  3. 深入浅出MagicalRecord-03
  4. 深入浅出MagicalRecord-04

============================

1. CoreData与MagicalRecord

在 ios 开发中,我们会使用CoreData来进行数据持久化。但是在使用CoreData进行存取等操作时,代码量相对较多。而 MagicalRecord 正是为方便操作 CoreData 而生。

MagicalRecord 的三个目标:

  • 简化 CoreData 相关代码
  • 清晰、简单、单行获取数据
  • 当需要优化请求的时候,仍然允许修改 NSFetchRequest

###MagicalRecord-Github地址

============================

2. 安装配置

方法1:

1.从github上下载MagicalRecord源码 [MagicalRecord-Github地址](https://github.com/magicalpanda/MagicalRecord)。

2.将MagicalRecord文件夹拖放并添加到Xcode项目中。

3.添加 CoreData.framework。

4.在项目的预编译头文件中(PCH)中导入CoreData+MagicalRecord.h。或者在将要使用的类中单独导入。

5.开始编码。

方法2:

1.如果已经安装了CocoaPods(不清楚CocoaPods使用的朋友们可以看唐巧的这篇用CocoaPods做iOS程序的依赖管理)。

在终端中输入pod search MagicalRecord,结果如下:

复制pod 'MagicalRecord', '~> 2.2'到项目中的Podfile并保存文件。命令行cd到工程根目录下,并执行pod install

2.以下步骤同方法1的3、4、5步骤。

注意:
如果你在使用 MagicalRecord 方法的时候不想带前缀MR_(比如用 findAll 代替 MR_findAll),只需在PCH文件中,在CoreData+MagicalRecord.h之前增加#defin MR_SHORTHAND即可。

============================

3. 环境需求

  • iOS SDK 6.x 及以上
  • OS X SDK 10.8 及以上
  • 使用Xcode5来运行github仓库中包含的测试Tests,但MagicalRecord库建立在Xcode4上。
下一篇:深入浅出MagicalRecord-02

Github项目地址

================


项目来自于苹果官方文档中的一个范例。

针对这个范例,我对一些英文注释做了中文说明,并尝试着加入自动保存到相簿的这个小功能。好吧,有些过于简单了。

保存到相簿
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 当从相册里选择一张照片或者用相机进行拍摄之后,该方法被调用
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];
NSLog(@"SMILE!");
[self.capturedImages addObject:image];

if ([self.cameraTimer isValid])
{
return;
}

if (self.imagePickerController.sourceType == UIImagePickerControllerSourceTypeCamera)
{

UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}


[self finishAndUpdate];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)image:(UIImage*)image didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo
{
UIAlertView *alert;

if (error)
{
alert = [[UIAlertView alloc] initWithTitle:@"错误"
message:@"保存失败"
delegate:self cancelButtonTitle:@"确定"
otherButtonTitles:nil];
}
else
{
alert = [[UIAlertView alloc] initWithTitle:@"成功"
message:@"保存成功"
delegate:self cancelButtonTitle:@"确定"
otherButtonTitles:nil];
}
[alert show];
}

后面要加入的功能

  • 将拍照的照片存储到应用沙盒中

这是一个我在业余时间学习python的一个小练习,纯属新手,有不对的地方,还恳朋友们留言指出。

1. Google搜索

当我们在google搜索引擎中输入关键字的时候,会实时弹出一些联想提示短语的列表。这篇文章想通过python做这样一个数据采集的工作。我们使用 Chrome 浏览器来分析下。(Google服务被GFW禁掉,我采用了VPN。)

第一步【输入关键字】

第二步【Chrome浏览器开发者工具打开网络标签查看】

第三步【查看返回Preview/Response】

2. 代码思路 Github

这里随便选择了三个关键字,分别是lovelikehate

Python之数据采集google搜索联想词
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
# A demo to show how to collect the suggestion words when we search with search engine 

import urllib
import urllib2
import re
import time
from random import choice

wordsSearchingList = ['love', 'like', 'hate']

for item in wordsSearchingList:
keyWord = urllib.quote(item)

url = 'https://www.google.co.jp/complete/search?client=hp&hl=zh-CN&gs_rn=48&gs_ri=hp&tok=9rRotU-cEDUkHKHgOoZAZw&cp=4&gs_id=je&q=%s&xhr=t' % (keyWord)
# 填写Request Headers,防止被ban掉
headers = {
"GET": url,
"Host": "www.google.co.jp",
"Referer": "https://www.google.co.jp/",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36",
}

req = urllib2.Request(url)

for key in headers:
req.add_header(key, headers[key])

html = urllib2.urlopen(req).read()

# 正则匹配
pattern = re.compile(r'"({searchWord}.*?)"'.format(searchWord = keyWord))
results = pattern.findall(html)
for item in results:
print item
time.sleep(1)
print '-------------------------------------'
输出结果
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
love
love tv show
love
love story
love the way you lie
love is an open door
love moschino
love never felt so good
loveq
love live 第二季
love actually
-------------------------------------
like
likert scale
likelihood ratio test
-------------------------------------
hate
hater snapback
hater帽子
hateoas
hater snapback uk
hate crime
hate speech
hateship loveship
haters gonna hate
hate什么意思
hater uk
-------------------------------------
[Finished in 5.0s]

3. 存在问题

正则匹配我用的是pattern = re.compile(r'"({searchWord}.*?)"'.format(searchWord = keyWord)),会匹配出类似"love story""love live 第二季"这样以love开头,并被双引号包围的短语。但这样会导致下面这个问题:

like 的联想词中其实是有中文联想的,但由于该正则表达式的问题,只显示了英文。

这个仍然有待改进。

番茄工作法是一个非常有效的工作方法,不了解的朋友点击这里看百科介绍

今天为大家推荐一款 Mac 下免费好用的番茄工作法软件

focus booster

这款软件的界面很清爽,相信你会喜欢上TA。使用起来也很简单,不用看什么帮助。基本上,25分钟 的工作时间,5分钟 的休息时间,当然这两个时间可以视个人情况进行调整。不过 25分钟 和 5分钟 是一个比较科学合理的时间,过长过短都不太好,推荐使用默认设置。

1. 常用方法 GithubGist地址

常用方法
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
    NSLog(@"HostName: %@", [[NSProcessInfo processInfo] hostName]);
//globallyUniqueString 唯一的标示符,每次调用都会不一样,可以用作一些临时缓存文件的名字
NSLog(@"GlobalUniqueString: %@", [[NSProcessInfo processInfo] globallyUniqueString]);
//操作系统名称
NSLog(@"OperatingSystemName: %@", [[NSProcessInfo processInfo] operatingSystemName]);
//操作系统版本
NSLog(@"OperatingSystemVersion: %@", [[NSProcessInfo processInfo] operatingSystemVersionString]);
//物理内存
NSLog(@"PhysicalMem: %llu", [[NSProcessInfo processInfo] physicalMemory]);
//进程名称
NSLog(@"ProcessName: %@", [[NSProcessInfo processInfo] processName]);

//供应商标识
NSLog(@"UniqueId: %@", [UIDevice currentDevice].identifierForVendor);
//设备类型(iPhone、iPad)
NSLog(@"userInterfaceIdiom: %d", [UIDevice currentDevice].userInterfaceIdiom);
//设备名字
NSLog(@"Name: %@", [UIDevice currentDevice].name);
//系统名字
NSLog(@"SystemName: %@", [UIDevice currentDevice].systemName);
//系统版本
NSLog(@"SystemVersion: %@", [UIDevice currentDevice].systemVersion);
//模型
NSLog(@"Model: %@", [UIDevice currentDevice].model);
//本地化的模型
NSLog(@"LocalizeModel: %@", [UIDevice currentDevice].localizedModel);
//电池状态
NSLog(@"BatteryLevel: %f", [UIDevice currentDevice].batteryLevel);

//2014-07-03 16:52:01.055 CityOfHeart[3528:60b] HostName: dounen
//2014-07-03 16:52:01.058 CityOfHeart[3528:60b] GlobalUniqueString: B4B1EC8C-A805-434B-9D57-106ED70C214A-3528-00000AF6A42D73D7
//2014-07-03 16:52:01.059 CityOfHeart[3528:60b] OperatingSystemName: NSMACHOperatingSystem
//2014-07-03 16:52:01.061 CityOfHeart[3528:60b] OperatingSystemVersion: Version 7.1.1 (Build 11D201)
//2014-07-03 16:52:01.063 CityOfHeart[3528:60b] PhysicalMem: 1035976704
//2014-07-03 16:52:01.064 CityOfHeart[3528:60b] ProcessName: CityOfHeart
//2014-07-03 16:52:01.074 CityOfHeart[3528:60b] UniqueId: <__NSConcreteUUID 0x16693c80> 5ACF8DA1-FAC1-4D70-AB1F-BB696188C5CA
//2014-07-03 16:52:01.075 CityOfHeart[3528:60b] userInterfaceIdiom: 0
//2014-07-03 16:52:01.157 CityOfHeart[3528:60b] Name: 童年
//2014-07-03 16:52:01.159 CityOfHeart[3528:60b] SystemName: iPhone OS
//2014-07-03 16:52:01.161 CityOfHeart[3528:60b] SystemVersion: 7.1.1
//2014-07-03 16:52:01.162 CityOfHeart[3528:60b] Model: iPad
//2014-07-03 16:52:01.163 CityOfHeart[3528:60b] LocalizeModel: iPad
//2014-07-03 16:52:01.165 CityOfHeart[3528:60b] BatteryLevel: -1.000000

2. 判断设备是否是7.0以上系统

[[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f

注意:
假如我们的设备版本号为7.1.1(为字符串类型),对其进行floatValue浮点化后值为7.100000

3. 判断设备是否是iPhone、iPad

  • iPad:[[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad
  • iPhone: [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone
UIUserInterfaceIdiom in UIDevice.h
1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UIUserInterfaceIdiom) {
#if __IPHONE_3_2 <= __IPHONE_OS_VERSION_MAX_ALLOWED
UIUserInterfaceIdiomPhone, // iPhone and iPod touch style UI
UIUserInterfaceIdiomPad, // iPad style UI
#endif
};

Dropbox最近被墙掉,我才意识到以前写的博客,采用dropbox的图片外链通通打不开了。

WTF!

后面准备采用七牛云存储。这个稳定,速度还不错。

等闲下来,我会将以前博客的dropbox图片外链地址修正下。

2014/07/02:图片地址修复完毕。

从去年从公司离职到现在,已有半年多。选择走了这条路,注定一路艰辛。从之前的游戏开发,到现在的IOS开发,从我的程序员生涯上来看,算是接触到了新的一面。我时常在想,这样做是否正确?这个问题也常常深夜扰乱我的心。

不要永远呆在自己的舒适区,走出去看看。

在自己擅长的领域去做事,才更有可能会成功。

对我而言,游戏开发领域是我的一个舒适区,IOS开发相对来说是个陌生的领域。上面的两种说法时常困扰着我。从创业初期的游戏开发到现在的应用转型,我有相当大的抵触心理,也曾有中途放弃的想法。但合伙人一哥的执着以及我对合伙人一哥的信任使得我一再说服我自己留了下来,沉下心来去理解现在这个应用产品,并逐步接纳了TA。慢慢的,我心底的抵触心里也逐渐消失。同时一哥对这个产品有着清晰的商业计划路线,使得我一并沉下心来想做好这个产品。

产品

从我理解来看,这是一款具有人文关怀的产品,TA旨在帮助人们更好的去社交,去管理好自己内心的情感。

碎碎念

目前需要一个IOS开发、服务器开发以及设计师一枚。这些天的苦苦寻觅,更让我觉得在创业初期,找到几个靠谱的人,确实不易。如果朋友们想了解下情况,可以给我发个邮件。我在上海。

在Lua中,table如何安全的移除元素这点挺重要,因为如果不小心,会没有正确的移除,造成内存泄漏。

引子

比如有些朋友常常这么做,大家看有啥问题

将test表中的偶数移除掉
1
2
3
4
5
6
7
8
9
10
local test = { 2, 3, 4, 8, 9, 100, 20, 13, 15, 7, 11}
for i, v in ipairs( test ) do
if v % 2 == 0 then
table.remove(test, i)
end
end

for i, v in ipairs( test ) do
print(i .. "====" .. v)
end

打印结果:

1
2
3
4
5
6
7
8
9
1====3
2====8
3====9
4====20
5====13
6====15
7====7
8====11
[Finished in 0.0s]

有问题吧,20怎么还在?这就是在遍历中删除导致的。

如何做呢?

Let's get started!
1
2
3
4
5
6
7
8
9
10
local test = { 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p' }
local remove = { a = true, b = true, c = true, e = true, f = true, p = true }

local function dump(table)
for k, v in pairs( table ) do
print(k)
print(v)
print("*********")
end
end

说明:一般我们不在循环中删除,在循环中删除会造成一些错误。这是可以建立一个remove表用来标记将要删除的,如上面例子,把将要删除的标记为true

方法1 从后往前删除

1
2
3
4
5
6
7
for i = #test, 1, -1 do
if remove[test[i]] then
table.remove(test, i)
end
end

dump(test)

为什么不从前往后,朋友们可以测试,table.remove操作后,后面的元素会往前移位,这时候后续的删除索引对应的元素已经不是之前的索引对应的元素了。

方法2 while删除

1
2
3
4
5
6
7
8
9
10
local i = 1
while i <= #test do
if remove[test[i]] then
table.remove(test, i)
else
i = i + 1
end
end

dump(test)

方法3 quick中提供的removeItem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function table.removeItem(list, item, removeAll)
local rmCount = 0
for i = 1, #list do
if list[i - rmCount] == item then
table.remove(list, i - rmCount)
if removeAll then
rmCount = rmCount + 1
else
break
end
end
end
end

for k, v in pairs( remove ) do
table.removeItem(test, k)
end

dump(test)

github地址