ios xcode8xcode 内存泄漏漏 全是地址,怎么定位

2506人阅读
iOS进阶(21)
写于前:有很多写Leak教程的文章,当时在使用的时候一直卡在Step4,不能定位memory Leaks代码的位置,最后找到这篇文章的Step5说到dSYM文件。其他文章都没提及。
---&Xcode编译项目后,我们会看到一个同名的 dSYM 文件,dSYM 是保存 16 进制函数地址映射信息的中转文件
还有一个问题,之前在跑自己的demo的时候Cmd+I,一直闪退,具体原因没查出来。后来就先Cmd+R,之后通过下面的入口进去不会闪退~
啰嗦完了,下面看转载的正文吧~
iOS 5.0之后apple引入了Xcode编译器特性ARC(Automatic Reference Counting,自动引用计数)来帮助开发者管理内存,但为了追求app的高性能与减少安装包大小,工作中很多时候需要我们手动管理内存。再牛的开发者也不能保证自己写的code 100%没有内存泄露,出现内存泄露不可怕,可怕的是我们时间与精力花了大把,但内存泄露依旧没解决,即影响了工作效率也影响自己的心情。
  下面就讲解xcode中的内存调试神器---Instruments Leak ,不管是ios开发菜鸟,还是有经验的开发者,使用Instruments Leak调试内存泄露是必备技能之一。
  废话少说,下面开始摊大饼了!!!
   创建一个基于ARC的测试demo,部分测试代码如下:
  以上几行代码作为app代理入口method,IOS开发者应该是最熟悉不过了,由于创建的是手动管理内存工程,内存泄露的code line一眼就可以定位。
  使用Leaks开始动态分析,点击XCode的Product菜单Profile启动Instruments:
   点击Profile Button编译,呵呵,报错了,如果你遇到这种情况也不要紧张,先看下报错信息:&
  MyViewController与MyNavigationController是我在.pch预编译文件中定义的宏:
  为什么正常编译就没问题,在Profile 中就编译通不过了,其实这里并不是你的代码写的有问题,问题出在Profile的一个编译选项上:
  打开工程的Edit Scheme选项
  选择Profile,将Build Configuration设置为Debug,这样在.pch文件中,#ifdef DEBUG 编译条件下定义的宏就生效 了。
  再次选择Profile building,OK, Success !!!
  进入Instruments主页面,选择Leak Logo
  这时Demo程序也运行起来了,工具显示效果如下:
&  红色的柱子表示内存泄露了。怎么通过这个工具看到在哪泄露了呢?
   先在工具栏按下红色的圆形按钮,把工具监视内存的活动停下来。选择Leak,然后点中间十字交叉那,选择Call Tree   
     
  这时候右下角的Call Tree的可选项可以选了。选中Invert Call Tree 和Hide System Libraries,显示如下:
  看到这里,你最想知道的应该是项目中哪里的code内存泄漏了,ok, 下面我们就来定位内存泄漏的code line .
  看上图中红色框中的Symbol Name 列,如果你猜想0xedc00与0xedbda是内存地址,那么已经很接近正确答案了,可是这东西对我来说有卵用。其实玄机就隐藏在这里,Xcode编译项目后,我们会看到一个同名的 dSYM 文件,dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件,关于dSYM更多的细节,我将在后面的blog中说明。回到上面的问题,显示0xedc00与0xedbda是因为我们的工程build
settings 的问题,没有生成dSYM 文件,也就无法解析debug&symbols。下面我们就来正确设置dSYM选项:
  设置好之后,重新 profile build一次,这时候内存泄露的具体代码找到了,下面的红色框框里指定了那个方法出现了内存泄露。
  你只要在这些方法上双击,就会跳转到具体的代码,是不是很牛叉。
  解决内存泄漏问题,将创建的vc对象release掉就OK了,再用Instruments Leak工具分析看看,这时候再怎么操作,都没有内存泄露了。表明内存泄露被堵住了。
大饼摊完了,最后附上有兴趣的同学可以研究一下Instruments中其他工具的用法。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:127658次
积分:2264
积分:2264
排名:第14732名
原创:87篇
转载:39篇
评论:26条
(1)(3)(2)(6)(5)(22)(8)(2)(19)(34)(20)(4)小站会根据您的关注,为您发现更多,
看到喜欢的小站就马上关注吧!
下一站,你会遇见谁的梦想?
智捷iOS课堂-最专业iOS技术研究、应用研发、技术培训机构&iOS传播者&&&&
iOS开发那些事-关于性能优化–选择nib还是故事板的讨论
故事板是苹果在iOS5之后推出的技术,本意是集成多个nib文件于一个故事板文件,管理起来方便,故事板还能反应控制器之间的导航关系,很多导航是需要连连线就可以了,不需写代码,使用起来很方便。但是我告诫读者,从内存占用角度看故事板不是一个好的技术。为了比较我们使用Xcode中的Master-Detail模板分别创建,基于故事板的应用StoryboardDemo和基于nib的应用 NibDemo。然后通过Instruments工具的Allocations模板分析ViewController视图控制器加载的时候,内存占用方面 有多少差别。NibDemo工程的Allocations模板跟踪,StoryboardDemo工程的Allocations模板跟踪。
&画面启动用时00:02.776.562毫秒,内存占用1.10MB。画面启动用时00:02.911.718毫秒,内存占用1.11MB。NibDemo比StoryboardDemo画面启动时间要长,内存要多占用0.01MB,即约等于10KB。默认情况下工程中有一个故事板文件,它集成了应用中几乎所有的控制器,随着业务复杂度增加,在故事板的IB设计画面会变的杂乱无比,故事板文件会变得非常的庞大。应用程序在加载故事板时候有些迟缓,内存的占用也会增加。事实上nib仍然是比较好的技术,只不过不能表达画面之间的导航关系,画面导航要手工编写代码。
iOS开发那些事-响应内存警告
好的应用应该在系统内存警告情况下释放一些可以重新创建的资源。在iOS中我们可以在应用程序委托对象、视图控制器以及其它类中获得系统内存警告消息。1、应用程序委托对象在应用程序委托对象中接收内存警告消息,需要重写applicationDidReceiveMemoryWarning:方法。AppDelegate的代码片段:- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application{NSLog(@&AppDelegate中调用applicationDidReceiveMemoryWarning:&);}2、视图控制器在视图控制器中接收内存警告消息,需要重写didReceiveMemoryWarning方法。ViewController的代码片段:- (void)didReceiveMemoryWarning{NSLog(@&ViewController中didReceiveMemoryWarning调用&);[super didReceiveMemoryWarning];//释放成员变量[_listTeams release];}注意释放资源代码应该放在[super didReceiveMemoryWarning]语句下面。3、其它类在其它类中可以使用通知,在内存警告时候iOS系统会发出 UIApplicationDidReceiveMemoryWarningNotification通知,凡是在通知中心注册了 UIApplicationDidReceiveMemoryWarningNotification通知的类都会接收到内存警告通知。 ViewController的代码片段:- (void)viewDidLoad{[super viewDidLoad];NSBundle *bundle = [NSBundle mainBundle];NSString *plistPath = [bundle pathForResource:@"team"ofType:@"plist"];//获取属性列表文件中的全部数据NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];self.listTeams =[array release];
&&& //接收内存警告通知,调用handleMemoryWarning方法处理
&&& NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
&&& [center addObserver:self
&&&&&&&&&&&&&& selector:@selector(handleMemoryWarning)
&&&&&&&&&&&&&&&&&& name:UIApplicationDidReceiveMemoryWarningNotification
&&&&&&&&&&&&&&&& object:nil];}//处理内存警告
-(void) handleMemoryWarning
&&& NSLog(@&ViewController中handleMemoryWarning调用&);
}我们在viewDidLoad方法中注册UIApplicationDidReceiveMemoryWarningNotification消 息,接收到报警信息调用handleMemoryWarning方法。这些代码完全可以写在其它类中,在ViewController中重写 didReceiveMemoryWarning方法就可以了,本例这是示意性介绍一下 UIApplicationDidReceiveMemoryWarningNotification报警消息。内存警告在设备上出现并不是经常的,一般我们没有办法模拟,但模拟器上有一个功能可以模拟内存警告,启动模拟器,选择模拟器菜单硬件&模拟内存警告,这个时候我们会在输出窗口中看到内存警告发生了。 16:49:16.419 RespondMemoryWarningSample[38236:c07] Received memory warning. 16:49:16.422 RespondMemoryWarningSample[38236:c07] AppDelegate中调用applicationDidReceiveMemoryWarning: 16:49:16.422 RespondMemoryWarningSample[38236:c07] ViewController中handleMemoryWarning调用 16:49:16.423 RespondMemoryWarningSample[38236:c07] ViewController中didReceiveMemoryWarning调用
iOS开发那些事-性能优化–autorelease的使用问题
在MRR中释放对象通过release或autorelease消息实现,release消息会立刻使引用计数-1释放,发送 autorelease消息会使对象放入内存释放池中延迟释放,对象的引用计数并不真正变化,而是向内存释放池中添加一条记录,直到当池被销毁前会通知池 中的所有对象全部发送release消息真正将引用计数减少。由于会使对象延迟释放,除非必须,否则不要使用autorelease释放对象,在iOS程序中默认内存释放池的释放是在程序结束,应用程序入口main.m文件代码如下:int main(int argc, char *argv[]){@autoreleasepool {return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}}代码被包裹在@autoreleasepool {& }之间,这是池的作用范围,默认是整个应用。如果产生大量对象采用autorelease释放也会导致内存泄漏。那么什么时候autorelease是必须呢?我们看看下面代码:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{static NSString *CellIdentifier = @&CellIdentifier&;UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];if (cell == nil) {cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];}NSUInteger row = [indexPath row];NSDictionary *rowDict = [self.listTeams objectAtIndex:row];cell.textLabel.text =& [rowDict objectForKey:@"name"];NSString *imagePath = [rowDict objectForKey:@"image"];imagePath = [imagePath stringByAppendingString:@".png"];cell.imageView.image = [UIImage imageNamed:imagePath];cell.accessoryType = UITableViewCellAccessoryDisclosureI}其中的cell对象不能马上release,我们需要使用它设置表视图画面。autorelease一般应用于为其它调用者提供对象的方法中,对象在该方法不能马上release,而需要延迟释放。此外,还有一种情况下使用了autorelease,即前文提到的&类级构造方法&:NSString *message = [NSString stringWithFormat:@"您选择了%@队。", rowValue];该对象的所有权虽然不是当前调用者,但它是由iOS系统通过发送autorelease消息放入到池中的,当然这一切对于开发者都是不可见的,我们也要注意减少使用这样的语句。
iOS开发那些事-性能优化–查找和解决僵尸对象
内存泄漏是当一个对象或变量在使用完成后没有释放掉,那么如果我们走了另外一个极端情况会什么样呢?这就导致过渡释放(over release)问题,从而使对象&僵尸化&,对象称为僵尸(zombies)对象。一个对象已经被释放过了,或者调用者没有这个对象的所有权而释放它, 都会造成过渡释放,产生僵尸对象。僵尸对象或许对很多人听起来很恐怖、也很陌生,如果要说起EXEC_BAD_ACCESS异常,可能大家并不陌生。试图调用僵尸对象方法应用会崩溃(应用直接跳出),并抛出异常EXEC_BAD_ACCESS。我们看看代码ViewController的代码片段:- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{NSUInteger row = [indexPath row];NSDictionary *rowDict = [self.listTeams objectAtIndex:row];NSString *rowValue& =& [rowDict objectForKey:@"name"];NSString *message = [[NSString alloc] initWithFormat:@&您选择了%@队。&, rowValue];UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@&请选择球队&message:messagedelegate:selfcancelButtonTitle:@&Ok&otherButtonTitles:nil];
&&& [alert release];&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&& &&&&&&&&&&&&&&&&&& &&&&&&&& ①[message release];
&&& [alert show];&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&& &&&&&&&& ②[tableView deselectRowAtIndexPath:indexPath animated:YES];}注意看程序代码的黑体部分,你会发现什么问题吗?程序运行的结果抛出EXEC_BAD_ACCESS异常。假设我们现在无法找到问题,可以使用 Instruments工具的Zombies跟踪模板。其中Instruments选择Zombies模板,点击Profile按钮就可以进入了。
点击Allocations的&i&按钮,弹出Target菜单配置Zombies模板,在Launch Configuration中勾选Record reference counts和Enable NSZombie detection。其中Record reference counts是显示引用计数,Enable NSZombie detection是能够检测僵尸对象。
这样在程序运行的时候,如果发现僵尸对象它就会弹出一个对话框,点击其中&&&按钮,在屏幕的下方会显示僵尸对象的详细信息。
僵尸对象为UIAlertView类型,从上到下僵尸对象是引用计数的变化是:创建 & 释放 & 僵尸化。打开扩展详细视图,在右边的跟踪堆栈信息进入我们程序代码,会打开对应代码,定位僵尸对象。
在上面的3条高亮显示的代码会影响对象引用计数,从中我们不难发现问题。关于解决方案就本例而言需要将代码②行的显示警告框的[alert show]语句,放在[alert release]语句之前调用就可以了。这就是僵尸对象问题。
iOS开发那些事--性能优化–内存泄露问题的解决
内存泄漏问题的解决内存泄漏(Memory Leaks)是当一个对象或变量在使用完成后没有释放掉,这个对象一直占有着这块内存,直到应用停止。如果这种对象过多内存就会耗尽,其它的应用就无法运行。这个问题在C++、C和Objective-C的MRR中是比较普遍的问题。在Objective-C中释放对象的内存是发送release和autorelease消息,它们都是可以将引用计数减1,当为引用计数为0时候,release消息会使对象立刻释放,autorelease消息会使对象放入内存释放池中延迟释放。上代码:- (void)viewDidLoad{[super viewDidLoad];NSBundle *bundle = [NSBundle mainBundle];NSString *plistPath = [bundle pathForResource:@"team"ofType:@"plist"];//获取属性列表文件中的全部数据self.listTeams = [[NSArray alloc] initWithContentsOfFile:plistPath];}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{static NSString *CellIdentifier = @&CellIdentifier&;UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];if (cell == nil) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];}NSUInteger row = [indexPath row];NSDictionary *rowDict = [self.listTeams objectAtIndex:row];cell.textLabel.text =& [rowDict objectForKey:@"name"];NSString *imagePath = [rowDict objectForKey:@"image"];imagePath = [imagePath stringByAppendingString:@".png"];cell.imageView.image = [UIImage imageNamed:imagePath];cell.accessoryType = UITableViewCellAccessoryDisclosureI}- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{NSUInteger row = [indexPath row];NSDictionary *rowDict = [self.listTeams objectAtIndex:row];NSString *rowValue& =& [rowDict objectForKey:@"name"];NSString *message = [[NSString alloc] initWithFormat:@&您选择了%@队。&, rowValue];UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@&请选择球队&message:messagedelegate:selfcancelButtonTitle:@&Ok&otherButtonTitles:nil];[alert show];[tableView deselectRowAtIndexPath:indexPath animated:YES];}大 家看看上面的3个方法会有什么问题呢?如果代码是基于ARC的是没有问题的,遗憾的是基于MRR,上面的代码都存在内存泄漏的可能性。理论上讲内 存泄漏是对象或变量没有释放引起的,但实践证明并非所有的未释放对象或变量都会导致内存泄漏,这与硬件环境和操作系统环境有关,因此我们需要检测工具帮助 我们找到这些&泄漏点&。在Xcode中提供了两种工具帮助查找泄漏点:Analyze和Profile,Analyze是静态分析工具 可以通过菜单 Product&Analyze启动,为静态分析之后的代码画面;Profile是动态分析工具,这个工具叫&Instruments&,它是Xcode 集成在一起,可以在Xcode中通过菜单Product&Profile启动,Instruments有很多Trace Template(跟踪模板)可以动态分析和跟踪内存、CPU和文件系统。
我们可以两个工具结合使用查找泄漏点,先使用Analyze静态分析查找可疑泄漏点,再用Profile动态分析中的Leaks和Allocations跟踪模板进行动态跟踪分析,确认这些点是否泄漏,或者是否有新的泄漏出现等。其 中的线段表明了程序执行的路径,在这个路径中,1:说明在25行Objective-C对象引用计数是1,说明在这里创建了一个 Objective-C对象;2:说明在27行引用计数为1这个,该对象没有释放,怀疑有泄漏。这样的说明已经很明显的告诉我们问题所在了, [[NSArray alloc] initWithContentsOfFile:plistPath]创建了一个对象,并赋值给 listTeams属性所代表的成员变量,然而完成了赋值工作之后,创建的对象并没有显示地发送release和autorelease消息。代码修改NSArray *array = [[NSArray alloc] initWithContentsOfFile:plistPath];self.listTeams =[array release];我们看一下tableView:cellForRowAtIndexPath:方法中的疑似泄漏点行末尾的蓝色图标展开分析结果
其 中主要是说明UITableViewCell*类型的cell对象在64行有可能存在泄漏。在表视图中 tableView:cellForRowAtIndexPath:方法是为表视图单元格实例化并设置数据的,因此cell对象实例化后不能马上 release,应该使用autorelease延迟释放。可以在创建cell对象的时候发送autorelease消息,代码修改如下:if (cell == nil) {cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];}我们看一下tableView:didSelectRowAtIndexPath:方法中的疑似泄漏点有两个,行末尾的图标展开分析结果。&&message对象创建之后没有释放,我们只需要在[alert show]之后添加[message release]语句代码就可以了。在Objective-C中实例化对象有两种方式:NSString *message = [[NSString alloc] initWithFormat:@&您选择了%@队。&, rowValue]; &&&&&&&&&&&&&&&&&&&&&&&&&&& ①NSString *message = [NSString stringWithFormat:@"您选择了%@队。", rowValue]; &&&&&&&&&&&&&&&&&&&&&&&&& ②① 行所示以init-开头构造方法,它的是在alloc之后调用该方法我们称为&实例构造方法&,该方法创建对象所有权是调用者,调用者需要对它的 生命周期负责,具体说负责创建和释放。而另一种是②行所示string-(去掉NS后类名)开头方法,它是通过类直接调用我们称为&类级构造方法&,该方 法是创建的对象所有权非调用者所有,调用者不无权释放它,否则就会因过渡释放而&僵尸化&,这个问题我们会在下一节介绍。UIAlertView*类型alert对象创建之后没有释放,我们只需要在[alert show]之后添加[alert release]语句代码就可以了,修改之后的代码- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{NSUInteger row = [indexPath row];NSDictionary *rowDict = [self.listTeams objectAtIndex:row];NSString *rowValue& =& [rowDict objectForKey:@"name"];NSString *message = [[NSString alloc] initWithFormat:@&您选择了%@队。&, rowValue];UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@&请选择球队&message:messagedelegate:selfcancelButtonTitle:@&Ok&otherButtonTitles:nil];[alert show];[alert release];[message release];[tableView deselectRowAtIndexPath:indexPath animated:YES];}上 面介绍的使用Analyze静态分析查找可疑泄漏点,之所以称为&可疑泄漏点&,但是这些点未必一定泄漏,确认这些点是否泄漏还要通过 Profile动态分析工具Instruments中的Leaks和Allocations跟踪模板,Analyze静态分析只是一个理论上的预测过程。 通过菜单Product&Profile启动, Profile动态分析工具中选择Leaks模板
Instruments 中虽然是选择了Leaks模板,但默认情况也会添加Allocations模板,基本上凡是分析内存都会使用 Allocations模板,它可以监控内存分布情况,选中Allocations模板(图中①区域),右边③区域会显示随着时间的变化内存使用折线图 表,同时在④区域会显示内存使用的详细信息,其中刚刚对象分配情况。点击Leaks模板(图中②区域),可以查看内存泄漏情况,如果在③区域有红线出现, 则有内存泄漏,④区域会显示泄漏的对象。
出 现的泄漏是在点击表视图中单元格测试tableView:didSelectRowAtIndexPath:方法方法时候发生的,其中 NSCFString类型的对象发生了泄漏,NSCFString类型在NSFoundation中是NSString*类型。点击泄漏对象前面的三角形 展开对象,可以看到它们的内存地址、占用字节、所属框架和响应方法信息。
打开扩展详细视图,可以看到右边的跟踪堆栈信息,其中我们自己应用代码,可以点击进入我们程序代码,会打开对应代码。 代码77并不是泄漏点,而是其中的NSString*类型对象在之后发生了泄漏,因此可以断定是message对象之后没有释放导致泄漏。我们修改代码如下:- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{NSUInteger row = [indexPath row];NSDictionary *rowDict = [self.listTeams objectAtIndex:row];NSString *rowValue& =& [rowDict objectForKey:@"name"];NSString *message = [[NSString alloc] initWithFormat:@&您选择了%@队。&, rowValue];UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@&请选择球队&message:messagedelegate:selfcancelButtonTitle:@&Ok&otherButtonTitles:nil];[alert show];
&&& [message release];[tableView deselectRowAtIndexPath:indexPath animated:YES];}添 加[message release]语句。很多人还会猜测alert对象(UIAlertView*)会有泄漏,因此重新运行Instruments工具,反复点击单元格测 试,并未发现表示内存泄漏的红线! Instruments工具认为alert对象不释放不会引起内存泄漏,如果我们想进一步评估它对于内存的应用,这个时候我们可以看看 Allocations模板的折线图表,每次点击总占用内存数都有所增加,这说明alert对象没有释放虽然不是很严重,但是也会增加占用内存,因此 alert对象释放也是必须的。
这就是我们介绍的内存泄漏问题解决方法,事实上内存泄漏是极其复杂问题,工具使用是一方面,经验是另一方面。提高经验,然后借助于工具才是解决内存泄漏的根本。
iOS开发那些事--iOS视图生命周期与视图控制器关系
iOS中视图是一个应用的重要组成部分,功能的实现与其息息相关,而视图控制器控制着视图,其重要性在整个应用中不言而喻。
视图生命周期与视图控制器关系
以视图的5种状态为基础,我们来系统的了解一下视图控制器的生命周期。在视图不同的生命周期,视图控制器会回调不同的方法。
viewDidLoad方法:视图控制器已被实例化,在视图被加载到内存中的时候调用该方法,这个时候视图并未出现。在该方法中通常进行的是对所控制的视图进行初始化处理。
视 图可见前后会调用viewWillAppear:方法和viewDidAppear:方法;视图不可见前后会调用viewWillDisappear:方 法和viewDidDisappear:方法。 4个方法调用父类相应的方法以实现其功能, 编码时该方法的位置可根据实际情况做以调整,参见如下代码:
-(void)viewWillAppear:(BOOL)animated
[super viewWillAppear:YES];
viewDidLoad 方法在应用运行的时候只调用一次,而这上述4个方法可以被反复调用多次,它们的使用很广泛但同时也具有很强的技巧性。例如:有的应用会使用重力加速计,重 力加速计会不断轮询设备以实时获得设备在z轴、x轴和y轴方向的重力加速度。不断的轮询必然会耗费大量电能进而影响电池使用寿命,我们通过利用这4个方法 适时地打开或者关闭重力加速计来达到节约电能的目的。怎么使用这4个方法才能做到&适时&是一个值得思考的问题。
iOS系统在低内存时情况 下会调用didReceiveMemoryWarning:和viewDidUnload:方法。iOS6之后就不再使用viewDidUnload:, 而仅支持didReceiveMemoryWarning:。didReceiveMemoryWarning:方法的主要职能是释放内存,包括视图控制 器中的一些成员变量和视图的释放。现举例如下:
- (void)didReceiveMemoryWarning {
self.button =self.myStringD =[myStringC release];
[super didReceiveMemoryWarning];}
iOS开发那些事--iOS6 UI状态保持和恢复
iOS设计规范中要求,当应用退出的时候(包括被终止运行时候),画面中UI元素状态需要保持的,当再次进来的时候看状态与退出是一样的。iOS6之后苹果提供以下API使得UI状态保持和恢复变得很容易了。在iOS6中我们可以在3地方实现状态保持和恢复:应用程序委托对象视图控制器自定义视图为了演示这个功能实现,我们把基于故事板的HelloWorld工程改造一下,在画面中添加一个文本框
用户在文本框中输入一些内容,应用程序退出并且终止后再次进来的时候,文本框中还会保持原来输入的内容。然后在IB的Scene中选中View Controller,打开右边的标识检查器,设置Restoration ID(恢复标识)为viewController。&恢复标识是iOS6为了实现UI状态保持和恢复添加的设置项目。我们还需要在应用程序委托对象AppDelegate代码部分做一些修改,添加的代码如下:-(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder{return YES;}&-(BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder{return YES;}&- (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder{[coder encodeFloat:2.0 forKey:@"Version"];}&- (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder{float lastVer = [coder decodeFloatForKey:@"Version"];NSLog(@&lastVer = %f&,lastVer);}其中application:shouldSaveApplicationState:在应用退出的时候调用,负责控制是否允许保存状态,返回YES情况是可以保存,NO是不保存。application:shouldRestoreApplicationState:是应用启动时候调用,负责控制是否恢复上次退出的时候的状态,返回YES情况是可以恢复,NO是不恢复。application:willEncodeRestorableStateWithCoder:方法是保存时候调用,在这个方法中实现UI状态或数据的保存,其中[coder encodeFloat:2.0 forKey:@"Version"]是保存简单数据。application:didDecodeRestorableStateWithCoder:方法是恢复时候调用,在这个方法中实现UI状态或数据的恢复,其中[coder decodeFloatForKey:@"Version"]语句是恢复上次保存的数据。要想实现具体画面中控件的保持和恢复,还需要在它视图控制器中添加一些代码,ViewController.m中添加的代码如下:-(void)encodeRestorableStateWithCoder:(NSCoder *)coder{[super encodeRestorableStateWithCoder:coder];[coder encodeObject:self.txtField.text forKey:kSaveKey];}&-(void)decodeRestorableStateWithCoder:(NSCoder *)coder{[super decodeRestorableStateWithCoder:coder];self.txtField.text = [coder decodeObjectForKey:kSaveKey];}在iOS6之后视图控制器都添加了两个:encodeRestorableStateWithCoder:和 decodeRestorableStateWithCoder:用来实现该控制器中的控件或数据的保存和恢复。其中 encodeRestorableStateWithCoder: 方法是在保存时候调用,[coder encodeObject:self.txtField.text forKey:kSaveKey]语句是按照指定的键保存文本框的内容,decodeRestorableStateWithCoder:方法是在恢复时 候调用,[coder decodeObjectForKey:kSaveKey]是恢复文本框内容时候调用,保存和恢复事实上就是向一个归档文件中编码和解码的过程。
iOS开发那些事--nib、xib与故事板的关系
nib、xib与故事板如果大家使用过苹果的官方资料,一定会发现某些资料上会提到nib文件,那么nib与xib是怎样的一种关系呢?最初只有nib文件,后来将其更名为xib,但大家一直沿袭nib这个叫法(即称xib文件为nib文件),所以目前为止,nib等同于xib。xib文件采用xml格式。前文已提到故事板是用来替代xib的,那么两者除后缀名外,还存在哪些差异呢?首先,在数量上,使用故事板技术时,一个工程只有一个故事板文件。当使用xib技术时,xib在数量上与视图控制器相对应,而一个工程可能会有很多视图控制器,相应地就会有很多xib文件。其次,故事板与视图的关系可以在IB设计器中很明显地体现,而xib与视图的关系则需要查看相关代码或利用其他资源。下面我们来举例说明。我们要做这样一个应用:两个不同的画面,有两个标签分别与其对应,点击标签两个画面实现互相切换,该应用采用标签栏导航模式,设计原型草图见图
选择Tabbed Application模板,分别采用xib和故事板文件实现&&&&可以看到采用xib技术的时候两个画面有两个xib文件,而采用故事板时候两个画面只有一个MainStoryboard.storyboard文件打开MainStoryboard.storyboard文件。
应用包含两个视图,两个视图存在切换关系,这些信息从上图中一目了然。事实上故事板是多个xib文件集合的描述文件,也采用xml格式。需要特别提出的是,虽然苹果官方主张使用故事板,但最正确的做法是我们要根据具体情况、具体问题对故事板和xib做以取舍,而不是一概而论。当应用 数据量很大、画面很多、关系很复杂的时候,如果使用storyboad技术,那我们在IB设计器中的工作就会变得庞大而复杂,除此之外整个工程的性能也会 受到一定影响。
iOS开发那些事--创建基于故事板的iOS 6的HelloWorld
基于故事板的HelloWorld工程Storyboard(故事板)是用来替代xib的技术,也是iOS 5最重要的新特性之一。我们用Storyboard(故事板)重构HelloWorld。
使用故事板重构HelloWorld勾选&Use Storyboards&项。工程创建完成之后,通过导航进入MainStoryboard.storyboard&添加Label控件
iOS开发那些事--创建基于nib的iOS 6的HelloWorld工程
创建基于nib的HelloWorld工程
创建工程启动Xcode,点击File&New&Project菜单,在打开的Choose a template for your new project界面中,选择Single View Application工程模板
然后点击Next按钮,随即出现界面。&&这里我们需要按照提示并结合自己的实际情况和需要输入相关内容。下面简要说明选项。
Product Name:工程名字。
Organization Name:组织名字。
Company Identifier:公司标识(很重要)。一般是将公司的域名倒过来输入进去(如com.51work6),这类似于Java中的包命名。
Bundle Identifier:捆绑标识符(很重要)。该标识符由Product Name+ Company Identifier构成。因为在App Store发布应用的时候会用到它,所以它的命名不可重复。
Class Prefix:类的前缀。为生成的类加前缀(如XZY ViewController)。
Devices:选择设备。可以构建基于iPhone或iPad的工程,也可以构建通用工程。通用工程是指一个工程可以同时适应iPhone和iPad,不论在iPhone上还是iPad上都可以正常运行。
Use Storyboards:工程是否采用故事板技术。
Use Automatic Reference Counting:工程是否采用ARC(自动引用计数)技术。ARC可以帮助我们管理工程的内存。
Include Unit Tests:是否产生单元测试相关的类。设置完相关的工程选项后,点击Next按钮,进入下一级页面。根据提示选择存放文件的位置,然后点击Create按钮。
在右下角的对象库中选择Label控件,将其拖曳到View上并调整其位置。双击Label控件,使其处于编辑状态(也可以通过控件的属性来设置),在其中输入Hello World
至此,整个工程创建完毕。
iOS开发那些事--编写OCUnit测试方法-应用测试方法
应用测试方法
应用测试是测试应用程序的一些功能,这个功能具体到点击一个按钮触发一个事件,因此它主要是测试表示层。我们看看视图控制器ViewController.m中有那些方法需要测试,然后再来设计测试用例。
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
- (void)didReceiveMemoryWarning
[super didReceiveMemoryWarning];
- (IBAction)onClick:(id)sender {
//关闭键盘
[self.txtRevenue resignFirstResponder];
self.lblTax.text =[self calculate:self.txtRevenue.text];
//计算个人所得税
-(NSString*) calculate:(NSString*)revenue {
viewDidLoad 和didReceiveMemoryWarning是否需要测试,要看我们是否这个方法中有一些自己编写的代码,就目前而言我们不需要测试它们。 onClick:是响应用户点击计算按钮的方法,它需要测试。calculate:方法是业务逻辑方法,我们在逻辑测试中测试过了,是否需要再测试呢?一 般情况下应该只在逻辑测试就可以了,但是如果该方法需要外部环境(依赖其它类或需要特殊运行环境等),逻辑测试无法提供则需要应用测试,这是应用测试能够 在设备上运行,它能够提供一个实际的、真实的测试环境。
下面我们实现onClick:方法的应用测试,我们要模拟点击按钮事件处理,它的输入条件通过文本框控件输入的,输出结果是通过标签控件展示的。设计测试用例选取常见值和边界值作为输入值,文本框的键盘限制为数字键盘。
输入验证不需要考虑太多,只需要考虑空情况,我们设计了6个用例。
onClick:方法应用测试用例
月收入总额(元)
月应纳个人所得税税额(元)
空白0.00测试不输入直接点击计算按钮
8000345.00测试整数
8000.59345.12测试小数
08000.59345.12测试有前导0数据
8195.15测试输入两个小数点
8195.15测试连在一起两个小数点
我们看看应用测试类AppllicationTest.h代码:
#import &AppDelegate.h&
#import &ViewController.h&
@interface AppllicationTest : SenTestCase
@property (nonatomic, strong) ViewController *viewC
应用测试类AppllicationTest.m中的setUp和tearDown方法代码:
- (void)setUp
[super setUp];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
UINavigationController *navController = (UINavigationController*)window.rootViewC
self.viewController&= (ViewController*)navController.topViewC
- (void)tearDown
self.viewController =
[super tearDown];
我们在setUp方法中需要初始化viewController属性,viewController代表的是一个视图控制器,它是iOS系统通过故事板文件创建,而不能简单的通过下面的语句实例化:
self.viewController&= [[ViewController alloc] init];
我 们可以通过应用程序委托对象AppDelegate获得window对象,每个window对象可以使用属性rootViewController取得它 的一个根视图控制器,本例中的根视图控制器是UINavigationController,而不是ViewController,所以我们还需要使用 UINavigationController的topViewController属性取得ViewController对象。
应用测试类AppllicationTest.m中的测试方法代码:
//测试不输入直接点击计算按钮
- (void)testOnClickInputBlank
STAssertNotNil(self.viewController, @&ViewController没有赋值。&);
//设定输入值
self.viewController.txtRevenue.text = @&";
//调用oncClick测试
[self.viewController onClick:nil];
//取得输出结果
NSString* strTax = self.viewController.lblTax.
STAssertEqualObjects(strTax, @&0.00&P, @&期望值是:0.00 实际值是:%@&, strTax);
//测试整数
- (void)testOnClickInputIntegerNumber
STAssertNotNil(self.viewController, @&ViewController没有赋值。&);
//设定输入值
self.viewController.txtRevenue.text = @&8000&P;
//调用oncClick测试
[self.viewController onClick:nil];
//取得输出结果
NSString* strTax = self.viewController.lblTax.
STAssertEqualObjects(strTax, @&345.00&P, @&期望值是:345.00 实际值是:%@&, strTax);
//测试小数
- (void)testOnClickInputOneDot
STAssertNotNil(self.viewController, @&ViewController没有赋值。&);
//设定输入值
self.viewController.txtRevenue.text = @&8000.59&P;
//调用oncClick测试
[self.viewController onClick:nil];
//取得输出结果
NSString* strTax = self.viewController.lblTax.
STAssertEqualObjects(strTax, @&345.12&P, @&期望值是:345.12 实际值是:%@&, strTax);
//测试输入两个小数点
- (void)testOnClickInputTwoDot
STAssertNotNil(self.viewController, @&ViewController没有赋值。&);
//设定输入值
self.viewController.txtRevenue.text = @&&P;
//调用oncClick测试
[self.viewController onClick:nil];
//取得输出结果
NSString* strTax = self.viewController.lblTax.
STAssertEqualObjects(strTax, @&8195.15&P, @&期望值是:8195.15 实际值是:%@&, strTax);
//测试有前导0数据
- (void)testOnClickInputPrefixZero
STAssertNotNil(self.viewController, @&ViewController没有赋值。&);
//设定输入值
self.viewController.txtRevenue.text = @&08000.59&P;
//调用oncClick测试
[self.viewController onClick:nil];
//取得输出结果
NSString* strTax = self.viewController.lblTax.
STAssertEqualObjects(strTax, @&345.12&P, @&期望值是:345.12 实际值是:%@&, strTax);
//测试连在一起两个小数点
- (void)testOnClickInputLinkDot
STAssertNotNil(self.viewController, @&ViewController没有赋值。&);
//设定输入值
self.viewController.txtRevenue.text = @&&P;
//调用oncClick测试
[self.viewController onClick:nil];
//取得输出结果
NSString* strTax = self.viewController.lblTax.
STAssertEqualObjects(strTax, @&8195.15&P, @&期望值是:8195.15 实际值是:%@&, strTax);
这 些测试方法都是非常类似的,首先需要使用STAssertNotNil宏判断一下self.viewController是否为nil,然后 self.viewController.txtRevenue.text设置文本框值,真正运行的时候我们是通过文本框控件输入的。语句 [self.viewController onClick:nil]是测试核心目的,参数是按钮对象指针onClick:中没有使用,传递nil就可以了。输出结果的取得是从lblTax标签控件 中取得的。最后使用STAssertEqualObjects宏断言。
iOS开发那些事--编写OCUnit测试方法-逻辑测试方法
应用测试和逻辑测试
添加OCUnit到工程时候,我们提到过,应用测试(Application Testing)和逻辑测试(Logic Testing)两个概念,它们并非是OCUnit中的概念,而是单元测试中概念。应用测试是对整个应用程序进行的测试,设计测试用例时候要考虑到运行环 境等因素,例如在测试JavaEE时候需要考虑Web容器和EJB容器等环境问题。而逻辑测试则是轻量级的,只测试某个业务逻辑对象的方法或算法正确性。
编写OCUnit测试方法每一个单元测试用例对应于测试类中的一个方法,因此测试类分为:逻辑测试类和应用测试类,在设计测试用例时候,逻辑测试和应用测试也是不同的。编写 OCUnit测试方法也是要分逻辑测试和应用测试。下面我们还是通过计算个人所得税应用介绍,它们的编写过程,被测试类ViewController编写 过程不再介绍。1、逻辑测试方法逻辑测试应该测试计算个人所得税的业务逻辑,即测试ViewController类中的calculate:方法LogicTest.h的代码如下:#import &SenTestingKit/SenTestingKit.h&#import &ViewController.h&@interface LogicTest : SenTestCase@property (nonatomic,strong) ViewController *viewC@end在h文件中定义viewController属性,注意定义为属性参数设置为strong。LogicTest.m的代码如下:#import &LogicTest.h&@implementation LogicTest- (void)setUp{[super setUp];self.viewController = [[ViewController alloc] init];}- (void)tearDown{self.viewController =[super tearDown];}//测试月应纳税额不超过1500元 用例1- (void)testCalculateLevel1{double dbRevenue = 5000;NSString *strRevenue = [NSString stringWithFormat:@"%f",dbRevenue];NSString* strTax =[self.viewController calculate:strRevenue];STAssertTrue([strTax doubleValue] == 45, @&期望值是:45 实际值是:%@&, strTax);}//测试月应纳税额超过1500元至4500元 用例2- (void)testCalculateLevel2{double dbRevenue = 8000;NSString *strRevenue = [NSString stringWithFormat:@"%f",dbRevenue];NSString* strTax =[self.viewController calculate:strRevenue];STAssertTrue([strTax doubleValue] == 345, @&期望值是:345 实际值是:%@&, strTax);}//测试月应纳税额超过4500元至9000元 用例3- (void)testCalculateLevel3{double dbRevenue = 12500;NSString *strRevenue = [NSString stringWithFormat:@"%f",dbRevenue];NSString* strTax =[self.viewController calculate:strRevenue];STAssertTrue([strTax doubleValue] == 1245, @&期望值是:1245 实际值是:%@&, strTax);}//测试月应纳税额超过9000元至35000元 用例4- (void)testCalculateLevel4{double dbRevenue = 38500;NSString *strRevenue = [NSString stringWithFormat:@"%f",dbRevenue];NSString* strTax =[self.viewController calculate:strRevenue];STAssertTrue([strTax doubleValue] == 7745, @&期望值是:7745 实际值是:%@&, strTax);}//测试月应纳税额超过35000元至55000元 用例5- (void)testCalculateLevel5{double dbRevenue = 58500;NSString *strRevenue = [NSString stringWithFormat:@"%f",dbRevenue];NSString* strTax =[self.viewController calculate:strRevenue];STAssertTrue([strTax doubleValue] == 13745, @&期望值是:13745 实际值是:%@&, strTax);}//测试月应纳税额超过55000元至80000元 用例6- (void)testCalculateLevel6{double dbRevenue = 83500;NSString *strRevenue = [NSString stringWithFormat:@"%f",dbRevenue];NSString* strTax =[self.viewController calculate:strRevenue];STAssertTrue([strTax doubleValue] == 22495, @&期望值是:22495 实际值是:%@&, strTax);}//测试月应纳税额超过80000元 用例7- (void)testCalculateLevel7{double dbRevenue = 103500;NSString *strRevenue = [NSString stringWithFormat:@"%f",dbRevenue];NSString* strTax =[self.viewController calculate:strRevenue];STAssertTrue([strTax doubleValue] == 31495, @&期望值是:31495 实际值是:%@&, strTax);}@end在setUp方法中初始化viewController,在tearDown方法中释放viewController属性。测试方法 testCalculateLevel1~ testCalculateLevel7是对应测试用例1~7,测试方法中STAssertTrue是OCUnit框架宏断言,这些与断言有关的宏。OCUnit框架断言宏
STAssertEqualObjects
当两个对象不相等,或者是其中一个对象为nil时候断言失败;
STAssertEquals
当参数1不等于参数2时候断言失败,用于C中基本数据测试;
STAssertNil
当参数不是nil时候断言失败;
STAssertNotNil
当参数是nil时候断言失败;
STAssertTrue
当表达式为false时候断言失败;
STAssertFalse
当表达式为ture时候断言失败;
STAssertThrows
如果表达式没有抛出异常,则断言失败;
STAssertNoThrow
如果表达式抛出异常,则断言失败;
iOS开发那些事--OCUnit测试框架
使用OCUnit测试框架iOS单元测试框架&原则上,是否使用测试框架都不会影响单元测试结果,但是&工欲善其事,必先利其器&使用单元测试框架更便于我们测试和分析结果。&主要的iOS单元测试框架有:&OCUnit,是开源测试框架,与Xcode工具集成在一起使用非常方便,测试报告以文本形式输出到输出窗口;&GHUnit,是开源测试框架,它可以将测试报告以应用形式可视化输出到设备或模拟器上,也可以以文本形式输出到输出窗口;GHUnit可以测试OCUnit编写的测试用例;&OCMock,是开源测试框架,它主要为测试提供Mock对象(伪对象)。&
添加OCUnit到工程&添加OCUnit到工程中有两种方法,一种是在创建工程时添加,勾选&include Unit Tests&;另一种是在现有工程中添加&Cocoa Touch Unit Testing Bundle&Target来实现。下面我们详细介绍这两种添加过程。&1、创建工程时候勾选&include Unit Tests&&该种方式添加的单元测试属于应用测试(Application Testing)。在创建一个工程时,如果采用&Single View Application&模板,在选项中勾选&include Unit Tests&即可在工程中添加OCUnit框架。&
&工程创建完,在导航面板中会多一个PITaxTests组(&工程名&Tests),包含PITaxTests测试类。在右边的Target栏中多了一个PITaxTests Target。&
&但是打开Scheme列表还只有一个PITax,这是我们需要注意的。运行它可以通过:选择菜单Product&Test或工具栏中Test按钮(下拉Run按钮选择)或快捷键command+U等几种方式。&如果打开Frameworks组会发现添加了SenTestingKit.framework,SenTestingKit.framework就 是OCUnit框架。因为单元测试框架一般命名为xUnit,如Java的单元测试框架是JUnit,.NET单元测试框架是NUnit等,OCUnit 是Objective-C单元测试框架之意。&2、现有工程中添加Target实现&该种方式添加的单元测试,属于逻辑测试(Logic Testing)。在一个现有工程中,选择菜单File&New&Target&,选择模板iOS&Other中的 &Cocoa Touch Unit Testing Bundle&。&
&点击Next按钮,在Product Name项目中输入LogicTest,创建完成后,在导航面板中多出了一个LogicTest组,包含LogicTest测试类。在右边的Target栏中多了一个LogicTest Target。&
&与上一种添加方式不同的是,在Scheme列表中会添加一个LogicTest,这也是我们需要注意的,这也是应用单元测试和逻辑单元测试的另一个 不同之处。运行它需要选择Scheme中LogicTest的iPhone 6.0 Simulator(或iPad 6.0 Simulator)运行,但是不能选择iOS Device,逻辑单元测试只能在模拟器中运行。然后再选择菜单Product&Test、工具栏中Test按钮(下拉Run按钮选择)和快捷键 command+U等几种方式运行。&无论那种方式添加,默认生成的测试类基本都是一样的,下面代码是默认生成的LogicTest测试类中的LogicTest.h和LogicTest.m文件。&
//&&LogicTest.h&&
//&&LogicTest&&
#import&&SenTestingKit/SenTestingKit.h&&&
@interface&LogicTest&:&SenTestCase&&
//&&LogicTest.m&&
//&&LogicTest&&
#import&&LogicTest.h&&&
@implementation&LogicTest&&
-&(void)setUp&&
[super&setUp];&&
//&Set-up&code&here.&&
-&(void)tearDown&&
//&Tear-down&code&here.&&
[super&tearDown];&&
-&(void)testExample&&
STFail(@&Unit&tests&are&not&implemented&yet&in&LogicTest&);&&
&作为OCUnit测试类需要引入&SenTestingKit/SenTestingKit.h&头文件,并继承 SenTestCase父类。testExample方法是一般的测试方法,方法名必须test开头,测试方法的个数没有限制,方法中STFail是 OCUnit框架定义的一个宏,是无条件断言失败,实际使用时候要修改这个方法中的代码。&在m文件中需要重新方法setUp和tearDown,我们自己编写的测试类一样,setUp方法是初始化方法,tearDown方法是释放资源的 方法,setUp和tearDown方法在每次调用测试方法之前和之后调用,因此在测试类运行的生命周期中这两个方法可能多次运行,它们的时序图
大家好,欢迎来到我的小站!
站长在关注

我要回帖

更多关于 xcode8 内存泄漏检测 的文章

 

随机推荐