发布于:2014-01-14 10:23阅读数:22668php
比较了好多关于instruments 仍是发现老外写的比较牛逼.因而果断翻译过来.有能力的的能够去看英文原版,鼓励你们看原版资料远离二手教程。原文 http://www.raywenderlich.com/23037/how-to-use-instruments-in-ios
“”算法
阅读器xcode
比较了好多关于instruments 仍是发现老外写的比较牛逼.因而果断翻译过来.有能力的的能够去看英文原版,鼓励你们看原版资料远离二手教程。这里是原文缓存
入门性能优化
为了节省你们的时间,提供一个演示的Demo给你们。代码传送门.网络
下载后解压而后用xcode打开。app
编译运行APP后 而后在搜索框内输入任意词汇,点击结果你会看到下面的结果框架
正如你所见的,这个app很简单.程序其实调用的是Flickr的API,经过app顶部的搜索框执行搜索后在下面的tableview显示你搜索的搜索词,搜索词后面的括号内有搜索结果的个数,点击此行进入一个略所图的结果列表页面 如上图. 点击其中一行 进入图像的大图模式,在这个页面你能够根据须要旋转图像。
到目前为止页面看起来差很少了,你也许会想应该能够直接提交appstore了吧.接下来这篇文章将会教你instruments工具来提升你app性能和稳定性.
“时间探测器”
天下武功,惟快不破。不少公司都信奉这个教条.巴不得把app压法周期压缩到最低,这就致使了开发中隐藏了不少问题,有点经验的工程师草率的优化下,更糟的状况那些没有经验的工程师甚至不会对app进行任何优化.
某种程度上来讲,你开发过程当中是能够忽略性能优化的. 十年前,移动设备的硬件资源是很是有限的.甚至连浮点数都是被禁止的.由于浮点数能致使代码变大计算的速度变慢.
科技发展如此迅速的今天,硬件很大程度上能够弥补软件的短板.如今的移动设备3D硬件处理的效率甚至媲美于PC机了,可是你不能总依赖于硬件和处理器速度来掩饰你APP作的多垃圾吧.(若是安卓系统跑在Iphone上还可以像iOS同样顺滑吗?实际上是一个道理的)
性能这个概念很抽线,因此咱们必须借助数据化图形化的输出方式.你可能花一周的时间去优化一个有趣的算法,可是这算法只占总执行时间的0.5%,无论你花多少精力去优化它,没人会注意到.相反一个for循环花费了90%的时间,你稍微修改下就能提升10%的效率,就是这个简单的修改能够获得你们很大的好感.由于.他们运行app时的第一感觉就是比以前快了不少.没人会care你修改的是一个多牛逼的算法,仍是一个简单的for循环.
这个说明了什么?
与其花费时间在优化小细节上不如多点时间找到你改优化的地方.
下面引出第一个工具 “时间事件查看器”(本身杜撰的名字英文—Time Profiler),———他能够测量时间的间隔,中断程序执行,跟踪每一个线程的堆栈.你能够想象下是xcode调试时按下暂停时的画面
好比,100个样本都在作1毫秒的间隔,而后在某个方法堆栈顶部有10个样本,你能够推算出大概的时间有10%个10毫秒花费在此方法中,这是一个近似值.
废话少说,时间是个检测到的。
从xcode的菜单选择Product-Profile,或者选择?
程序会启动Instruments,这时候你会看到一个选择窗口
这是instruments全部测试仪器的面板,选择 “timer profilter” 点击“profile”回启东模拟器和app,此时会要求你输入一次密码,以便instruments能有权限去截获监听此进程。
在工具窗口中,能够看到时间计数,并留下了一个小箭头移动到右侧的图形在屏幕的中央上方。这代表该应用程序正在运行。
如今开始运行app,搜索一些图片,这时候你发现查找一个结果太慢了,并且搜索结果列表页面滚动起来也是让人没法忍受的。
首先,确保工具栏中的视图选择有选择的全部三个选项,以下所示:
这将确保全部的面板都打开。如今,研究下面的截图和它下面的每一个部分的解释:
1. 录控按钮。中间的红色按钮将中止与启动它被点击时,应用程序目前正在分析。注意这其实是中止和启动应用程序,而不是暂停它。
2. 运行定时器和运行导航,定时器显示APP已经运行了多长时间,箭头之间是能够移动的。若是中止,而后使用录制按钮从新启动应用程序,这将开始一个新的运行。显示屏便会显示“run2 of 2”,你能够回到第一次运行的数据,首先你中止当前运行,而后按下左箭头回去。
3. 运行轨道。
4. 扩展面板,在时间探查仪器的状况下,它是用来跟踪显示堆栈。
5. 详细地面板。它显示了你正在使用的仪器的主要信息,这是使用频率最高的部门,能够从它这里看到cpu运行的时间
6. 选项面板,稍后介绍。
重头戏来了。
深究
执行图像搜索,并深究结果。我我的比较喜欢寻找“狗”,固然你也能够选择任意你想要的内容。好比猫啊美女啊什么的。
如今上下滚动记下列表,让时间探测器测量下数据,而后注意看下屏幕的变化和数值。这些数值反应了CPU周期。
可是你也许会发现下面的数值太多,看你的眼花缭乱。下面打开左边的调用树 而后按着以下的配置
如下介绍下配置选项:
Separate by Thread: 每一个线程应该分开考虑。只有这样你才能揪出那些大量占用CPU的"重"线程
Invert Call Tree: 从上倒下跟踪堆栈,这意味着你看到的表中的方法,将已从第0帧开始取样,这一般你是想要的,只有这样你才能看到CPU中话费时间最深的方法.也就是说FuncA{FunB{FunC}} 勾选此项后堆栈以C->B-A 把调用层级最深的C显示在最外面
Hide Missing Symbols: 若是dSYM没法找到你的app或者系统框架的话,那么表中看不到方法名只能看到十六进制的数值,若是勾线此项能够隐藏这些符号,便于简化数据
Hide System Libraries: 勾选此项你会显示你app的代码,这是很是有用的. 由于一般你只关心cpu花在本身代码上的时间不是系统上的
Show Obj-C Only: 只显示oc代码 ,若是你的程序是像OpenGl这样的程序,不要勾选侧向由于他有多是C++的
Flatten Recursion: 递归函数, 每一个堆栈跟踪一个条目
Top Functions: 一个函数花费的时间直接在该函数中的总和,以及在函数调用该函数所花费的时间的总时间。所以,若是函数A调用B,那么A的时间报告在A花费的时间加上B.花费的时间,这很是真有用,由于它可让你每次下到调用堆栈时挑最大的时间数字,归零在你最耗时的方法。
若是您已启用上述选项,虽然有些值可能会略有不一样,下面的结果的顺序应该是相似下表:
经过上面你能看到大部分时间都花在更新表格照片了.
双击此行,而后将会看到以下
那么这颇有趣,不是吗!几乎四分之三的时间花费在setPhoto:方法都花在创造照片的图像数据!
如今能够看到的是什么问题,NSData’s dataWithContentsOfURL 方法并不会当即返回,由于要从网上去数据,每次调用都须要长达几秒的时间返回,而此方法运行在主线程,可想而知会有什么结果了.
其实为了解决这个问题,类提供了一个ImageCache 的后台异步下载的方法.
如今,您能够切换到Xcode和手动找到该文件,但仪器有一个方便的“打开Xcode中”按钮,就在你的眼前。找到它的面板只是上面的代码并单击它:
想以下修改
- (void)setPhoto:(FlickrPhoto *)photo { _photo = photo; self.textLabel.text = photo.title;f // NSData *imageData = [NSData dataWithContentsOfURL:_photo.thumbnailUrl]; // self.imageView.image = [UIImage imageWithData:imageData]; [[ImageCache sharedInstance] downloadImageAtURL:_photo.thumbnailUrl completionHandler:^(UIImage *image) { self.imageView.image = image; [self setNeedsLayout]; }]; }
修改好厚,在仪器从新运行该应用程序Product—Profile(或cmd-记住,这些快捷键真的会为您节省一些时间)。
请注意,这个时候会再问一次你是否使用一块儿。这是由于你还有一个窗口中打开这个程序,及仪器假定您要使用相同的选项再次运行。
执行一些更多的搜索,并注意此时用户界面不是那么卡顿了!这些图像如今异步加载,并缓存在后台,因此一旦他们已经被下载一次,他们没必要再次下载。
看上去很不错!是时候发布了吗? 固然还不够
分配,分配,分配
接下来的仪器是分配工具。它能给出你全部建立和存储它们的内存的详细信息,它也显示你保留了每一个对象的计数。
关闭仪器,回到Xcode和选择Product->Profile。而后,从选择器分配并单击配置文件。以下图:
程序再次打开 而后你会看到
这个时候你会发现两个曲目。一个叫(分配)Allocations,以及一个被称为VM Tracker(虚拟机跟踪)。该分配轨道将详细在本教程中讨论;虚拟机跟踪也是很是有用的,但更复杂一点。
因此你的错误会追踪下?
有隐藏的项目,你可能不知道有东西在那儿。你可能已经据说了内存泄漏。但你可能不知道的是,其实有两种泄漏。
第一个是真正的内存泄漏,一个对象还没有被释放,可是再也不被引用的了。所以,存储器不能被从新使用。
第二类泄漏是比较麻烦一些。这就是所谓的“无界内存增加”。这发生在内存继续分配,并永远不会有机会被释放。
若是永远这样下去你的程序占用的内存会无限大,当超过必定内存的话 会被系统的看门狗给kill掉.
创建一个场景,你能够检测出无限的内存增加。首先,在应用程序使10个不一样的搜索(不要用已经存在的搜索)。确保搜索的一些结果!如今让程序等待几秒钟。
你应该已经注意到,在分配的轨道图不断上升。这是告诉你的,内存被分配了。它的这一特征,将引导你找到无限的内存增加。
你将要执行的是“heap shot analysis”。为此,按这个按钮叫“Mark Heap”。你会发现的详细面板左侧的按钮
按下它,你会看到一个红色的标志出如今轨道上,像这样:
heap shot分析的目的是执行一个动做屡次,看看若是内存是否无限增加。搜索一个内容,稍等几秒加载图像,而后返回主页。而后再标记堆。反复这样作不一样的搜索。
演戏几个搜索后,仪器会看起来像这样:
这时你应该会疑问。图中的蓝色是怎么回事了,你继续这样操做10次这样的搜索 蓝色还不断变高:
那确定是很差的。别急,有什么关于内存的警告?你知道这些,对不对?内存警告是告诉一个应用程序,内存警告是ios处理app最好的方式尤为是在内存愈来愈吃紧的时候,你须要清除一些内存。
内存一直增加其实也不必定是你的代码除了问题,也有多是UIKit 系统框架自己致使的.
经过选择HardwareSimulate内存警告在iOS模拟器的菜单栏模拟内存警告。你会发现,记忆体使用量出现小幅回落,但绝对不会回到它应该的。因此仍是有无限的内存增加发生的地方。
究其缘由,堆出手作钻进搜索的每次迭代后,你能够看到内存的分配每一个镜头之间。一块儿来看看在详细信息面板,你会看到一堆一堆的镜头。
在iOS模拟器的菜单栏中选择hardwaresimulate内存警告模拟内存警告。你会发现内存使用会出现小幅回落,但确定不会回到它应该在的地方。
每一次的搜索后作你能够看到内存已拍摄之间的分配。在详细信息面板看一看,你会看到一好多堆镜头。
稳准狠
第一个堆镜头做为参照,而后随便打开一个堆镜头,你会看到以下:
靠,这是一个很大的对象!从哪里开始看呢?
最好的方式是经过列表,你在你的应用程序直接使用的类。在这种状况下,HTTPHeaderDict,CGRegion,CGPath,CFNumber,等等都是能够忽略了。
可是,一个突出的是UIImage,这确定是在你程序使用的。点击上的UIImage左侧的箭头显示的完整列表。选择一个,在扩展详细信息面板:
图中灰色的是系统库,黑色部分是你应用的代码,要得到此跟踪更多的上下文,双击惟一的黑框ImageCache方法,这时候将掉转到以下方法
- (void)downloadImageAtURL:(NSURL*)url completionHandler:(ImageCacheDownloadCompletionHandler)completion { UIImage *cachedImage = [self imageForKey:[url absoluteString]]; if (cachedImage) { completion(cachedImage); } else { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; [self setImage:image forKey:[url absoluteString]]; dispatch_async(dispatch_get_main_queue(), ^{ completion(image); }); }); } }
工具是很是有用的,你如今要努力经过本身的代码思考发生了什么.看看经过上面的方法,你会看到它调用一个名为setImage方法:forKey:。这种方法在缓存以防它再次使用之后的应用程序的图像。啊!那么这确定听起来像它多是一个问题!
一块儿来看看该方法的实现:
- (void)setImage:(UIImage*)image forKey:(NSString*)key { [_cache setObject:image forKey:key]; }
从网络上下载一个图片添加字典中,你会注意到这些图片历来没有从字典清楚过。
,这就是内存为何会一直增加,由于应用程序并不会从缓存里删除东西.它只会一直增长他们。
要解决此问题,你须要的是ImageCache收到UIApplication内存吃紧的警告时.清理缓存。
为了使ImageCache能接收通知,修改init方法以下:
- (id)init { if ((self = [super init])) { _cache = [NSMutableDictionary new]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(memoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } return self; }
注册UIApplicationDidReceiveMemoryWarningNotification执行memoryWarning:方法。
- (void)memoryWarning:(NSNotification*)note { [_cache removeAllObjects]; }
memoryWarning删除缓存中的全部对象。这将确保没有持图像。
为了测试此修复程序,再次启动仪器(从Xcode中有cmd)和重复的步骤。不要忘了在模拟结束内存警告!
注意:请确保您从Xcode中退出,从新构建,而不是仅仅点击仪器仪表上的红色按钮,以确保您使用的是最新的代码。
这一次分析应该是这样的:
这个时候,内存受到内存警告后急剧降低。但仍是有一些内存总体增加,但远不及像之前那样。
究其缘由仍是有必定的增加确实是因为系统库,并无太多能够作的。看来,系统库不释放全部的内存,这多是由设计或多是一个错误。你能够在你的应用程序作的是释放尽量多的内存越好,你已经作到这一点!
干得好!还有一个问题,修补了, - 仍然有泄漏,你尚未解决的第一种类型的问题。
内存泄露
内存泄漏的仪器。这是用来找到第一类泄漏前面提到的 - 当一个对象再也不被引用时出现的那种。
检测泄漏是能够理解的一个很复杂的事情,但泄漏的工具记得,已分配的全部对象,并按期经过扫描每一个对象以肯定是否有任何不能从任何其余对象访问的。
关闭仪器,回到Xcode和选择Product->Profile
回到你的应用程序!执行搜索,获得结果。而后点选结果的预览行打开全屏浏览器。按下旋转按钮在左上角,而后再按一次。
回到仪器,等待片刻。若是你已经正确地完成上述步骤后,你会发现泄漏已经出现了!你的工具窗口将看起来像这样:
返回到模拟器,并按下旋转几回。而后返回到仪器和等会,获得以下结果:
哪来的泄漏从哪里来?扩展详细信息面板
在扩展的详细信息面板打开CGContext上名单。在列表中选择CGContext上的元素之一,这代表致使要建立的对象,以下面的堆栈跟踪:
再次,涉及到你的代码中的帧显示为黑色。因为只有一个,双击它,看看代码的方法。
有问题的方法是rotateTapped: ,这是被调用时被窃听旋转按钮的处理程序。这种方法旋转原始图像,并建立一个新的图像,以下:
- (void)rotateTapped:(id)sender { UIImage *currentImage = _imageView.image; CGImageRef currentCGImage = currentImage.CGImage; CGSize originalSize = currentImage.size; CGSize rotatedSize = CGSizeMake(originalSize.height, originalSize.width); CGContextRef context = CGBitmapContextCreate(NULL, rotatedSize.width, rotatedSize.height, CGImageGetBitsPerComponent(currentCGImage), CGImageGetBitsPerPixel(currentCGImage) * rotatedSize.width, CGImageGetColorSpace(currentCGImage), CGImageGetBitmapInfo(currentCGImage)); CGContextTranslateCTM(context, rotatedSize.width, 0.0f); CGContextRotateCTM(context, M_PI_2); CGContextDrawImage(context, (CGRect){.origin=CGPointZero, .size=originalSize}, currentCGImage); CGImageRef newCGImage = CGBitmapContextCreateImage(context); UIImage *newImage = [UIImage imageWithCGImage:newCGImage]; self.imageView.image = newImage; }
再次,仪器只能在这里给你一个提示,问题出在哪里,它不能告诉你确切位置的泄漏。这是惟一可以证实你在建立对象泄露的地方.你可能认为ARC并有不多是形成代码中内存泄漏…对不对?
回想一下,ARC只涉及Objective-C的对象。它无论理保留和的CoreFoundation对象而不是Objective-C的对象的释放。
啊,如今它开始变得明显的问题是什么?
-CGContextRef和CGImageRef对象永远不会被释放!为了解决这个问题,在rotateTapped方法的末尾添加如下两行代码:
CGImageRelease(newCGImage); CGContextRelease(context);
这两种调用都须要来维护这两个对象的保留计数。这个说明,你还须要了解引用计数 - 即便你在你的项目中使用的ARC!
从在Xcode中,使用cmd工具构建和运行应用程序。
在使用泄漏仪器仪器再看看应用程序,看看是否泄漏的被固定。若是你正确地遵循上述步骤,泄漏应消失!