上图是几种时间复杂度的关系,性能优化必定程度上是为了下降程序执行效率减低时间复杂度。 以下是几种时间复杂度的实例:node
return array[index] == value;
复制代码
for (int i = 0, i < n, i++) {
if (array[i] == value)
return YES;
}
复制代码
/// 找数组中重复的值
for (int i = 0, i < n, i++) {
for (int j = 0, j < n, j++) {
if (i != j && array[i] == array[j]) {
return YES;
}
}
}
复制代码
NSArray / NSMutableArray
containsObject; indexOfObject; removeObject
均会遍历元素查看是否匹配,复杂度等于或小于 O(n)objectAtIndex;firstObject;lastObject; addObject; removeLastObject
这些只针对栈顶,栈底的操做时间复杂度都是 O(1)indexOfObject:inSortedRange:options:usingComparator:
使用的是二分查找,时间复杂度是O(log n)NSSet / NSMutableSet / NSCountedSet
集合类型是无序而且没有重复元素的。这样可使用hash table 进行快速的操做。好比,addObject; removeObject; containsObject
都是按照 O(1) 来的。须要注意的是将数组转成Set 时,会将重复元素合并为一个,而且失去排序。ios
NSDictionary / NSMutableDictionary
和 Set 同样均可以使用 hash table ,多了键值对应。添加和删除元素都是 O(1)。objective-c
containsObject
方法在数组和Set里的不一样的实现containsObject
在数组中的实现///GUNSTEP NSArray indexOfObject: 方法的实现
- (BOOL)containsObject:(id)anObject {
return [self indexOfObject:anObject] != NSNotFound;
}
- (NSUInteger) indexOfObject: (id)anObject
{
unsigned c = [self count];
if (c > 0 && anObject != nil)
{
unsigned i;
IMP get = [self methodForSelector: oaiSel];
BOOL (*eq)(id, SEL, id)
= (BOOL (*)(id, SEL, id))[anObject methodForSelector: eqSel];
for (i = 0; i < c; i++)
if ((*eq)(anObject, eqSel, (*get)(self, oaiSel, i)) == YES)
return i;
}
return NSNotFound;
}
复制代码
containsObject
在 Set 里的实现:- (BOOL) containsObject: (id)anObject
{
return (([self member: anObject]) ? YES : NO);
}
//在 GSSet,m 里有对 member 的实现
- (id) member: (id)anObject
{
if (anObject != nil)
{
GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)anObject);
if (node != 0)
{
return node->key.obj;
}
}
return nil;
}
复制代码
GCD
进行性能优化能够经过GCD提供的方法将一些耗时操做放到非主线程进行,使得App 可以运行的更加流畅,响应更快,可是使用GCD 时须要注意避免可能引发的线程爆炸和死锁的状况。在非主线程处理任务也不是万能的,若是一个处理须要消耗大量内存或者大量CPU操做,GCD也不合适,须要将大任务拆分红不一样的阶段任务分时间进行处理。数据库
NSOperationQueue
的并发数 - NSOperationQueue.maxConcurrentOperationCount
举个会形成线程爆炸和死锁的例子:swift
for (int i = 0, i < 999; i++) {
dispatch_async(q,^{...});
}
dispatch_barrier_sync(q,^{...});
复制代码
如何避免上述的的线程爆炸和死锁呢? 首先使用 dispatch_apply
数组
dispatch_apply(999,q,^(size_t i){...});
复制代码
或者使用 dispatch_semaphore
缓存
#define CONCURRENT_TASKS 4
dispatch_queue_t q = dispatch_queue_create("com.qiuxuewei.gcd", nil);
dispatch_semaphore_t sema = dispatch_semaphore_create(CONCURRENT_TASKS);
for (int i = 0; i < 999; i++) {
dispatch_async(q, ^{
dispatch_semaphore_signal(sema);
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
复制代码
I/O 操做是性能消耗大户,任何的I/O操做都会使低功耗状态被打破。因此减小 I/O 操做次数是性能优化关键。以下是优化的一些方法:安全
NSCache
为什么使用 NSCache
而不适应 NSMutableDictionary
呢?相交字典 NSCache
有如下优势:性能优化
NSCache
是线程安全的- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
缓存对象在即将被清理时回调。evictsObjectWithDiscardedContent
能够控制是否可被清理。SDWebImage
在设置图片时就使用 NSCache
进行了性能优化:服务器
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
// 检查 NSCache 里是否有
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// 从磁盘里读
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
复制代码
利用 NSCache
自动释放内存的特色将图片放到 NSCache
里,这样在内存警告时会自动清理掉不经常使用的图片,在读取 Cache 里内容时,若是没有被清理直接返回图片数据,清理了会执行 I/O 从磁盘中读取图片,经过这种方式减小磁盘操做,空间也会更加有效的控制释放。
通知,Voip, 定位,蓝牙 等都会使设备从 Standby 状态唤起。唤起这个过程会有比较大的消耗。应该避免频繁发生。 以 定位 API 举例:
[locationManager startUpdatingLocation]
这个方法会使设备一直处于活跃状态。
[locationManager allowDeferredLocationUpdatesUntilTraveled:<#(CLLocationDistance)#> timeout:<#(NSTimeInterval)#>]
高效节能的定位方式,数据会缓存在位置硬件上。适合跑步应用。
[locationManager startMonitoringSignificantLocationChanges]
会更节能,对于那些只有在位置有很大变化的时候才须要回调的应用须要采用这种方式,好比天气应用。
[locationManager startMonitoringForRegion:<#(nonnull CLRegion *)#>]
也是一种节能的定位方式,好比在博物馆内按照不一样区域监测展现不一样信息之类的应用。
// start monitoring location
[locationManager startUpdatingLocation]
// Stop monitoring when no longer needed
[locationManager stopUpdatingLocation]
复制代码
不要轻易使用 startUpdatingLocation() 除非万不得已,尽快的使用 stopUpdatingLocation() 来结束定位还用户一个节能设备。
坚持几个编码原则:
在 UICollectionView
和 UITableView
会使用到 代码复用的机制,在所展现的item数量超过屏幕所容纳的范围时,只建立少许的条目(一般是屏幕最大容纳量 + 1),经过复用来展现全部数据。这种机制不会为每一条数据都建立 Cell .加强效率和交互流畅性。 在iOS6之后,不只能够复用cell,也能够复用每一个section 的 header 和 footer。 在复用UITableView 会用到的 API:
// 复用 Cell:
- [UITableView dequeueReusableCellWithIdentifier:];
- [UITableView registerNib:forCellReuseIdentifier:];
- [UITableView registerClass:forCellReuseIdentifier:];
- [UITableView dequeueReusableCellWithIdentifier:forIndexPath:];
// 复用 Section 的 Header/Footer:
- [UITableView registerNib:forHeaderFooterViewReuseIdentifier:];
- [UITableView registerClass:forHeaderFooterViewReuseIdentifier:];
- [UITableView dequeueReusableHeaderFooterViewWithIdentifier:];
复制代码
在使用代码复用须要注意在设置Cell 属性是,条件判断须要覆盖全部可能,避免由于复用致使数据错误的问题。例如在 cellForRowAtIndexPath:
方法内部:
if (indexPath %2 == 0) {
cell.backgroundColor = [UIColor redColor];
}else{
cell.backgroundColor = [UIColor clearColor];
}
复制代码
UIView
又一个 opaque
属性, 在不须要透明效果的时候,应该尽可能设置它为 YES, 能够提升绘图效率。 在静态视图做用可能不明显,但在 UITableVeiw
或 UICollectionView
这种滚动 的 Scroll View 或是一个复杂动画中,透明效果对程序性能有较大的影响!
当加载一个 Xib 时,它全部的内容都会被加载,如歌这个 Xib 中有的View 你不会立刻用到,加载就是浪费资源。而加载 StoryBoard 时,并不会把全部的ViewController 都加载,只会按需加载。
UIKit
会把它全部的工做放在主线程执行,好比:绘制界面,管理手势,响应输入等。当把全部代码逻辑都放在主线程时,有可能由于耗时太长而卡住主线程形成程序没法响应,流畅性差等问题。因此一些 I/O 操做,网络数据解析都须要异步在非主线程处理。
当从 App bundle 中加载图片到 UIImageView 时,最好确保图片的尺寸和 UIImageView 相对应。不然会使UIImageView 对图片进行拉伸,这样会影响性能。若是图片时从网络加载,须要手动进行 scale。在UIImageView 中使用resize 后的图片
在使用 NSArray / NSDictionary / NSSet
时,了解他们的特色便于在合适的时机选择他们。
在网络请求的数据量较大时,能够将数据进行压缩再进行传输。能够下降延迟,缩短网络交互时间。
展示视图的两种形式一种是懒加载,当用到的时候去建立并展示给用户,另一种提早分配内存建立出视图,不用的时候将其隐藏,等用到的时候将其透明度变为1,两种方案各有利弊。懒加载更合理的使用内存,视图隐藏让视图的展示更迅速。在选择时须要权衡二者利弊作出最优选择。
开发须要秉承一个原则,对于一些更新频率低,访问频率高的内容进行缓存,例如:
处理 Memory Warning 的几种方式:
- [AppDelegate applicationDidReceiveMemoryWarning:]
代理方法。UIViewController
中重载 didReceiveMemoryWarning
方法。UIApplicationDidReceiveMemoryWarningNotification
通知。当经过这些方式监听到内存警告时,你须要立刻释放掉不须要的内存从而避免程序被系统杀掉。
好比,在一个 UIViewController 中,你能够清除那些当前不显示的 View,同时能够清除这些 View 对应的内存中的数据,而有图片缓存机制的话也能够在这时候释放掉不显示在屏幕上的图片资源。
可是须要注意的是,你这时清除的数据,必须是能够在从新获取到的,不然可能由于必要数据为空,形成程序出错。在开发的时候,可使用 iOS Simulator 的 Simulate memory warning 的功能来测试你处理内存警告的代码。
高开销对象,顾名思义就是初始化很耗性能的对象。好比:NSDateFormatter
, NSCalendar
.为了不频繁建立,咱们可使用一个全局单例强引用着这个对象,保证整个App 的生命周期只被初始化一次。
// no property is required anymore. The following code goes inside the implementation (.m)
- (NSDateFormatter *)dateFormatter {
static NSDateFormatter *dateFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd a HH:mm:ss EEEE"];
});
return dateFormatter;
}
复制代码
设置 NSDateFormatter 的 date format 跟建立一个新的 NSDateFormatter 对象同样慢,所以当你的程序中要用到多种格式的 date format,而每种又会用到屡次的时候,你能够尝试为每种 date format 建立一个可复用的 NSDateFormatter 对象来提供程序的性能。
一般用到的有两种: JSON 和 XML。 JSON 优势:
缺点:
而XML的优缺点刚好相反。解析数据不须要所有读取完才解析,能够变加载边解析,这样在处理大数据集时能够有效提升性能。 选择哪一种格式取决于应用场景。
为一个View 设置背景图,咱们想到的方案有两种
[UIColor colorWithPatternImage:<#(nonnull UIImage *)#>]
将一张图转化为 UIColor, 直接为 View 设置 backgroundColor。两种方案各有优缺点:若使用一个全尺寸图片做为背景图使用 UIImageView 会节省内存。 当你计划采用一个小块的模板样式图片,就像贴瓷砖那样来重复填充整个背景时,你应该用 [UIColor colorWithPatternImage:<#(nonnull UIImage *)#>]
这个方法,由于这时它可以绘制的更快,而且不会用到太多的内存。
离屏渲染:GPU在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做。 离屏渲染须要屡次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束之后,将离屏缓冲区的渲染结果显示到屏幕上又须要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动做。
设置以下属性均会形成离屏渲染:
例如给一个View设置阴影,一般咱们会使用这种方式:
imageView.layer.shadowOffset = CGSizeMake(5.0f, 5.0f);
imageView.layer.shadowRadius = 5.0f;
imageView.layer.shadowOpacity = 0.6;
复制代码
这种方式会触发离屏渲染,形成没必要要的内存开销,咱们彻底可使用以下方式代替:
imageView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:CGRectMake(imageView.bounds.origin.x+5, imageView.bounds.origin.y+5, imageView.bounds.size.width, imageView.bounds.size.height)] CGPath];
imageView.layer.shadowOpacity = 0.6;
复制代码
不会形成离屏渲染。
CALayer 有一个属性是 shouldRasterize 经过设置这个属性为 YES 能够将图层绘制到一个屏幕外的图像,而后这个图像将会被缓存起来并绘制到实际图层的 contents 和子图层,若是很不少的子图层或者有复杂的效果应用,这样作就会比重绘全部事务的全部帧来更加高效。可是光栅化原始图像须要时间,并且会消耗额外的内存。
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [[UIScreen mainScreen] scale];
复制代码
使用光栅化的一个前提是视图不会频繁变化,若一个频繁变化的视图,例如 排版多变,高度不一样的 Cell, 光栅化的意义就不大了,反而形成必要的内存损耗。
UITableView
iOS 中数据存储方案有如下几种:
在启动时的一些网络配置,数据库配置,数据解析的工做放在异步线程进行。
当须要在代码中建立许多临时对象时,你会发现内存消耗激增直到这些对象被释放,一个问题是这些内存只会到 UIKit 销毁了它对应的 Autorelease Pool 后才会被释放,这就意味着这些内存没必要要地会空占一些时间。这时候就是咱们显式的使用 Autorelease Pool 的时候了,一个示例以下:
//一个很大数组
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding error:&error];
/* Process the string, creating and autoreleasing more objects. */
}
}
复制代码
添加 Autorelease Pool 会在每一次循环中释放掉临时对象,提升性能。
imageNamed
和 imageWithContentsOfFile
imageNamed
会对图片进行缓存,适合屡次使用某张图片imageWithContentsOfFile
从bundle中加载图片文件,不会进行缓存,适用于加载一张较大的而且只使用一次的图片,例如引导图等今年的 WWDC 2018 Apple 向咱们推荐了一种性能比较高的大图加载方案:
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
let sourceOpt = [kCGImageSourceShouldCache : false] as CFDictionary
// 其余场景能够用createwithdata (data并未decode,所占内存没那么大),
let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOpt)!
let maxDimension = max(pointSize.width, pointSize.height) * scale
let downsampleOpt = [kCGImageSourceCreateThumbnailFromImageAlways : true,
kCGImageSourceShouldCacheImmediately : true ,
kCGImageSourceCreateThumbnailWithTransform : true,
kCGImageSourceThumbnailMaxPixelSize : maxDimension] as CFDictionary
let downsampleImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOpt)!
return UIImage(cgImage: downsampleImage)
}
做者:知识小集
连接:https://juejin.im/post/5b396fece51d4558a3055131
来源:掘金
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。
复制代码
详细关于二者的分析可参照笔者的另一篇博客:iOS-UIImage imageWithContentsOfFile 和 imageName 对比
GCD 很轻易的能够开辟一个异步线程(不会100%开辟新线程),若不加以控制,会致使开辟的子线程愈来愈多浪费内存。而且在多线程状况下由于网络时序会形成数据处理错乱,因此能够:
预处理:初次展现须要消耗大量内存的数据需提早在后台线程处理完毕,须要时将处理好的数据进行展示 延时加载:提早加载下级界面的数据内容。举个栗子:相似抖音视频滑动,在播放当前视频的时候就提早将下个视频的数据加载好,等滑到下个视频时直接进行展现!
若视图无需和用户交互,相似绘制线条,单纯展现一张图片,能够将图片对象赋值给 layer 的 content 属性,以提升性能。 可是不能滥用,不然会形成代码难以维护的恶果。
以上。
参考: