iOS5 切换中文键盘时覆盖输入框的完美解决方案

copy from http://www.apkbus.com/blog-107838-44715.html

众所周知,iOS5之前,iPhone上的键盘的高度是固定为216.0px高的,中文汉字的选择框是悬浮的,所以不少应用都将此高度来标注键盘的高度(包括米聊也是这么做的)。

可是在iOS5中,键盘布局变了,尤其是中文输入时,中文汉字选择框就固定在键盘上方,这样就使得原本与键盘紧密贴合的界面视图被中文汉字选择框给覆盖住了。一方面影响了界面的美观,另一方面,如果被覆盖的部分就是文本输入框的话,用户就无法看到输入的内容了。因此这个问题就必须得解决了。

解决方法

其实在一开始使用216.0px这个固定值来标注键盘的高度就是错误的。因为在iOS3.2以后的系统中,苹果就提供了键盘使用的API以及Demo程序——“KeyboardAccessory”。

处理键盘事件的正确方法是这样的:(包括获取键盘的位置以及键盘弹出和消失动画的时间)

1)在要使用键盘的视图控制器中(既viewDidLoad中),接收键盘事件的通知:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];

// 键盘高度变化通知,ios5.0新增的
#ifdef __IPHONE_5_0
float version = [[[UIDevice currentDevice] systemVersion] floatValue];
if (version >= 5.0) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
#endif

2)然后添加键盘事件的处理代码:

获取到当前keyboard的高度以及动画时间,然后对视图进行对应的操作即可。

#pragma mark –
#pragma mark Responding to keyboard events
– (void)keyboardWillShow:(NSNotification *)notification {

/*

Reduce the size of the text view so that it’s not obscured by the keyboard.

Animate the resize so that it’s in sync with the appearance of the keyboard.

*/

NSDictionary *userInfo = [notification userInfo];

// Get the origin of the keyboard when it’s displayed.

NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];

// Get the top of the keyboard as the y coordinate of its origin in self’s view’s coordinate system. The bottom of the text view’s frame should align with the top of the keyboard’s final position.

CGRect keyboardRect = [aValue CGRectValue];

// Get the duration of the animation.

NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];

NSTimeInterval animationDuration;

[animationDurationValue getValue:&animationDuration];

// Animate the resize of the text view’s frame in sync with the keyboard’s appearance.

[UIView animateWithDuration:animationDuration animations:^{

//此处的viewFooter即是你的输入框View

//20为状态栏的高度

self.viewFooter.frame = CGRectMake(viewFooter.frame.origin.x, keyboardRect.origin.y-20-viewFooter.frame.size.height,viewFooter.frame.size.width, viewFooter.frame.size.height);

} completion:^(BOOL finished){

}];

}

– (void)keyboardWillHide:(NSNotification *)notification {

NSDictionary* userInfo = [notification userInfo];

NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];

CGRect keyboardRect = [aValue CGRectValue];

/*

Restore the size of the text view (fill self’s view).

Animate the resize so that it’s in sync with the disappearance of the keyboard.

*/

[UIView animateWithDuration:0 animations:^{

self.viewFooter.frame = CGRectMake(viewFooter.frame.origin.x, keyboardRect.origin.y-20-viewFooter.frame.size.height, viewFooter.frame.size.width, viewFooter.frame.size.height);

} completion:^(BOOL finished){

}];

}  3)在视图控制器消除时(即viewDidUnload中),移除键盘事件的通知:

[[NSNotificationCenter defaultCenter] removeObserver:self];

背景:

ios5之前,iphone上的键盘的高度是固定为216.0px高的,中文汉字的选择框是悬浮的,所以不少应用都将此高度来标注键盘的高度。

可是在ios5中,键盘布局变了,尤其是中文输入时,中文汉字选择框就固定在键盘上方,这样就使得原本与键盘紧密贴合的界面视图被中文汉字选择框给覆盖住了。一方面影响了界面的美观,另一方面,如果被覆盖的部分就是文本输入框的话,用户就无法看到输入的内容了。因此这个问题就必须得解决了。

解决方法:

其实在一开始使用216.0px这个固定值来标注键盘的高度就是错误的。因为在ios3.2以后的系统中,苹果就提供了键盘使用的api以及demo程序——“KeyboardAccessory”。

处理键盘事件的正确方法是这样的:(包括获取键盘的高度以及键盘弹出和消失动画的时间)

 

1)在要使用键盘的视图控制器中,接收键盘事件的通知:

复制代码
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];

        // 键盘高度变化通知,ios5.0新增的  
#ifdef __IPHONE_5_0
        float version = [[[UIDevice currentDevice] systemVersion] floatValue];
        if (version >= 5.0) {
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil];
        }
#endif
复制代码

 

2)然后添加键盘事件的处理代码:

获取到当前keyboard的高度以及动画时间,然后对视图进行对应的操作即可。

复制代码
#pragma mark -
#pragma mark Responding to keyboard events
- (void)keyboardWillShow:(NSNotification *)notification {
    
    /*
     Reduce the size of the text view so that it's not obscured by the keyboard.
     Animate the resize so that it's in sync with the appearance of the keyboard.
     */
    
    NSDictionary *userInfo = [notification userInfo];
    
    // Get the origin of the keyboard when it's displayed.
    NSValue* aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
    
    // Get the top of the keyboard as the y coordinate of its origin in self's view's coordinate system. The bottom of the text view's frame should align with the top of the keyboard's final position.
    CGRect keyboardRect = [aValue CGRectValue];
    
    // Get the duration of the animation.
    NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
    NSTimeInterval animationDuration;
    [animationDurationValue getValue:&animationDuration];
    
    // Animate the resize of the text view's frame in sync with the keyboard's appearance.
    [self moveInputBarWithKeyboardHeight:keyboardRect.size.height withDuration:animationDuration];
}


- (void)keyboardWillHide:(NSNotification *)notification {
    
    NSDictionary* userInfo = [notification userInfo];
    
    /*
     Restore the size of the text view (fill self's view).
     Animate the resize so that it's in sync with the disappearance of the keyboard.
     */
    NSValue *animationDurationValue = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
    NSTimeInterval animationDuration;
    [animationDurationValue getValue:&animationDuration];
    
    [self moveInputBarWithKeyboardHeight:0.0 withDuration:animationDuration];
}
复制代码

3)在视图控制器消除时,移除键盘事件的通知:

[[NSNotificationCenter defaultCenter] removeObserver:self];

ps:

ios5隐藏功能分享——“字典”功能(英英字典):

在任何输入框中选中一个英文单词,此时会有选择项“复制”,“删除”…等,还有一个向右的箭头,点击这个向右的箭头后,就会出现“定义”选项,点击这个“定义”按钮即会弹出这个英语单词的英文解释。

 

 

首先,你得监听键盘的事件,最基本的两个事件:

UIKeyboardWillShowNotification

UIKeyboardWillHideNotification

UIKeyboard…

iOS 5新增加了一些

UIKeyboardDidChangeFrameNotification(will)

一般情况下,前两个事件已经可以完成你要做的事情。在你的事件处理方法中加上NSNotification参数可以为你获得更多的东西:

view plainprint?

– (void)keyboardWillShow:(NSNotification *)notification

{

CGPoint beginCentre = [[[notification userInfo] valueForKey:UIKeyboardCenterBeginUserInfoKey] CGPointValue];

CGPoint endCentre = [[[notification userInfo] valueForKey:UIKeyboardCenterEndUserInfoKey] CGPointValue];

CGRect keyboardBounds = [[[notification userInfo] valueForKey:UIKeyboardBoundsUserInfoKey] CGRectValue];

CGRect keyboardFrames = [[[notification userInfo] valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];

UIViewAnimationCurve animationCurve = [[[notification userInfo] valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue];

NSTimeInterval animationDuration = [[[notification userInfo] valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

}

如果你要在程序中使用键盘的高度和宽度,永远不要尝试去手动指定,动态获取也很简单而且一定准确,不会出现键盘

挡住输入框的问题。

你可以利用这些参数把动画做的和键盘一致。假设你要把一个控件放在Window上,并且想让它的交互方式和键盘一样,

如果只是简单的做个向下偏移动画并不能很好的完成,因为你还要考虑从导航栏中Pop出来的时候,这个时候的键盘动画是

在x轴上偏移的,你用UIKeyboardFrameEndUserInfoKey获取的frame可以很准确的做到。

 

如果在某些特殊的字段上,你不想用默认的键盘,而是用类似于Picker这样的拾取器,你只需要设置inputView就行了,用你自定义的视图去替换掉键盘;如果你想在键盘上面再增加一个视图,比如toolbar,那么你可以不用自己对toolbar的位置进行控制,只需要设置inputAccessoryView就行了,这个值默认为nil,设置的视图将在你的控件变成第一响应者的时候显示在inputView的上方。

\\

 

在UIScrollView(UITableView继承于它)上,当你触碰控件使之变成第一响应者的时候,系统会自动调整位置,避免键盘挡住控件。如果在代码中用becomeFirstResponder使之变成第一响应者将不会出现自动调整。你可以设置contentOffset去手动调整。其他视图,最简单的方法就是修改控件的frame属性,让控件总是显示在可见区域。

 

 

IOS键盘事件记

IOS5.0之前不支持键盘sizechange事件(5.0之前键盘尺寸一直保持不变),而5.1居然在键盘size改变时无法正常发出通知。所以要想捕获键盘尺寸变化的事件,可以在键盘didShow事件里通过beginSize和endSize和判断。

 

如果想让UI随着键盘的变化而变化(动画一致),需要在willshow及willhide里加入动画代码。动画的两个参数需要和键盘同步(animationDuration和animationCurve)。

获取键盘的动画参数:

NSDictionary *info = [notification userInfo];

NSTimeInterval duration = 0;

UIViewAnimationCurve curve;

[[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&duration];

[[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&curve];

 

duration在默认【中文拼音输入法】弹出时,不能正确获取duration,需要加一行代码修正:

duration = (duration > 0 ? duration: 0.25);//已知,IOS5.0.1里面键盘弹出动画的0.25S的,瑕疵:如果这里动画时间不是0.25就有可能动画不同步了

 

【Updated at 2012-05-12】键盘处于显示状态的UIViewController被UINavigationController pop出去时,也会发出 UIKeyboardWillHideNotification通知,只不过这个键盘隐藏的轨迹是【从左到右的滑动】而不是从上到下的收起。所以,如果想在这个通知的响应代码里去调整UI(比如恢复底部工具条的origin.y使其始终居于底部),应该避免横向滑动键盘消失这种情况。可以通过以下响应代码来鉴别是否横向滑动导致键盘消失:

NSDictionary *info = [notification userInfo];

CGPoint kbEndOrigin = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].origin;

if(kbEndOrigtin.x > 0) {//横向滑动导致了键盘消失,不必调整UI

return;

}

//恢复UI显示

Advertisements

分享别人的面经

本文来着Zhengli Sun同学的分享

http://zhengli-sun.blogspot.com/2011/12/cs.html

我的美国CS面试经验分享

过去的一年多里,参加了一些面试,虽然面过的公司不多,但都从头一直走到尾。毕竟自己也是花了大量的时间和精力在这一场场的面试里。所以,就絮叨下自己的一些经验,希望能给在美国找实习找工作的同学们提供一点点帮助。

开始前的一些说明:

1. 笔者只是一介小本科,虽然留了学,但是留了级,学识浅薄,目光短浅,文章若有不恰之处,恳请各位大牛不吝指正!

2. 笔者面试的岗位均为Software Engineer,俗称“程序猿”。如果读者是非CS专业或没有找此类工作的需求,请ctrl + w。本文更多的倾向于CS技术层面,关于面试仪表妆容礼仪等等的其他问题,请出门右拐。

3. 鉴于保密协议,本文只谈面试准备材料和方法,不涉及任何具体面试题。(当然,你如果单独请笔者吃饭,可以考虑)

4. 本文涉及的内容更多地适用于在美国本土的技术面试。美国的面试更加正式规范,国内同学可做适当参考。

5. 个人认为,面试的成功 = 60%的平时积累 + 30%的考前准备 + 10%的其他因素(如自信、谈吐)。所以,面试的准备对于我们这类凡人来说,异常重要;靠平时积累就能虐了面试官的大牛,不在本文考虑之列。

我面过的公司: 百度总部,微软总部,Bloomberg总部,Google总部。

笔者运气较好,除了在微软败在了最后一轮大manager的石榴裙下,其他三家都顺利拿到了offer:先后在百度和Bloomberg实习,并将于明年正式加入Google工作。

下面将分Behavior Question和Technical Question分别介绍个人的面试准备技巧:

I. Behavior Question

这类问题的特点是,易准备,好回答,必出现。所以一定要花几个小时好好准备,写写提纲,面试前对着镜子说几次。

a. HR Question

最无聊的一类问题,比如“why Microsoft?”、“what’s your plan in 5 years? ” 一般为HR所喜欢。

推荐准备材料:http://hrinterviews.blogspot.com/。把这64道题刷一下,所有的HR问题都不会是问题了。准备的方法类似于托福口语,准备几个段子,反复用,就很充分了。

另外,回答一定要真诚。比如,如果被问到“what’s your weakness?”,你如果回答:我太追求完美太热爱工作巴拉巴拉——太过时太恶心人了吧,亲!

b. Talk about your project

一般会在面试的开始被问及,必然会被问到的题目之一。把简历上的项目好好地阐述,辅以画图更佳。一些经典的Follow up是:What is the most challenging part? What will you do if you have opportunities to improve it?

百分一万的准备好这些问题!面试官通常会刨根问底。答的吭吭哧哧,几乎是不诚信的表现。

c. Question for interviewer

一般会在面试的最后十分钟里,面试官会请你提出问题。这是你展现对公司的激情、个人的兴趣、和面试官套近乎等等等等的大好机会。不要说“no”或者仅仅问“啥时知道结果啊,哥们”这类的问题。至少准备五个有深度的问题。

个人经验来说,最好的方法还是随机应变,根据之前面试的情况来合理提问。比如,我在Google的一次面试里,面试官无意间提及他在设计一门新的编程语言。面试最后,我就满脸好奇地说:“talk about your language, please”。然后我和他就编程语言的设计各方面进行了一些小讨论,他最后离开时万分兴奋。就这样,对面试官的尊重,自身兴趣和能力的展现,对技术的激情——一脉相承,水到渠成。

II. Technical Question

技术面试的最核心部分。

下面是一些笔者使用过的材料(请适当支持正版):

•Programming Interviews Exposed

入门级书籍,可以了解一些基本概念。

•Cracking the Coding Interview

中级书籍,经典必备教材,重点推荐,重中之重!从头到尾我做过五次。

•Hacking a Google Interview

MIT的一门课程,教学Handout可作为中级题目练习。

mitbbs的JobHunt板块

资料很多,水帖更多,可以寻找到很多战友和第一手的面经。可以重点学习里面的精华贴。

http://www.sureinterview.com/

中高级的算法题。

http://www.leetcode.com/

高级算法题,难度偏难,可做适当了解。个人认为,如果不是面Google,里面的题目被面到的可能性不高。

Topcoder

知名的编程练习网站,有一些相关的材料和教材很经典。

•面经来源:

mitbbs的JobHunt板块GlassdoorCareerCup

C++ FAQ

非常有名的高级C++语言学习网站。啃下来会很有帮助。主要的目的是为了应付关于Object-Oriented的相关题目。

如果你准备用Java,也请至少把语言使用能力达到中阶。

•Object Oriented Analysis and Design (Oreilly Head First.)和

Design Patterns Oct.2004(Oreilly Head First)

两本OOP的经典教材。据说Design Pattern挺重要,但个人从未遇到过相关题目。但是大致了解一下,总不会错。

•Wikipedia/Google

仔细查阅每一个你所不知道的算法、数据结构和概念,做好笔记。等你在面试时发现一个名词你见过却不知道是什么,你会把肠子悔青的。

•每个公司所关注的技术

这一点非常重要。比如面Google,就要把Distributed System和Information Retrieval的相关技术了解下,好好看看他家的经典Paper:Map-Reduce和Google File System;比如面Bloomberg,对C++的了解和使用一定要啃到一定级别;比如面Amazon,要准备好OOP。

相信我,花六个月的时间,把上述的所有材料搞定,世界上没有哪个技术公司你进不去的。(You know I’m kidding… But it’s basically the fact. )

你可能会问,那如果我只有一周,或者两天,甚至更短的时间去准备一场面试,该怎么办?

我的回答是:第一,如果它是phone interview或者on-campus interview,那只是初级的筛选,难度不会很高,just relax;第二,拿下上述材料中的初级和中级部分(再次强调Cracking the Coding Interview这本书),然后根据公司来决定学习重点,这样就应该有不错的发挥了。毕竟个人积累不同,尽力而为吧。

当你拿到on-site的邀请时,不要去炫耀你的成就了,赶紧去准备之后的面试吧。On-site的难度深度都会有很大的提高。那才是真正的战斗!过不了on-site,你什么也都不是!

下面我会分topic介绍一下准备重点。在你准备面试的过程中,你也应该有一份这样类似的word文档,记录你每天学习到的所有东西。

面试准备绝不是背诵和题海战术,而是能帮助你对CS知识的理解和运用提升到新高度的过程。

1.) Time Complexity分析

基础中的基础。绝大部分情况下,算法的时间复杂度能一眼看出来。

如果是面Google,需要掌握一些严密的时间复杂度的数学推导,有些算法不是一眼能看出时间复杂度的。

2.) Coding

废话!

但是需要练习的是在纸上和在白板上写code。 (不要小看这件事!关掉愚蠢的Eclipse和VC吧)

更关键的是,写的代码要一次成型,bug-free,即使多花点时间。如果你平时有写完代码再慢慢debug的习惯,是很不利的。被面试官找出bug来,你的分数会被大扣分!

语言选择上,C++和Java,抑或 C#,都是无可挑剔的选择——好比,孙权刘备曹操主;

Python,Ruby,Perl啥的也还行,在字符串处理上有奇效,但面试官未必买账,因为有些问题他需要你从底层实现起——貂蝉诸葛主;

啥,你说汇编?——黄盖主!还是开局鞭挞至一血的!

3.) Data Structure

题目类型大多是:给定一些实际需求,来设计相应的数据结构。所以,对每一种数据结构的特点、时间复杂度要非常熟悉,而且要有很敏锐的第一感。

a. Hashtables

可以说是人类发明的最重要的数据结构之一了。面试时的出现率极高!

保证你玩得转Collision strategies和hash function。如果深入到如何设计具体的hash function,题目的难度也会是很大的。

b. Trees

BST,BFS,DFS,trie,Kruskal’s Algorithm ,Prim’s Algorithm

Balanced tree就没什么研究必要了。

c. Graphs:

图的几种储存形式,BFS/DFS,Dijkstra,A* algorithm

d. Linked List/Queue/Stack/Heap

相应操作的时间复杂度要了如指掌。保证你能轻松写出C++ STL或Java Library对应类库的API。

4.) Algorithm

重中之重的重中之重!

Sort,Recursion,Binary Search,Greedy Strategy等等等等要全面准备到。

Dynamic Programming的一些经典题也要会。如果面Google,可能要准备一下DP的高级题目。

笔者认为,准备这类题目毫无捷径,只有不断刷题,总结,刷题,总结。要培养出对题目的直觉,这是一个漫长的训练过程。

在面试的时候,一般来说,要先给面试官提供一个暴力搜索的方法,然后计算复杂度。然后再慢慢做优化。面试时一定要keep talking,提出自己的想法,展现自己的思路。如果你get stuck,面试官也会给出相应的hint(当然这是会被扣分的)。

5.) System Design

常见形式是:给定大数据量和N台机器,解决一个特定的问题。较开放的题目。在网络公司的面试中经常出现。

解法有固定套路,可以参考Cracking the Coding Interview 相关章节,并自己做一些总结和应用。这类题目看起来很难,掌握方法后,实际难度并不算很高,而且容易展现自身的分析能力,容易出彩。当然,面试官很可能会做适当的延伸,涉及到具体的技术,这就靠自身平时的积累见招拆招了。

推荐的一些补充阅读材料:

External Sort (http://en.wikipedia.org/wiki/External_sorting)

Web Crawler (http://nlp.stanford.edu/IR-book/html/htmledition/web-crawling-and-indexes-1.html)

Google MapReduce (http://labs.google.com/papers/mapreduce.html)

Google File System (http://labs.google.com/papers/gfs.html)

6.) Mathematics

重点在于组合数学和概率论。会有一些这类的变体出现。稍微准备准备就可以了,相信国人的数学水平,绝对凌驾于世界巅峰,不管他面试官是阿三还是老美还是欧洲人。

7.) Operating Systems

Processes vs. Threads

Locks, mutexes and semaphores

Deadlock and livelock

Scheduling: FIFO, priority, shortest remaining time, round robin, and multi level.

不算特别重要。至少笔者从未遇过相关题目。

8.) Bit manipulation

两个目的:应付该类面试题(出现率不高,但是Google喜欢问);

用于自己的编程技巧——尽管有些silly,但是在代码中整一点bit manipulation,是很geek的事。

9.) Design Pattern

了解这些:Observer Pattern, Decorator pattern, Factory Pattern, Singleton Pattern

面试是一个很吃经验的考试。不要顾忌前几次的失败,那都是必要的练级。

最后,送上我笃信的一句话:”Success is just like being pregnant. Everybody congratulates you but nobody knows how many times you were fucked”。

谨以此祝愿所有的童鞋都能获得自己Dream Company的offer!

全文完。

Zhengli Sun©2011

Multithreading with Core Data on iOS

This post is copied from http://cutecoder.org/programming/multithreading-core-data-ios/

A while ago I recommended not to multithread at all with Core Data. That was true during Snow Leopard’s days. But with the advent of context hierarchy and the pervasive use of blocks on Lion and iOS 5, multi-threading with Core Data becomes approachable.

As I was working on Resonate, I discovered a number of lessons on writing multithreaded applications on iOS. A lot of these lessons were discovered “the hard way” – through misguided attempts and many crashes. I’d thought I share this with you so that you don’t need to go through the same mistakes.

Even though I use the word “multithreading”, chances are you won’t need to deal with threads directly. Using operation queues is the way to go for most of your multiprocessing mechanisms. Simply package your algorithms into distinct units of work and then let NSOperationQueuemanage them for you. Don’t create NSThread instances directly unless you have a pretty damn good reason why.

Use UIManagedDocument for Core Data apps on iOS.

Really, when you’re writing iOS 5 applications that uses Core Data, you’ll want to use UIManagedDocument. At a bare minimum, use this class to manage your Core Data stack. That is, let core data manage your instances of NSManagedObjectContextNSPersistentStoreCoordinator, and other parts of the Core Data stack.

Even if you need to share your Core Data classes with your Mac app, you can still separate your entity classes that will work accross both platforms. If you think that UIManagedDocument isn’t good enough for your app, you’d better have a damn good reason why.

When you use UIManagedDocument, you’ll get a set of interrelated NSManagedObjectContext instances. Amain context that you use as per normal with your GUI objects and the root context, which is the main context’s parent that are used for asynchronously saving data to persistent storage. When dealing with object contexts created by UIManagedDocument you should not save them directly – you’ll bypass undo management and a lot of other behind-the-scenes stuff. UIManagedDocument will periodically save these for you in the correct order.

Use the managed object context hierarchy

From this building block, I go further to recommend a pattern for using the new context hierarchy. I discovered this works nicely on iOS when I was working on Resonate.

  • Use the root context for saving data obtained by network refreshes.
  • Use the main context for GUI components and others that needs to be in the main thread.
  • Create individual worker contexts as children of the main context for background data processing tasks.

Core data context use

Use the Root Context for Network Refreshes

Being a Twitter client, Resonate often need to ask Twitter for the latest tweets and other data. When Twitter returns and Resonate parsed the resulting JSON data, it then invokes the root context’s operation queue to store the parsed JSON data into managed objects.

You can use this pattern yourself. You run network operations asynchronously, parse the resulting data, and then when you’re ready to store it you call performBlock on the root context.

You can see the pattern in the sequence diagram below. Typically the user initiates the refresh from an action in the view controller. If you use ASIHTTPRequest for your network I/O, you can make it run asynchronously. When the request is complete, you pass the raw data into an operation queue for parsing (note that +[ASIHTTPRequest sharedQueue] is convenient for this). When the data is parsed, you make use of the root context’s private queue to update the data to your managed objects. Upon completion of the update, you pass the object IDs of the updated objects back to your view controller in the main thread. Please take care not to pass the actual managed object instances between operation queues as this will certainly cause problems.

Network Refresh Pattern

One caveat of this approach is that you need to separate network-sourced data with user-edited data. That is attributes that can be updated from the network should not be editable by the user. This is to prevent conflicts when UIManagedDocument tries to saves your contexts.

Use Multiple Child Contexts for Worker Queues

If you need to do some long-running data processing, you should run those in a background operation queue and not in the main thread. But really to make your app responsive, anything that may take longer than half a second should be taken off the main thread – especially important in iOS devices where the CPU is a lot slower than the one found in OS X machines.

Those background operations should use their own private NSManagedObjectContext instances. I also recommend that these contexts are set as children of the main context. Why? Because when the operation completes and the context is saved then the results are “pushed” into the main context. This also rescues the GUI from having to refresh its NSManagedObjectContext to get the updated objects. Since you create these contexts yourself, you are responsible for saving them – unlike the main and root contexts that are owned by UIManagedDocument.

How to use it? Refer to the interaction diagram below. Typically the view controller creates the worker operation class (which is an instance of NSOperation). As part of the worker’s initial setup regime, the view controller provides its context as the parent context for use by the worker object. Then when the worker starts it creates its own private NSManagedObjectContext instance and use that context as it’s scratchpad data store. Just before the worker completes, it saves the context to push its changes to the main thread’s context.

Core Data for Background Processing

Conclusion

You now know how to multi-thread effectively in Core Data applications. Although this article focuses on iOS, you should be able to apply the same principles to OS X. You have learned about:

  • Using UIManagedDocument for managing your Core Data stack
  • The new NSManagedObjectContext hierarchy (nested contexts)
  • How use each level in the context hierarchy for different operation types (network refresh, main/GUI thread, worker operation).

Problems with nested contexts

This post is copied from http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains

When Mac OS X 10.7 and iOS 5 were released, I was so excited about the new features in Core Data. The additions of explicit concurrency types, nested contexts, and iCloud syncing promised to make developing with Core Data even more delightful. After evaluating these features, our team decided that we were going to use them in the next major release of one of our apps. Unfortunately (as is sometimes the case), some of these features aren’t quite ready for prime time.

Today I’m going to discuss nested context support in Core Data and various issues that exist with them. Some of these issues are minor bugs. Others may be considered functionally correct by the Core Data team, but result in unexpected behavior. Regardless, they add up to nested contexts being a feature you should avoid completely (as of this writing).

Background

So what are nested contexts anyway? To avoid a very lengthy explanation, I’m going to start by assuming that you understand the Core Data stack. Nested contexts allow you to set up a managed object context so that it accesses data from a parent context instead of from a persistent store. If you request an object from a managed object context that has a parent context, Core Data will first look in the parent. If the parent context has that object in memory, you’ll get a new managed object just like that one. So if there are changes in the parent, you’ll get the changed version of the object. If the object doesn’t exist in that context, it will keep going up through parent contexts until it finally fetches the data from the persistent store. Basically, retrieving data goes all the way up through all ancestor contexts. Saving, on the other hand, doesn’t traverse up through parent contexts — it only saves one level up. All of this is pretty well explained elsewhere, so I’m going to leave it at that.

Issues

Throughout the last 9 months, we’ve been using nested contexts in an attempt to simplify and improve some of the data processing that we do in our personal finance application, Koku. It feels like at every turn we’ve been finding issues in Core Data where nested contexts are to blame. Many of these issues are a bit difficult to explain because there is a lot of setup required to reproduce them. With that being said, here is what we’ve found so far:

Child contexts block ancestors

This is one of the most fundamental problems with nested contexts. When I was first told about this through some friends in Chicago, I didn’t believe it. Basically, when you execute a fetch request in a child context, it blocks the parent context while performing the fetch. What does this mean? Well it means that you can’t really use nested contexts to solve a lot of concurrency issues.

Here’s an example: you have a bunch of objects in your database that need to be fetched and have time-intensive code executed on them. The following code looks like it would solve your problems:

NSManagedObjectContext *asyncContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[asyncContext setParentContext:mainContext];
[asyncContext performBlock:^{
    // create fetch request, execute, and do work
}];

But executing that fetch request blocks the main context. Assuming that main context is running on the main thread, then this also makes your application stop responding.

I’ve commonly heard people using this setup for importing from some type of service. And this is where things start to get bad. Let’s say you’re importing a bunch of JSON objects and you need to check if each already exists in Core Data based on an identifier. You write code that loops and checks for an object in Core Data with that identifier, then creates a new object from the JSON data if it doesn’t exist (yes, that could be optimized). It may look like everything is going fine because your UI is still responsive through most of that import (especially if you’re reading from a stream since there will be a delay between each ID check). The truth is, though, that every time you check for an object with an identifier, you’re blocking your UI. It may only be for a fraction of a second, but no one is expecting this.

Now this in itself is not really a large problem. Once you understand it, it’s just another part of the framework that you work with/around. The real problem is that nothing in the documentation indicates that this will happen. In fact, the release notes read:

This pattern has a number of usage scenarios, including:

  • Performing background operations on a second thread or queue.
  • Managing discardable edits, such as in an inspector window or view.

Right there in the release notes, Apple is telling us to use nested contexts to do work in the background. But you absolutely should not use them for that. They will block the parent.

radar://10581934

Making changes into a parent context can be slow

Sometimes the cost of making changes in a child context is outrageous. Simply changing a boolean value on a bunch of objects can be prohibitively slow. It seems that Core Data tries to do something while processing pending changes on the child context. A reasonable assumption is that Core Data is trying to negotiate changes between the two contexts. All we’ve been able to determine is that there’s a lot going on with dictionary lookup that can hang the app. Everything takes far longer than if you were doing the same thing in a context that was tied directly to a persistent store coordinator.

Fetch results are wrong in a child context

It’s possible that fetch requests will not return the right results. I cannot even begin to understand how nested contexts got released when something this fundamental is broken. (Ok, so it’s broken in a very specific way, but still.)

Here’s how you can get Core Data to return the wrong results: set up a nested context relationship just like they describe for UIManagedDocument. For those who are unfamiliar, this involves an asynchronous context that is connected to the persistent store coordinator. The main queue context sets its parent to be this asynchronous context.

