1. 如何對iOS設備進行性能測試?
Xcode->Open Developer Tool -> Instruments ->Time Profiler
測試介紹:App耗時方法檢測
2. 開發項目時你是怎么檢查內存泄露?
1). 靜態分析 analyze。
2). instruments工具里面有個leak可以動態分析。
3. UITableView 如何進行優化?
參考資料:tableView懶加載圖片, tableview的優化,異步繪制cell,UITableViewCell 性能優化
4. 什么是懶加載?
答:懶加載就是只在用到的時候才去初始化。也可以理解成延時加載。
我覺得最好也最簡單的一個例子就是tableView中圖片的加載顯示了, 一個延時加載, 避免內存過高,一個異步加載,避免線程堵塞提高用戶體驗。
5. UITableView重用機制?
UITableView 通過重用單元格來達到節省內存的目的: 通過為每個單元格指定一個重用標識符,即指定了單元格的種類,當屏幕上的單元格滑出屏幕時,系統會把這個單元格添加到重用隊列中,等待被重用,當有新單元格從屏幕外滑入屏幕內時,從重用隊列中找看有沒有可以重用的單元格,如果有,就拿過來用,如果沒有就創建一個來使用。
6. 如何高性能的給 UIImageView 加個圓角?
不好的解決方案:使用下面的方式會強制Core Animation提前渲染屏幕的離屏繪制, 而離屏繪制就會給性能帶來負面影響,會有卡頓的現象出現。
self.view.layer.cornerRadius = 5.0f;
self.view.layer.masksToBounds = YES;
正確的解決方案:使用繪圖技術
- (UIImage *)circleImage {
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
// 獲得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一個圓
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪
CGContextClip(ctx);
// 將圖片畫上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 關閉上下文
UIGraphicsEndImageContext();
return image;
}
還有一種方案:使用了貝塞爾曲線"切割"個這個圖片, 給UIImageView 添加了的圓角,其實也是通過繪圖技術來實現的。
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
7. 談談 UITableView 的優化
1). 正確的復用cell。
2). 設計統一規格的Cell
3). 提前計算并緩存好高度(布局),因為heightForRowAtIndexPath:是調用最頻繁的方法;
4). 異步繪制,遇到復雜界面,遇到性能瓶頸時,可能就是突破口;
4). 滑動時按需加載,這個在大量圖片展示,網絡加載的時候很管用!
5). 減少子視圖的層級關系
6). 盡量使所有的視圖不透明化以及做切圓操作。
7). 不要動態的add 或者 remove 子控件。最好在初始化時就添加完,然后通過hidden來控制是否顯示。
8). 使用調試工具分析問題。
8. 如何實行cell的動態的行高
如果希望每條數據顯示自身的行高,必須設置兩個屬性,1.預估行高,2.自定義行高。
設置預估行高 tableView.estimatedRowHeight = 200。
設置定義行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。
如果要讓自定義行高有效,必須讓容器視圖有一個自下而上的約束。
9. 內存管理有哪幾種?
Objective-C的內存管理主要有三種方式ARC(自動內存計數)、手動內存計數、內存池。
1). 自動內存計數ARC:由Xcode自動在App編譯階段,在代碼中添加內存管理代碼。
2). 手動內存計數MRC:遵循內存誰申請、誰釋放;誰添加,誰釋放的原則。
3). 內存釋放池Release Pool:把需要釋放的內存統一放在一個池子中,當池子被抽干后(drain),池子中所有的內存空間也被自動釋放掉。內存池的釋放操作分為自動和手動。自動釋放受runloop機制影響。
有一個很經典的面試題,考察自動釋放池的如下:
for (int i = 0; i < MAXFLOAT; i++) {
NSString *string = @"stdy";
string = [string lowercaseString];
string = [string stringByAppendingString:@"123"];
NSLog(@"--%@", string);
}
? 上述的這種寫法,會使內存慢慢增加,如何解決呢,面試官想要的答案就是用自動釋放池,你也可以改成其他的,但不是面試官要的,你懂的,修改如下:
for (int i = 0; i < MAXFLOAT; i++) {
@autoreleasepool {
NSString *string = @"stdy";
string = [string lowercaseString];
string = [string stringByAppendingString:@"123"];
NSLog(@"--%@", string);
}
}
9.1 什么時間會創建自動釋放池?
? 從程序啟動到加載完成是一個完整的運行循環,然后會停下來,等待用戶交互,用戶的每一次交互都會啟動一次運行循環,來處理用戶所有的點擊事件、觸摸事件,運行循環檢測到事件并啟動后,就會創建自動釋放池。
? 子線程的 runloop 默認是不工作,無法主動創建,必須手動創建。自定義的 NSOperation 和 NSThread 需要手動創建自動釋放池。比如:自定義的 NSOperation 類中的 main 方法里就必須添加自動釋放池。否則出了作用域后,自動釋放對象會因為沒有自動釋放池去處理它,而造成內存泄露。
? 但對于 blockOperation
和invocationOperation
這種默認的Operation ,系統已經幫我們封裝好了,不需要手動創建自動釋放池。
@autoreleasepool 當自動釋放池被銷毀或者耗盡時,會向自動釋放池中的所有對象發送 release 消息,釋放自動釋放池中的所有對象。
如果在一個vc的viewDidLoad中創建一個 Autorelease對象,那么該對象會在 viewDidAppear 方法執行前就被銷毀了。
10. 什么會造成離屏渲染?
GPU屏幕渲染有兩種方式:
(1)On-Screen Rendering (當前屏幕渲染)
指的是GPU的渲染操作是在當前用于顯示的屏幕緩沖區進行。
(2)Off-Screen Rendering (離屏渲染)
指的是在GPU在當前屏幕緩沖區以外開辟一個緩沖區進行渲染操作。
下面的情況或操作會引發離屏渲染:
- 為圖層設置遮罩(layer.mask)
- 將圖層的layer.masksToBounds / view.clipsToBounds屬性設置為true
- 將圖層layer.allowsGroupOpacity屬性設置為YES和layer.opacity小于1.0
- 為圖層設置陰影(layer.shadow *)。
- 為圖層設置layer.shouldRasterize=true
- 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層
- 文本(任何種類,包括UILabel,CATextLayer,Core Text等)。
- 使用CGContext在drawRect :方法中繪制大部分情況下會導致離屏渲染,甚至僅僅是一個空的實現。
優化方案
1). 圓角優化
方案1 :使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角
方案2 :使用CAShapeLayer和UIBezierPath設置圓角
2). shadow優化
對于shadow,如果圖層是個簡單的幾何圖形或者圓角圖形,我們可以通過設置shadowPath來優化性能,能大幅提高性能
其他優化
? 當我們需要圓角效果時,可以使用一張中間透明圖片蒙上去使用ShadowPath指定layer陰影效果路徑;
? 使用異步進行layer渲染(Facebook開源的異步繪制框架AsyncDisplayKit)設置layer的opaque值為YES,減少復雜圖層合成盡量使用不包含透明(alpha)通道的圖片資源;
? 盡量設置layer的大小值為整形值;
? 直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案很多情況下用戶上傳圖片進行顯示,可以讓服務端處理圓角使用代碼手動生成圓角Image設置到要顯示的View上;
? 利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片
11. 說一下NSTimer在使用的時候內存泄漏的分析?
NSTimer必須與RunLoop搭配使用,因為其定時任務的觸發基于RunLoop。
NSTimer使用常見的Target-Action模式。由于RunLoop會強引用timer,timer會強引用Target,容易造成循環引用、內存泄露等問題。
12. loop 強引用timer, timer 強引用 target,如果不能釋放,會造成內存泄漏,有一個面試官問如果在target中傳入weak的self,那么可以解決循環引用問題嗎?
答案:否
? Target強引用or弱引用Timer并不是問題的關鍵,問題的關鍵是:一定要在Timer使用完畢調用invalidate使之失效(手動調用or系統自動調用),Timer從RunLoop中被移除并清除強引用,這個操作可打破引用1、2,而引用3是強弱引用已經不重要了。
? NSTimer一共有三種初始化方案:init開頭的普通創建方法、timer開頭的類工廠方法、scheduled開頭的類工廠方法。前兩者需要手動加入RunLoop中,后者會自動加入當前RunLoop的DefaultMode中。
13. 對于NSTimer,面試官還會問,它是否是時間準確呢?
? 大家可能都知道是時間不準確的,因為受RunLoop的影響,那么GCD中也有延時,如果用GCD來做延時,那時間準確嗎?
GCD的time是準確的,GCD 的線程管理是通過系統來直接管理的。
GCD Timer 是通過 dispatch port 給 RunLoop 發送消息,來使 RunLoop 執行相應的 block。
如果所在線程沒有 RunLoop,那么 GCD 會臨時創建一個線程去執行 block,執行完之后再銷毀掉,因此 GCD 的 Timer 是不依賴 RunLoop 的。
14. 大次數循環內存暴漲問題
for (int i = 0; i < 100000; i++) {
NSString *string = @“Abc”;
string = [string lowercaseString];
string = [string stringByAppendingString:@“xyz”];
NSLog(@"%@", string);
}
改:
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = @“Abc”;
string = [string lowercaseString];
string = [string stringByAppendingString:@“xyz”];
NSLog(@"%@", string);
}
}
15. 內存泄漏可能會出現的幾種原因,聊聊你的看法?
15.1 非OC對象如何處理?
15.2 地圖類內存若泄漏,如何處理?
15.3 若常用框架出現內存泄漏如何處理?
iOS 出現內存泄漏的幾種原因
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``
``