上圖是幾種時間複雜度的關係,性能優化必定程度上是爲了下降程序執行效率減低時間複雜度。 以下是幾種時間複雜度的實例: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 屬性,以提升性能。 可是不能濫用,不然會形成代碼難以維護的惡果。
以上。
參考: