APP崩潰mysql
啓動秒退git
在新 iOS 上正常的應用,到了老版本 iOS 上秒退最多見緣由是系統動態連接庫或Framework沒法找到。這種狀況一般是因爲 App 引用了一個新版操做系統裏的動態庫(或者某動態庫的新版本)或只有新 iOS 支持的 Framework,而又沒有對老系統進行測試,因而當 App 運行在老系統上時便因爲找不到而秒退。解決辦法是等開發人員發現這個問題後升級程序,或由用戶自行升級其操做系統。程序員
還有一種常見的秒退是程序在升級時,修改了本地存儲的數據結構,可是對用戶既存的舊數據沒有作好升級,結果致使初始化時由於沒法正確讀取用戶數據而秒退。這類問題一般只需刪除程序後從新安裝一遍就能解決。但缺點是用戶的既存數據會丟失——就算有備份可能也無濟於事,由於備份下來的舊數據仍是沒法被正確升級。github
還有一類秒退或是用到 App 裏某個功能後必退的緣由,是開發時用到了只有新版操做系統才支持的某個方法,而又沒有對該方法是否存在於老系統中作出判斷。例如程序啓動時用到了 Game Center,而沒有判斷用戶的機器是否支持 Game Center,因而就秒退了。sql
訪問的數據爲空或者類型不對數據庫
這類狀況是比較常見的,後端傳回了空數據,客戶端沒有作對應的判斷繼續執行下去了,這樣就產生了crash。或者本身本地的某個數據爲空數據而去使用了。還有就是訪問的數據類型不是指望的數據類型而產生崩潰。好比,我指望服務端返回string類型,可是後臺給我返回的是NSNumber類型,那麼我操做時候用到的是string的方法。結果由於找不到對應的方法而崩潰。解決辦法:一、服務端都加入默認值,不返回空內容或無key。或者是在客戶端進行非空判斷。二、對容易出錯的地方提早進行類型判斷。編程
點擊事件方法處理不當後端
這類狀況比較常見,好比個人點擊事件須要對傳入的參數作處理,可是點擊時,我傳入的對象類型或者傳入爲空,等到參數進行處理的時候,因爲方法不當,產生crash。數組
數組越界緩存
當客戶端嘗試對數組中的數據進行操做的時候,數組爲空或者所作的操做index 超過數組自己範圍,就會引發崩潰。
下拉刷新時崩潰
使用下拉刷新時,若是下拉的距離長了就會崩潰。緣由是,在下拉刷新請求數據以前就將本地數組清空了。分析,下拉刷新的邏輯:一、下拉 二、下拉達到臨界值時觸發網絡請求 三、等待數據加載到本地之後才更新datasource 四、tableview reloadData。 若是先清空數組再下拉請求,後果就是往下拉的距離超過一個 cell 的高度時,table view 的幾個委託方法就會被調用,因爲 data source 已經被清空,形成錯誤的內存訪問(包括數組越界,訪問已銷燬的對象)致使 crash。
操做了不應操做的對象、野指針
iOS中有空指針和野指針兩種概念。
空指針是沒有存儲任何內存地址的指針。如Student s1 = NULL;和Student s2 = nil;
而野指針是指指向一個已刪除的對象("垃圾"內存既不可用內存)或未申請訪問受限內存區域的指針。野指針是比較危險的。由於野指針指向的對象已經被釋放了,不能用了,你再給被釋放的對象發送消息就是違法的,因此會崩潰。
野指針訪問已經釋放的對象crash其實不是必現的,由於dealloc執行後只是告訴系統,這片內存我不用了,而系統並無就讓這片內存不能訪問。
因此野指針的奔潰是比較隨機的,你在測試的時候可能沒發生crash,可是用戶在使用的時候就可能發生crash了。
注意:arc環境比非arc環境更少出現野指針。
一、對象釋放後內存沒被改動過,原來的內存保存無缺,可能不Crash或者出現邏輯錯誤(隨機Crash)。
二、對象釋放後內存沒被改動過,可是它本身析構的時候已經刪掉某些必要的東西,可能不Crash、Crash在訪問依賴的對象好比類成員上、出現邏輯錯誤(隨機Crash)。
三、對象釋放後內存被改動過,寫上了不可訪問的數據,直接就出錯了極可能Crash在objc_msgSend上面(必現Crash,常見)。
四、對象釋放後內存被改動過,寫上了能夠訪問的數據,可能不Crash、出現邏輯錯誤、間接訪問到不可訪問的數據(隨機Crash)。
對象釋放後內存被改動過,寫上了能夠訪問的數據,可是再次訪問的時候執行的代碼把別的數據寫壞了,遇到這種Crash只能哭了(隨機Crash,難度大,機率低)!!
五、對象釋放後再次release(幾乎是必現Crash,但也有例外,很常見)
內存處理不當
用instruments排查內存泄露問題
主線程UI長時間卡死,被系統殺掉
主線程被卡住是很是常見的場景,具體表現就是程序不響應任何的UI交互。這時按下調試的暫停按鈕,查看堆棧,就能夠看到是究竟是死鎖、死循環等,致使UI線程被卡住。
多線程之間切換訪問引發的crash
多線程引發的崩潰大部分是由於使用數據庫的時候多線程同時讀寫數據庫而形成了crash。
內存緊張
這個如今不多遇到了。
ARC內存泄漏
block系列
在 ARC 下,當 block 獲取到外部變量時,因爲編譯器沒法預測獲取到的變量什麼時候會被忽然釋放,爲了保證程序可以正確運行,讓 block 持有獲取到的變量,向系統顯明:我要用它,大家千萬別把它回收了!然而,也正因 block 持有了變量,容易致使變量和 block 的循環引用,形成內存泄露!
對於 block 中的循環引用一般有兩種解決方法:
一、將對象置爲 nil ,消除引用,打破循環引用;
(這種作法有個很明顯的缺點,即開發者必須保證 _networkFetecher = nil; 運行過。若不如此,就沒法打破循環引用。
但這種作法的使用場景也很明顯,因爲 block 的內存必須等待持有它的對象被置爲 nil 後纔會釋放。因此若是開發者但願本身控制 block 對象的生命週期時,就能夠使用這種方法。)
二、將強引用轉換成弱引用,打破循環引用;
(__weak __typeof(self) weakSelf = self;若是想防止 weakSelf 被釋放,能夠再次強引用 __typeof(&*weakSelf) strongSelf = weakSelf;代碼 __typeof(&*weakSelf) strongSelf 括號內爲何要加 &* 呢?主要是爲了兼容早期的 LLVM
block 的內存泄露問題包括自定義的 block,系統框架的 block 如 GCD 等,都須要注意循環引用的問題。
有個值得一提的細節是,在種類衆多的 block 當中,方法名帶有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API ,如
1
2
|
- enumerateObjectsUsingBlock:
- sortUsingComparator:
|
這一類 API 一樣會有循環引用的隱患,但緣由並不是編譯器作了保留,而是 API 自己會對傳入的 block 作一個複製的操做。
delegate系列
1
|
@property (nonatomic, weak) id delegate;
|
說白了就是循環使用的問題,假如咱們是寫的strong,那麼 兩個類之間調用代理就是這樣的啦
1
2
3
4
5
6
7
8
9
10
|
BViewController *bViewController = [[BViewController alloc] init];
bViewController.delegate = self;
//假設 self 是AViewController
[self.navigationController pushViewController:bViewController animated:YES];
/**
假如是 strong 的狀況
bViewController.delegate ===> AViewController (也就是 A 的引用計數 + 1)
AViewController 自己又是引用了 ===> delegate 引用計數 + 1
致使: AViewController Delegate ,也就循環引用啦
*/
|
Delegate建立並強引用了 AViewController;(strong ==> A 強引用、weak ==> 引用計數不變)
因此用 strong的狀況下,至關於 Delegate 和 A 兩個互相引用啦,A 永遠會有一個引用計數 1 不會被釋放,因此形成了永遠不能被內存釋放,所以weak是必須的。
performSelector 系列
performSelector 顧名思義即在運行時執行一個 selector,最簡單的方法以下
1
|
- (id)performSelector:(SEL)selector;
|
這種調用 selector 的方法和直接調用 selector 基本等效,執行效果相同
1
2
|
[object methodName];
[object performSelector:@selector(methodName)];
|
但 performSelector 相比直接調用更加靈活
1
2
3
4
5
6
7
8
9
|
SEL selector;
if
(
/* some condition */
) {
selector = @selector(newObject);
}
else
if
(
/* some other condition */
) {
selector = @selector(copy);
}
else
{
selector = @selector(someProperty);
}
id ret = [object performSelector:selector];
|
這段代碼就至關於在動態之上再動態綁定。在 ARC 下編譯這段代碼,編譯器會發出警告
1
|
warning: performSelector may cause a leak because its selector is unknow [-Warc-performSelector-leak]
|
正是因爲動態,編譯器不知道即將調用的 selector 是什麼,不瞭解方法簽名和返回值,甚至是否有返回值都不懂,因此編譯器沒法用 ARC 的內存管理規則來判斷返回值是否應該釋放。所以,ARC 採用了比較謹慎的作法,不添加釋放操做,即在方法返回對象時就可能將其持有,從而可能致使內存泄露。
以本段代碼爲例,前兩種狀況(newObject, copy)都須要再次釋放,而第三種狀況不須要。這種泄露隱藏得如此之深,以致於使用 static analyzer 都很難檢測到。若是把代碼的最後一行改爲
[object performSelector:selector];
不建立一個返回值變量測試分析,簡直不可思議這裏竟然會出現內存問題。因此若是你使用的 selector 有返回值,必定要處理掉。
還有一種狀況就是performSelector的延時調用[self performSelector:@selector(method1:) withObject:self.myView afterDelay:5];,performSelector關於內存管理的執行原理是這樣的,當執行[self performSelector:@selector(method1:) withObject:self.myView afterDelay:5];的時候,系統將myView的引用計數加1,執行完這個方法以後將myView的引用計數減1,而在延遲調用的過程當中極可能就會出現,這個方法被調用了,可是沒有執行,此時myView的引用計數並無減小到0,也就致使了切換場景的dealloc方法沒有被調用,這也就引發了內存泄漏。
NSTimer
NSTimer會形成循環引用,timer會強引用target即self,在加入runloop的操做中,又引用了timer,因此在timer被invalidate以前,self也就不會被釋放。
因此咱們要注意,不只僅是把timer看成實例變量的時候會形成循環引用,只要申請了timer,加入了runloop,而且target是self,雖然不是循環引用,可是self卻沒有釋放的時機。以下方式申請的定時器,self已經沒法釋放了。
1
2
|
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(commentAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
解決這種問題有幾個實現方式,你們能夠根據具體場景去選擇:
增長startTimer和stopTimer方法,在合適的時機去調用,好比能夠在viewDidDisappear時stopTimer,或者由這個類的調用者去設置。
每次任務結束時使用dispatch_after方法作延時操做。注意使用weakself,不然也會強引用self。
1
2
3
4
5
6
7
|
- (void)startAnimation
{
WS(weakSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf commentAnimation];
});
}
|
使用GCD的定時器,一樣注意使用weakself。
1
2
3
4
5
6
7
|
WS(weakSelf);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
[weakSelf commentAnimation];
});
dispatch_resume(timer);
|
內存泄漏的檢測方式
1.靜態分析
使用XCode分析功能,Product->Analyze
使用靜態檢測能夠檢查出一些明顯的沒有釋放的內存,包括NSObject和CF開頭的內存泄漏,最多見問題有2種,這些問題都不復雜,須要的是細心:
MRC的文件,常常遺漏release或者autorelease。
C方式申請的內存,忘記釋放了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//1
static inline NSString* iphone_device_info(){
size_t size;
sysctlbyname(
"hw.machine"
, NULL, &size, NULL, 0);
char *machine = (char*)malloc(size);
sysctlbyname(
"hw.machine"
, machine, &size, NULL, 0);
NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
...
}
//2
if
(alpha != 1) {
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGColorRef color = CGColorCreate(colorSpaceRef, (CGFloat[]){255, 255, 255, 0.3});
[btn.layer setBorderColor:color];
}
|
不過在修改時應該注意:
這些場景是否真的泄漏了,以避免重複釋放。
注意該文件是MRC仍是ARC,須要不一樣的內存處理方式。
若是是C申請的內存,注意new delete, malloc free的配對處理。
好比咱們的代碼中會遇到這樣的問題。
1
2
3
4
|
if
([self.itemOutput hasNewPixelBufferForItemTime:currentTime]) {
[self displayPixelBuffer:[self.itemOutput copyPixelBufferForItemTime:currentTime itemTimeForDisplay:NULL]];
[_program useGlProgram];
}
|
進行靜態檢測時會報copyPixelBufferForItemTime內存泄漏,copy後的對象須要進行釋放,可事實上,在「displayPixelBuffer」函數中已經對傳入對內存進行了釋放,咱們姑且不論這樣對寫法是否合理,只是切記在修改時注意結合上下文處理須要釋放的內存。
2.動態檢測 使用instruments
在Allocation中咱們主要關注的是Persistent和Persistent Bytes,分別表示當前時間段,申請了可是還沒釋放的內存數量和大小。
記住當前這兩個值,而後進入某個新頁面,退出該頁面,觀察這兩個值是否增長。須要注意的是,因爲有些圖片調用自己是有緩存的,若是是用SDWebImage管理,則網絡圖片都會緩存在內存中。所以退出頁面後內存有增長是正常的,並且還有些單例的內存也是不會釋放的,咱們能夠再次進入同一個頁面,在圖片都加載過的狀況下,反覆進入退出查看內存情況,若是持續增長,則說明有泄漏。
詳細使用:
第三方工具MLeaksFinder
一、使用簡單,不侵入業務邏輯代碼,不用打開 Instrument
二、不須要額外的操做,你只需開發你的業務邏輯,在你運行調試時就能幫你檢測
三、內存泄露發現及時,更改完代碼後一運行即能發現(這點很重要,你立刻就能意識到哪裏寫錯了)
四、精準,能準確地告訴你哪一個對象沒被釋放
具體特色,原理和集成方式能夠參考以下博客的內容:
界面卡頓
1、離屏渲染
OpenGL中,GPU屏幕渲染有如下兩種方式:
一.On-Screen Rendering
意爲當前屏幕渲染,指的是GPU的渲染操做是在當前用於顯示的屏幕緩衝區中進行。當前屏幕渲染是不須要額外建立額外的緩存,也不須要開啓新的上下文,相較於離屏渲染,性能更好。
可是受當前屏幕渲染的侷限因素限制(只有自身上下文,屏幕緩存有限等),不少圖形渲染,當前屏幕渲染是解決不了的,這時必須使用到離屏渲染。
二.Off-Screen Rendering
離屏渲染,指的是GPU在當前屏幕緩衝區之外新開闢一個緩衝區進行渲染操做。
特殊的離屏渲染:CPU渲染
若是咱們重寫了drawRect方法,而且使用任何Core Graphics的技術進行了繪製操做,就涉及到了CPU渲染,整個渲染過程有CPU在APP內同步的完成,渲染獲得的bitmap(位圖)最後再交由GPU用於顯示。
注意:CoreGraphics一般是線程安全的,因此能夠進行一步繪製,顯示的時候再放回主線程,一個簡單的異步繪製過程大體以下
1
2
3
4
5
6
7
8
9
10
11
|
-(void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}
|
普通的離屏渲染
相比於當前屏幕渲染,離屏渲染的代價是很高的,主要體如今兩個方面:
1 建立新的緩衝區
想要進行離屏渲染,首先要建立一個新的緩衝區。
2 上下文切換
離屏渲染的整個過程,須要屢次切換上下文環境:先是從當前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束之後,將離屏緩衝區的渲染結果顯示到屏幕上有須要將上下文回家從離屏切換到當前屏幕。而上下文環境的切換時要付出很大代價的。
設置瞭如下屬性時,都會觸發離屏繪製:
1.shouldRasterize(光柵化)
2 masks(遮罩)
3 shadows (陰影)
4 edge antialiasing (抗鋸齒)
5 group opacity (不透明)
其中shouldRasterize(光柵化)是比較特殊的一種:
光柵化概念:將圖轉化爲一個個柵格組成的圖像。
光柵化特色:每一個元素對應幀緩衝區中的一像素。
shouldRasterize = YES 在其餘屬性觸發離屏渲染的同時, 會將光柵化後的內容緩存起來,若是對應的layer 及其sublayers沒有發生改變,在下一幀的時候能夠直接複用。shouldRasterize = YES, 這將隱式的建立一個位圖,各類陰影遮罩等效果也會保存到位圖中並緩存起來,從而減小渲染的頻度(不是矢量圖)。
至關於光柵化是把GPU的操做轉到CPU上了,生成位圖緩存,直接讀取複用。
當你使用光柵化時,你能夠開啓"Color Hits Green And Misses Red"來檢查該場景下光柵化操做是不是一個號的選擇。綠色表示緩存被複用,紅色表示緩存在被重複建立。
若是光柵化的層變紅的太頻繁那麼光柵化對優化可能沒有多少用處,位圖緩存從內存中刪除又從新建立的太過頻繁,紅色代表緩存重建的太遲。能夠針對性的選擇某個較小而深的層結構進行光柵化,來嘗試減小渲染時間。
注意:
對於常常變更的內容, 這個時候不要開啓,不然會形成性能的浪費。
例如咱們平常常常打交道的TableViewCell, 由於TableViewCell 的重繪是很頻繁的(由於cell的複用),若是cell的內容不斷變化,則cell須要不斷重繪,若是此時設置了cell.layer可光柵化,則會形成大量的離屏渲染,下降圖形性能。
爲何使用離屏渲染
當使用圓角,陰影,遮罩的時候,圖層屬性的混合體被指定爲在未預合成以前不能直接在屏幕中繪製,因此就須要屏幕外渲染被喚起。
屏幕外渲染並不意味着軟件繪製,可是它意味着圖層必須在被顯示在一個屏幕外上下文中被渲染(不論死CPU仍是GPU)。
因此當使用離屏渲染的時候會很容易形成性能消耗,由於在OpenGL裏離屏渲染會單獨在內存中建立一個屏幕外緩衝區並進行渲染,而屏幕外緩衝區跟當前屏幕緩衝區上下文切換也是很耗性能的。
如何選擇
擺在咱們面前的有三個選擇:當前屏幕渲染、離屏渲染、CPU渲染。該使用哪一個呢?
儘可能使用當前屏幕渲染
鑑於離屏渲染、CPU渲染可能帶來的性能問題,通常狀況下,咱們要儘可能使用當前屏幕渲染。
離屏渲染、CPU渲染
因爲GPU的浮點運算能力比CPU強,CPU渲染的效率可能不如離屏渲染;但若是僅僅是實現一個簡單的效果,直接使用CPU渲染的效率又可能比離屏渲染好,畢竟普通的離屏渲染要涉及到緩衝區建立和上下文切換等耗時操做。普通的離屏繪製是發生在繪製服務(是獨立的處理過程)而且同時經過GPU執行。當OpenGL的繪製程序在繪製每一個layer的時候,有可能由於包含多子層級關係而必須停下來把他們合成到一個單獨的緩存裏。你可能認爲GPU應該老是比CPU牛逼一點兒,可是在這裏咱們仍是須要慎重的考慮一下。由於對GPU來講,噹噹前屏幕到離屏上下文環境的來回切換,代價是很是大的。由於對一些簡單的繪製過程來講,這個過程有可能用CoreGraphics,所有用CPU來完成反而會比GPU作的更好,因此若是你正在嘗試處理一些複雜的層級,而且在猶豫到底用-[CALayer setShouldRasterize:]仍是經過CoreGraphics來繪製層級上的全部內容,惟一的方法就是測試而且進行權衡。
Instuments監測離屏渲染
Instruments的Core Animation工具中又幾個和離屏渲染相關的檢查選項:
Color offscreen-Rendered Yellow
開啓後會把那些須要離屏渲染的圖層高亮成黃色,這就意味着黃色圖層可能存在性能問題。
Color Hits Green and Misses Red
若是shouldRasterize 被設置成YES,對應的渲染結果會被緩存,若是圖層是綠色,就表示這些緩存被複用;若是是紅色就表示緩存會被重複建立,這就表示該出存在性能問題了。
iOS 版本上的優化
iOS 9.0以前 UIImageView跟UIButton設置圓角都會觸發離屏渲染
iOS 9.0 以後 UIButton設置圓角會觸發離屏渲染,而UIImageView裏png圖片設置圓角不會再觸發離屏渲染了,若是設置其餘陰影效果之類的仍是會觸發離屏渲染。
離屏渲染總結
1.儘可能使用當前屏幕渲染,能不適用離屏渲染則儘可能不用,你應當儘可能避免使用layer的border、corner、shadow、mask等技術。
2.必須離屏渲染時,相對簡單的視圖應該使用CPU渲染,相對複雜的視圖則使用通常的離屏渲染。
2、tableview滑動卡頓
1."讓出"主線程,讓主線程減負。所謂"讓出"主線程,指的是不要什麼操做都放在主線程裏。放在主線程中的通常都是視圖相關的操做,好比添加子視圖、更新子視圖、刪除子視圖等。像其餘的圖片下載、數據請求這樣的操做盡可能不要放在主線程中進行。
1
2
3
4
5
6
7
8
9
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//處理一些跟當前視圖不要緊的事情
//...
//只用來操做與當前視圖有關係的事情,好比:刷新tableView
dispatch_async(dispatch_get_main_queue(), ^{
[tableView reload];
});
});
|
2.正確重用cell。正確重用cell不只僅要重用cell視圖,還須要好好重用cell的子視圖。
1
2
3
4
5
6
|
static NSString *Identifier = @
"WeatherCell"
;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier];
if
(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:
reuseIdentifier:]
}
|
上面的代碼在有cell可重用的時候,不會再建立新的cell,可是下面的一句話基本上會讓重用粉身碎骨。
1
2
3
4
|
//清空子視圖
[cell.contentView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOLBOOL *stop) {
[obj removeFromSuperview];
}];
|
上面這段代碼之因此會出現,緣由衆所周知:cell重用時會出現重疊。"解決"了重疊問題,那麼新問題來了,從新建立視圖既消耗內存還佔用時間,嚴重會出現滑動出現卡頓現象,並且都刪除了重建還能叫重用麼?
3、其餘解決卡頓辦法
imageView 儘可能設置爲不透明
opaque儘可能設爲YES,當imageView的opaque設置爲YES的時候其alpha的屬性就會無效,imageView的半透明取決於其圖片半透明或者imageView自己的背景色的合成的圖層view是半透明的。若是圖片所有不是半透明就不會觸發圖層的blend操做,整個圖層就回不透明,若是疊加的圖片有出現半透明的,就會立馬觸發圖層的blend操做,整個圖層不透明。
opaque設爲NO的話,當opaque爲NO的時候,圖層的半透明取決於圖片和其自己合成的圖層爲結果。
背景色儘量設爲alpha值爲1
當某一塊圖層的alpha和其superView的背景色alpha不同的時候會觸發alpha合成操做,這是一項看似簡單可是卻很是消耗CPU性能的操做。
UIView的背景色設置
UIview的背景色儘可能不要設置爲clearColor,這樣也會觸發alpha疊加,在tableview滑動時候是很是消耗性能的。子視圖的背景色儘量設置成其superView的背景色,這樣圖層合成的時候就不會觸發blend操做了。
最好不適用帶有alpha通道的圖片,若是有alpha儘可能讓美工取消alpha通道。
alpha通道 是透明的意思。
cell上layer儘可能避免使用圓角
在工做中關於滑動界面咱們會時常遇到cell行設置頭像爲圓角等需求,這時候咱們儘可能避免使用layer.masksToBounds,由於這會觸發離屏渲染(上面有講離屏渲染)。
優化UIImageView圓角方式 用貝塞爾曲線,不會觸發離屏渲染:
1
2
3
4
5
6
7
8
9
10
|
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];
|
優化圖片的加載方式
1
2
|
UIImage Image = [UIImage imageNamed:@
"helloworld"
;
UIImage Image = [UIImage imageWithContentOfFile:@
"helloworld"
];
|
圖片的兩種加載方式:
第一種:當咱們常常須要這張圖片而且僅僅是小圖的時候,咱們能夠使用此方式加載圖片。這種方式是把圖片緩存在圖片緩衝區,當咱們使用的時候會經過圖片的名字也就是經過key的方式去查找圖片在緩存區的內存地址。
當咱們使用不少圖片的時候系統就會開闢不少內存來存儲圖片。
第二種:當咱們使用工程裏面的一張大圖而且使用次數不多甚至爲1次的時候,咱們優先會採用這種方式來加載圖片,這種方式當使用完圖片的時候會當即丟棄釋放資源,因此對性能不會帶來負擔。
儘可能延遲圖片的加載
當咱們在滑動頁面的時候,尤爲是對於那種佈局特別複雜的cell,滑動的時候不要加載圖片,當滑動中止的時候再進行圖片的加載。
咱們都知道無論是tableview仍是scrollview在滾動的時候須要顯示東西都是經過runloop去拿。當滾動的時候runloop會處於NSRunLoopTrackingMode的模式,咱們能夠經過一個主線程隊列dispatch_after或者selfPerformSeletor設置runloop的模式爲NSDefaultRunLoopMode模式,就能夠作到中止滾動再加載圖片。注:其實嚴格意義上selfPerformSelector的事件就是在主線程隊列中等待。
使用懶加載,即 須要時再加載。
最終要的是避免阻塞主線程。
讓圖片的繪製、圖片的下載、對象的建立、文本的渲染等這些耗時的操做盡量採用子線程的方式去處理,對於layer以及UI的操做不得不在主線程裏面,只能想辦法優化(Facebook -> AsyncDisplayKit)
xib、storyBoard、純代碼
storyBoard能夠爲開發者節省大量的時間,提升開發效率,可是對於那種複雜的滑動界面,用storyBoard時很是耗資源的,對於那種重用性不強固定不怎麼變化的界面仍是storyBoard省事兒。
不要重複建立沒必要要的tableviewCell
UItableView只須要一屏幕的cell對象就能夠了,由於tableview提供了cell的緩存機制,在不可見的時候,能夠將其緩存起來,而在須要時繼續使用便可。值得一提的是,cell被重用時,它內部繪製的內容並不會被自動清除,所以你可能須要調用setNeedsDisplayInRect:或setNeedsDisplay方法。
減小視圖的數目
UITableViewCell包含了textLabel、detailTextLabel和imageView等view,而你還能夠自定義一些視圖放在它的contentView裏,然而view是很大的對象,建立它會消耗較多的資源,而且也影響渲染性能。若是你的cell包含圖片且數量較多,使用默認的cell會很是影響性能。最佳的解決辦法是繼承UITableViewCell,並在其drawRect:中自行繪製:
1
|
(void)drawRect:(CGRect)rect {
if
(image) { [image drawAtPoint:imagePoint]; self.image = nil; }
else
{ [placeHolder drawAtPoint:imagePoint]; } [text drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeTailTruncation]; }
|
不過這樣一來,你會發現選中一行後,這個cell就變藍了,其中的內容就被擋住了。最簡單的方法就是將cell的selectionStyle屬性設爲UITableViewCellSelectionStyleNone,這樣就不會被高亮了。
此 外還能夠建立CALayer,將內容繪製到layer上,而後對cell的contentView.layer調用addSublayer:方法。這個例 子中,layer並不會顯著影響性能,但若是layer透明,或者有圓角、變形等效果,就會影響到繪製速度了。解決辦法可參見後面的預渲染圖像。
不要作多餘的繪製工做。
在實現drawRect:的時候,它的rect參數就是須要繪製的區域,這個區域以外的不須要進行繪製。
例如上例中,就能夠用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判斷是否須要繪製image和text,而後再調用繪製方法。
預渲染圖像。
你會發現即便作到了上述幾點,當新的圖像出現時,仍然會有短暫的停頓現象。解決的辦法就是在bitmap context裏先將其畫一遍,導出成UIImage對象,而後再繪製到屏幕
少用addView給cell動態添加view,能夠初始化的時候就添加,而後經過hide控制是否顯示。
提早計算並緩存好高度,由於heightForRow最頻繁的調用。
善用hidden隱藏(顯示)Subview
圖片壓縮
一 首先要知道 什麼是壓縮:
「壓」 指文件體積變小,可是像素不變,長寬尺寸不變,那麼質量可能降低
「縮」 指文件的尺寸變小,也就是像素數減小,而長寬尺寸變小,文件體積一樣會減少。
二 圖片的壓處理
圖片的壓處理,咱們能夠使用UIImageJPEGRepresentation或UIImagePNGRepresentation方法實現
如代碼:
1
2
3
4
5
6
|
- (void)_imageCompression{
UIImage *image = [UIImage imageNamed:@
"HD"
];
//第一個參數是圖片對象,第二個參數是壓的係數,其值範圍爲0~1。
NSData * imageData = UIImageJPEGRepresentation(image, 0.7);
UIImage * newImage = [UIImage imageWithData:imageData];
}
|
關於PNG和JPEG格式壓縮
UIImageJPEGRepresentation函數須要兩個參數:圖片的引用和壓縮係數UIImagePNGRepresentation只須要圖片引用做爲參數。UIImagePNGRepresentation(UIImage image)要比UIImageJPEGRepresentation(UIImage image,1.0)返回的圖片數據量大不少。一樣的一張照片,使用UIImagePNGRepresentation(image)返回的數據量大小爲200k,而UIImageJPEGRepresentation(image,1.0)返回的數據量大小隻爲150k,若是對圖片的清晰度要求不是極高,建議使用UIImageJPEGRepresentation,能夠大幅度下降圖片數據量。注意:壓縮係數不宜過低,一般是0.3~0.7,太小則可能會出現黑邊等。
三 圖片「縮」處理
經過[image drawInRect:CGRectMake(0,0,targetWidth,targetHeight)能夠進行圖片「縮」處理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
/**
* 圖片壓縮到指定大小
* @param targetSize 目標圖片的大小
* @param sourceImage 源圖片
* @return 目標圖片
*/
- (UIImage*)imageByScalingAndCroppingForSize:(CGSize)targetSize withSourceImage:(UIImage *)sourceImage
{
UIImage *newImage = nil;
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
CGPoint thumbnailPoint = CGPointMake(0.0,0.0);
if
(CGSizeEqualToSize(imageSize, targetSize) == NO)
{
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if
(widthFactor > heightFactor)
scaleFactor = widthFactor;
// scale to fit height
else
scaleFactor = heightFactor;
// scale to fit width
scaledWidth= width * scaleFactor;
scaledHeight = height * scaleFactor;
// center the image
if
(widthFactor > heightFactor)
{
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
}
else
if
(widthFactor < heightFactor)
{
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}
UIGraphicsBeginImageContext(targetSize);
// this will crop
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width= scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
if
(newImage == nil)
NSLog(@
"could not scale image"
);
//pop the context to get back to the default
UIGraphicsEndImageContext();
return
newImage;
}
|
數據持久化
數據持久化是一種非易失性存儲技術,在重啓計算機或設備後也不會丟失數據,試講內存中的數據模型轉換爲存儲模型,以及將存儲模型轉換爲內存中的數據模型的統稱。數據模型能夠是任何數據結構或對象模型,存儲模型能夠是關係模型、XML、二進制流等。持久化技術主要用於MVC模型中的model層。目前iOS平臺上主要使用以下四種技術:
NSUserDefaults()
屬性列表概念:屬性列表是一種基於xml序列化的數據歐諾個就存儲文件,又稱plist文件,原理是將一些基本數據類型讀寫進plist文件(plist文件是XML格式文件,由於經常使用於存儲配置信息,使用又稱做plist格式文件)並以明文方式存儲在設備中。許多OC 的基本數據類型(如NSArray、NSString 等)自己提供了向plist文件讀寫的方法,可是實際項目中咱們用到的更可能是NSUserDefaults,NSUserDefaults是蘋果基於屬性列表所封裝的一個單例類,該類提供了基本數據類型的plist文件存儲方法,由於其使用方便,代碼易懂, NSUserDefaults成爲了最經常使用的數據持久化方式之一。
NSUserDefaults經常使用方法
1
2
3
4
5
6
7
8
9
10
11
12
|
//從 NSUserDefaults 中取出 key 值所對應的 Value
id = [[NSUserDefaults standardUserDefaults] objectForKey:(NSString *)];
//將數據對象存儲到 NSUserDefaults 中
[[NSUserDefaults standardUserDefaults] setObject:(id)
forKey:(NSString *)];
//將數據對象從 NSUserDefaults 中移除
[[NSUserDefaults standardUserDefaults] removeObjectForKey(NSString *)];
//同步更新到Plist文件,當修改了 NSUserDefaults 的數據後,必須進行此步操做
[[NSUserDefaults standardUserDefaults] synchronize];
|
NSUserDefaults特色
NSUserDefaults經常使用於存儲OC基本數據類型,不適合存儲自定義對象,NSUserDefaults支持的數據類型有:NSNumer(NSInteger,float,double)NSSstring,NSDate,NSArray,NSDictionary,BOOL.
自定義對象能夠轉化成基本類型NSData後再使用NSUserDefaults機型存儲,但並不經常使用。
當plist文件存儲的數據發生改變(寫操做)時,須要調用syschronize方法同步,不然數據沒法同步保存。
Key值應具備惟一性,重名時將覆蓋先前key值。
實際開發中,NSUserDefaults經常使用語於存儲配置信息,優勢是簡便,缺點是全部數據都以明文存儲在plist文件中,容易被解讀致使安全性不高。
NSUserDefautls將數據存儲在什麼地方了?
它存儲在應用的一個plist文件中,在程序沙盒位置的/Library/Prefereces裏面有個plist文件,存儲的就是你的userDefaults,想要刪掉的話,用removeObjectForKey或者刪掉沙盒,也就是你的應用程序而後從新安裝。
此外還能夠自定義plist文件的位置進行存儲數據
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
1.得到文件路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@
"123.plist"
];
2.存儲
NSArray *array = @[@
"123"
, @
"456"
, @
"789"
];
[array writeToFile:fileName atomically:YES];
3.讀取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@
"%@"
, result);
4.注意
只有以上列出的類型才能使用plist文件存儲。
存儲時使用writeToFile: atomically:方法。 其中atomically表示是否須要先寫入一個輔助文件,再把輔助文件拷貝到目標文件地址。這是更安全的寫入文件方法,通常都寫YES。
讀取時使用arrayWithContentsOfFile:方法。
|
對象歸檔(序列化)
和屬性列表同樣,對象歸檔也是將對象寫入文件並保存在硬盤內,因此本質上是另外一種形式的序列化(存儲模型不一樣)。雖然說任何對象均可以被序列化,但只有某些特定的對象才能放置到某個集合類(例如:NSArray,NSMutableArray,NSDictionary,NSData等)中,並使用該集合類的方法在屬性列表中讀寫,一旦將包含了自定義對象的數組寫入屬性列表,程序就會報錯。歸檔與屬性列表方式不一樣,屬性列表只有指定的一些對象才能進行持久化且明文存儲,而歸檔時任何實現了NSCoding協議的對象均可以被持久化,且歸檔後的文件是加密的。對象歸檔涉及兩個類: NSKeyedArchiver和NSKeyedUnarchiver,這兩個類是NSCoder的子類,分別用於歸檔和解檔。
對象歸檔
如今,咱們有一個自定義的Person類,該類有name,age,height三個屬性,其.h文件以下
1
2
3
4
5
6
|
//Person.h
#import
@interface Person:NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,assign)double height;
|
在.m文件中,咱們要實現NSCoding中的兩個協議方法,這兩個方法分別在歸檔和解檔時會被調用,Person類的.m文件以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
//Person.m
#import"Person.h"
@implementation Person
/*
* 歸檔時調用該方法,該方法說明哪些數據須要儲存,怎樣儲存
*/
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:_name forKey:@
"name"
];
[encoder encodeInt:_age forKey:@
"age"
];
[encoder encodeDouble:_name forKey:@
"height"
];
}
/*
* 歸檔時調用該方法,該方法說明哪些數據須要解析,怎樣解析
*/
-(id)initWithCoder:(NSCoder *)decode
{
if
(self = [
super
init]) {
_name = [decode decodeObjectForKey:@
"name"
];
_age = [decode decodeIntForKey:@
"age"
];
_height = [decode decodeDoubleForKey:@
"height"
];
}
return
self;
}
@end
|
這個Person類就具備了歸檔與解檔能力,當你須要對一個Person類的實力對象進行儲存或者解析時,在你本身的方法中只要鍵入以下代碼便可,下面兩個方法對應兩個按鈕的回調,點擊按鈕時分別執行person對象的歸檔和解檔。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//寫操做
- (IBAction)Write {
Person *p = [[Person alloc]init];
p.name = @
"jin"
;
p.age = 10;
p.height = 176.0;
//設置歸檔後文件路徑
NSString *path = @
"/Users/macbookair/Desktop/person.data"
;
//歸檔
[NSKeyedArchiver archiveRootObject:p toFile:path];
}
//讀操做
- (IBAction)read {
//設置路徑
NSString *path = @
"/Users/macbookair/Desktop/person.data"
;
//解檔
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@
"%@--%d---%f"
,p.name ,p.age ,p.height);
}
|
對象歸檔特色
能夠將自定義對象寫入文件或從文件中讀出。
因爲歸檔時進行了加密處理,所以安全性高於屬性列表。
CoreData(集成化)
當你的應用程序須要在本地存儲大量的關係型數據模型時,顯然上述方法並不適合,由於不論對象歸檔仍是數據列表,一旦數據模型之間存在依賴關係,問題就將變得複雜。而此時iPhone自帶的輕量級數據庫Sqlite便成爲咱們的首選,若是你熟悉數據庫,那麼恭喜,CoreData也將再也不神祕,你能夠理解爲它是蘋果對Sqlite封裝的一個框架,你能夠在Xcode中進行Sqlite數據庫的可視化操做
爲何要使用CoreData?
CoreData脫離了Sql語句,集成化更高。實際上,一個成熟的工程中必定是對數據持久化進行了封裝的,應該避免在業務邏輯中直接編寫Sql語句。
CoreData對版本遷移支持的較好,App升級以後數據庫字段或者表有更改會致使crash,CoreData的版本管理和數據遷移變得很是有用,手動寫sql語句操做相對麻煩一些。
CoreData不光能操縱SQLite,CoreData和iCloud的結合也很好,若是有這方面需求的話優先考慮CoreData。
CoreData是支持多線程的,但須要thread confinement的方式實現,使用了多線程以後能夠最大化的防止阻塞主線程。
Sqlite(靈活)
Sqlite是iPhone自帶的的數據庫管理系統。若是你對數據庫和Sql語句不陌生,那麼在介紹完CoreData後,你必定不知足CoreData,做爲一個程序員,也許你更但願可以直接操做數據庫。既然蘋果選擇Sqlite做爲數據庫,天然也提供了一系列能夠直接操做它的函數(C語言函數),利用這些函數你徹底可以本身封裝一個sqlite數據庫框架,但同時你必須熟悉sql語句以及C語言語法。SQLite數據庫的幾個特色:
基於C語言開發的輕型數據庫
在iOS中須要使用C語言語法進行數據庫操做、訪問(沒法使用ObjC直接訪問,由於libqlite3框架基於C語言編寫)
SQLite中採用的是動態數據類型,即便建立時定義了一種類型,在實際操做時也能夠存儲其餘類型,可是推薦建庫時使用合適的類型(特別是應用須要考慮跨平臺的狀況時)
創建鏈接後一般不須要關閉鏈接(儘管能夠手動關閉)
#FMDB
FMDB框架中重要的框架類
FMDatabase
FMDatabase對象就表明一個單獨的SQLite數據庫,用來執行SQL語句
FMResultSet
使用FMDatabase執行查詢後的結果集
FMDatabaseQueue
用於在多線程中執行多個查詢或更新,它是線程安全的
網絡傳輸協議
RunTime
總結的至關好 -----> iOS-RunTime,再也不只是據說
方法一 從APP服務器獲取暱稱和頭像
暱稱和頭像的獲取:當收到一條消息(羣消息)時,獲得發送者的用戶ID,而後查找手機本地數據庫是否有此用戶ID的暱稱和頭像,如沒有則調用APP服務器接口經過用戶ID查詢出暱稱和頭像,而後保存到本地數據庫和緩存,下次此用戶發來信息便可直接查詢緩存或者本地數據庫,不須要再次向APP服務器發起請求
暱稱和頭像的更新:當點擊發送者頭像時加載用戶詳情時從APP服務器查詢此用戶的具體信息而後更新本地數據庫和緩存。當用戶本身更新暱稱或頭像時,也能夠發送一條透傳消息到其餘用戶和用戶所在的羣,來更新該用戶的暱稱和頭像。
方法二 從消息擴展中獲取暱稱和頭像
暱稱和頭像的獲取:把用戶基本的暱稱和頭像的URL放到消息的擴展中,經過消息傳遞給接收方,當收到一條消息時,則能經過消息的擴展獲得發送者的暱稱和頭像URL,而後保存到本地數據庫和緩存。當顯示暱稱和頭像時,請從本地或者緩存中讀取,不要直接從消息中把賦值拿給界面(不然當用戶暱稱改變後,同一我的會顯示不一樣的暱稱)。
暱稱和頭像的更新:當擴展消息中的暱稱和頭像URI與當前本地數據庫和緩存中的相應數據不一樣的時候,須要把新的暱稱保存到本地數據庫和緩存,並下載新的頭像並保存到本地數據庫和緩存。
能夠參考http://www.jianshu.com/p/26b1294c71f6