With the contexts set up, create a new object. Let’s imagine this object represents a blog and its variable name is awesomeBlog.  Add a couple of articles to that blog. Now if you were to set up a fetch request for all of the articles with a predicate of [NSPredicate predicateWithFormat:@"blog = %@", awesomeBlog], you’d get back the right results. But if you save the main context and then the async context (so the data is actually persisted to the disk) and try executing that fetch request, you would get no results. And it doesn’t matter if you set up the fetch request before or after you persist the data. You’ll never get the right results. Without nested contexts, this works just fine!

There is a workaround for this issue. You can simply obtain a permanent ID for the object when you create it. But there is a catch. The async context (at least in the case of UIManagedDocument) exists so that you can write data asynchronously. By getting a permanent ID for each object you create, you’ve made it so that you’re doing synchronous writes more frequently.

Oh, and as an added bonus, everyone who uses UIManagedDocument will experience this bug.

This has been reported as fixed for an upcoming release of iOS (which means the same should be true for Mac OS X).

radar://11891033 fixed in an upcoming release

Cannot access relationship in a child context for unsaved objects

This is pretty similar to the issue above in that it deals with relationships of objects that have been created. The difference is in the context setup and that it doesn’t really involve saving.

Here’s what can happen: let’s say you’re working in a managed object context called mainContext. It doesn’t need to have a parent context; it can be hooked up directly to the persistent store. Insert a new object into that context. Let’s call it a blog again. Add a few articles to that blog. Now, create a new managed object context and set it’s parent to mainContext. We’ll call this new context editing context. It’s another one of those common workflows that Apple documented in the release notes for nested contexts — discardable edits. But here’s what happens: when you access your blog ineditingContext, it has no articles.

The workaround for this is the same as the last one. Just obtain the permanent ID of the object beforehand. This has also been reported as fixed for Mac OS X 10.8 Mountain Lion (which means the same should be true for iOS 6).

radar://10209854 fixed in an upcoming release

Sorting is not honored when there are changes in a parent context

Sort descriptors are important, right? Well they don’t seem to always matter when you’re using nested contexts. If you have changes in a parent context and you execute a fetch request in the child, sorting does not work. Mix that with some of the above issues and you may get out of order, incomplete results. Yikes!

NSFetchedResultsController deadlocks

You never want your application to deadlock. With NSFetchedResultsController and nested contexts, it’s pretty easy to do. Using the sameUIManagedDocument setup described above, executing fetch requests in the private queue context while using NSFetchedResultsControllerwith the main queue context will likely deadlock. If you start both at about the same time it happens with almost 100% consistency.NSFetchedResultsController is probably acquiring a lock that it shouldn’t be. This has been reported as fixed for an upcoming release of iOS.

radar://11861499 fixed in an upcoming release

Crash after obtaining permanent IDs

A few of the issues above could be worked around by getting the permanent ID of an object. This can actually lead to a crash in certain circumstances, though. Again using the UIManagedDocument setup, create a new main queue context, editingContext, off of the existing main queue context,mainContext. Create a new object in editingContext. Save editingContext. Obtain a permanent ID for the same object in the mainContext. Core Data will eventually crash when cleaning up resources no longer in use.

radar://10480982 fixed in an upcoming release

Wrapping Up

If you made it through all of those, you’ve probably come to the conclusion that nested contexts are currently something to avoid. If not, the bullet points alone may have been enough to cause some unease. And these are only the issues that we’ve experienced. I wouldn’t be surprised if there are more. In discussions with peers, I’ve found that there are many people who are discovering some of these same issues and finding ways to work around them individually. I hope this list helps you make an informed decision about using nested contexts before you go down the same road we did. Also, please consider filing duplicate radars for those that are listed above. If you have anything to add to this list, please reach out to me on twitter or send me an email.

Thinking back over the lifetime of Core Data, it’s pretty unfortunate to see so many problems with the recently introduced features. I remember back when Core Data was released that SenTestingKit was just making its way into Xcode. At some point, Apple used Core Data as a poster child for unit testing. They claimed they were able to tune and enhance the performance in Core Data, making it screaming fast because of the comprehensive tests that they wrote. Whether this was marketing or not, the framework has been pretty solid throughout the years. The issues that have cropped up with nested contexts (and don’t even get me started on iCloud) feel like huge oversights and problems that fall nicely into the comprehensive test coverage category.

With all that being said, nested contexts are such a wonderful idea that could simplify architecture issues that so many developers using Core Data face. I look forward to the day that they work flawlessly and we get to start using them to make the next generation of data backed applications.

Update 2012.08.08: Updated information on radars. Update 2012.09.16: Updated information on radars.