iOS面试题目及答案总结
這是一些常見的筆試問題,總共12道題目。題目標(biāo)紅了,答案的話是我從網(wǎng)上查來的,大家覺得不標(biāo)準(zhǔn)的可以自行百度。
?
(1)????應(yīng)用程序啟動時的順序
首先回顧一下應(yīng)用程序的啟動過程
?
①.先加載Main函數(shù)
?
②.在Main函數(shù)里的 UIApplicationMain方法中,創(chuàng)建Application對象 創(chuàng)建Application的Delegate對象
?
③.創(chuàng)建主循環(huán),代理對象開始監(jiān)聽事件
?
④.啟動完畢會調(diào)用 didFinishLaunching方法,并在這個方法中創(chuàng)建UIWindow
?
⑤.設(shè)置UIWindow的根控制器是誰
?
⑥.如果有storyboard,會根據(jù)info.plist中找到應(yīng)用程序的入口storyboard并加載箭頭所指的控制器
?
⑦.顯示窗口
?
本文考慮的時步驟③之后到步驟⑦結(jié)束時將要調(diào)用的方法
?
其中有AppDelegate,ViewController,MainView(控制器的View),ChildView(子控件的View)的18個方法
?
AppDelegate中的:
?
1.application:didFinishLaunchingWithOptions:
?
2.applicationDidBecomeActive:
?
ViewController中的:
?
3.loadView
?
4.viewDidLoad
?
5.load
?
6.initialize
?
7.viewWillAppear
?
8.viewWillLayoutSubviews
?
9.viewDidLayoutSubviews
?
10.viewDidAppear
?
MainView(控制器的View)中的:
?
11.initWithCoder(如果沒有storyboard就會調(diào)用initWithFrame,這里兩種方法視為一種)
?
12.awakeFromNib
?
13.layoutSubviews
?
14.drawRect
?
ChildView(子控件View)中的:
?
15.initWithCoder(如果沒有storyboard就會調(diào)用initWithFrame,這里兩種方法視為一種)
?
16.awakeFromNib
?
17.layoutSubviews
?
18.drawRect
(2)????堆和棧的區(qū)別
按管理方式分
對于棧來講,是由系統(tǒng)編譯器自動管理,不需要程序員手動管理
對于堆來講,釋放工作由程序員手動管理,不及時回收容易產(chǎn)生內(nèi)存泄露
按分配方式分
堆是動態(tài)分配和回收內(nèi)存的,沒有靜態(tài)分配的堆
棧有兩種分配方式:靜態(tài)分配和動態(tài)分配
靜態(tài)分配是系統(tǒng)編譯器完成的,比如局部變量的分配
動態(tài)分配是有alloc函數(shù)進(jìn)行分配的,但是棧的動態(tài)分配和堆是不同的,它的動態(tài)分配也由系統(tǒng)編譯器進(jìn)行釋放,不需要程序員手動管理
一位網(wǎng)友用10個字總結(jié)了堆和棧的區(qū)別
?
棧是吃了吐 堆是吃了拉
?
(3)????線程和進(jìn)程的區(qū)別
1.1 程序:
?
由源代碼生成的可執(zhí)行應(yīng)用。(例如:QQ.APP)
?
1.2 進(jìn)程:
?
一個正在運(yùn)行的程序可以看做一個進(jìn)程。(例如:正在運(yùn)行的QQ就是一個進(jìn)程),進(jìn)程擁有獨(dú)立運(yùn)行所需的全部資源。
?
1.3 線程:
?
程序中獨(dú)立運(yùn)行的代碼段。(例如:接收QQ消息的代碼)
一個進(jìn)程是由一或多個線程組成。進(jìn)程只負(fù)責(zé)資源的調(diào)度和分配,線程才是程序真正的執(zhí)行單元,負(fù)責(zé)代碼的執(zhí)行。
?
2. 單線程與多線程有什么區(qū)別
?
2.1單線程
?
每個正在運(yùn)行的程序(即進(jìn)程),至少包含一個線程,這個線程叫主線程。
主線程在程序啟動時被創(chuàng)建,用于執(zhí)行main函數(shù)。
只有一個主線程的程序,稱作單線程程序。
主線程負(fù)責(zé)執(zhí)行程序的所有代碼(UI展現(xiàn)以及刷新,網(wǎng)絡(luò)請求,本地存儲等等)。這些代碼只能順序執(zhí)行,無法并發(fā)執(zhí)行。
?
2.2多線程
?
擁有多個線程的程序,稱作多線程程序。
iOS允許用戶自己開辟新的線程,相對于主線程來講,這些線程,稱作子線程。
可以根據(jù)需要開辟若干子線程
子線程和主線程是 都是 獨(dú)立的運(yùn)行單元,各自的執(zhí)行互不影響,因此能夠并發(fā)執(zhí)行。
?
2.3區(qū)別
?
單線程程序:只有一個線程,代碼順序執(zhí)行,容易出現(xiàn)代碼阻塞(頁面假死)。
多線程程序:有多個線程,線程間獨(dú)立運(yùn)行,能有效的避免代碼阻塞,并且提高程序的運(yùn)行性能。
注意:iOS中關(guān)于UI的添加和刷新必須在主線程中操作。
(4)????如何開啟多線程
在這篇文章中,我將為你整理一下 iOS 開發(fā)中幾種多線程方案,以及其使用方法和注意事項(xiàng)。當(dāng)然也會給出幾種多線程的案例,在實(shí)際使用中感受它們的區(qū)別。還有一點(diǎn)需要說明的是,這篇文章將會使用 Swift 和 Objective-c 兩種語言講解,雙語幼兒園。OK,let's begin!
?
概述
?
這篇文章中,我不會說多線程是什么、線程和進(jìn)程的區(qū)別、多線程有什么用,當(dāng)然我也不會說什么是串行、什么是并行等問題,這些我們應(yīng)該都知道的。
?
在 iOS 中其實(shí)目前有 4 套多線程方案,他們分別是:
?
Pthreads
NSThread
GCD
NSOperation& NSOperationQueue
所以接下來,我會一一講解這些方案的使用方法和一些案例。在將這些內(nèi)容的時候,我也會順帶說一些多線程周邊產(chǎn)品。比如:線程同步、 延時執(zhí)行、 單例模式 等等。
?
Pthreads
?
其實(shí)這個方案不用說的,只是拿來充個數(shù),為了讓大家了解一下就好了。百度百科里是這么說的:
?
POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標(biāo)準(zhǔn)。該標(biāo)準(zhǔn)定義了創(chuàng)建和操縱線程的一整套API。在類Unix操作系統(tǒng)(Unix、Linux、Mac OS X等)中,都使用Pthreads作為操作系統(tǒng)的線程。
簡單地說,這是一套在很多操作系統(tǒng)上都通用的多線程API,所以移植性很強(qiáng)(然并卵),當(dāng)然在 iOS 中也是可以的。不過這是基于 c語言 的框架,使用起來這酸爽!感受一下:
?
OBJECTIVE-C
?
當(dāng)然第一步要包含頭文件
?
#import<pthread.h>
然后創(chuàng)建線程,并執(zhí)行任務(wù)
?
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
??? pthread_t thread;
??? //創(chuàng)建一個線程并自動執(zhí)行
??? pthread_create(&thread, NULL, start,NULL);
}
?
void*start(void *data) {
??? NSLog(@"%@", [NSThreadcurrentThread]);
?
??? return NULL;
}
打印輸出:
?
2015-07-2723:57:21.689 testThread[10616:2644653] <NSThread: 0x7fbb48d33690>{number= 2, name = (null)}
看代碼就會發(fā)現(xiàn)他需要 c語言函數(shù),這是比較蛋疼的,更蛋疼的是你需要手動處理線程的各個狀態(tài)的轉(zhuǎn)換即管理生命周期,比如,這段代碼雖然創(chuàng)建了一個線程,但并沒有銷毀。
?
SWIFT
?
很遺憾,在我目前的swift1.2 中無法執(zhí)行這套方法,原因是這個函數(shù)需要傳入一個函數(shù)指針CFunctionPointer<T> 類型,但是目前 swift 無法將方法轉(zhuǎn)換成此類型。聽說 swift 2.0 引入一個新特性 @convention(c), 可以完成 Swift 方法轉(zhuǎn)換成 c 語言指針的。在這里可以看到
?
那么,Pthreads方案的多線程我就介紹這么多,畢竟做 iOS 開發(fā)幾乎不可能用到。但是如果你感興趣的話,或者說想要自己實(shí)現(xiàn)一套多線程方案,從底層開始定制,那么可以去搜一下相關(guān)資料。
?
NSThread
?
這套方案是經(jīng)過蘋果封裝后的,并且完全面向?qū)ο蟮摹K阅憧梢灾苯硬倏鼐€程對象,非常直觀和方便。但是,它的生命周期還是需要我們手動管理,所以這套方案也是偶爾用用,比如 [NSThread currentThread],它可以獲取當(dāng)前線程類,你就可以知道當(dāng)前線程的各種屬性,用于調(diào)試十分方便。下面來看看它的一些用法。
?
創(chuàng)建并啟動
?
先創(chuàng)建線程類,再啟動
?
OBJECTIVE-C
?
? // 創(chuàng)建
? NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:nil];
?
? // 啟動
? [thread start];
SWIFT
?
? //創(chuàng)建
? let thread = NSThread(target: self, selector:"run:", object: nil)
?
? //啟動
? thread.start()
創(chuàng)建并自動啟動
?
OBJECTIVE-C
?
? [NSThreaddetachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
SWIFT
?
?NSThread.detachNewThreadSelector("run:", toTarget: self,withObject: nil)
使用NSObject 的方法創(chuàng)建并自動啟動
?
OBJECTIVE-C
?
? [selfperformSelectorInBackground:@selector(run:) withObject:nil];
SWIFT
?
很遺憾 too! 蘋果認(rèn)為 performSelector: 不安全,所以在 Swift 去掉了這個方法。
?
Note: TheperformSelector: method and related selector-invoking methods are not importedin Swift because they are inherently unsafe.
其他方法
?
除了創(chuàng)建啟動外,NSThread還以很多方法,下面我列舉一些常見的方法,當(dāng)然我列舉的并不完整,更多方法大家可以去類的定義里去看。
?
OBJECTIVE-C
?
//取消線程
-(void)cancel;
?
//啟動線程
-(void)start;
?
//判斷某個線程的狀態(tài)的屬性
@property(readonly, getter=isExecuting) BOOL executing;
@property(readonly, getter=isFinished) BOOL finished;
@property(readonly, getter=isCancelled) BOOL cancelled;
?
//設(shè)置和獲取線程名字
-(void)setName:(NSString*)n;
-(NSString*)name;
?
//獲取當(dāng)前線程信息
+(NSThread *)currentThread;
?
//獲取主線程信息
+(NSThread *)mainThread;
?
//使當(dāng)前線程暫停一段時間,或者暫停到某個時刻
+(void)sleepForTimeInterval:(NSTimeInterval)time;
+(void)sleepUntilDate:(NSDate *)date;
SWIFT
?
Swift的方法名字和OC的方法名都一樣,我就不浪費(fèi)空間列舉出來了。
?
其實(shí),NSThread用起來也挺簡單的,因?yàn)樗湍菐追N方法。同時,我們也只有在一些非常簡單的場景才會用 NSThread, 畢竟它還不夠智能,不能優(yōu)雅地處理多線程中的其他高級概念。所以接下來要說的內(nèi)容才是重點(diǎn)。
?
GCD
?
GrandCentral Dispatch,聽名字就霸氣。它是蘋果為多核的并行運(yùn)算提出的解決方案,所以會自動合理地利用更多的CPU內(nèi)核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。同時它使用的也是 c語言,不過由于使用了 Block(Swift里叫做閉包),使得使用起來更加方便,而且靈活。所以基本上大家都使用 GCD 這套方案,老少咸宜,實(shí)在是居家旅行、殺人滅口,必備良藥。不好意思,有點(diǎn)中二,咱們繼續(xù)。
?
任務(wù)和隊(duì)列
?
在 GCD 中,加入了兩個非常重要的概念:任務(wù) 和 隊(duì)列。
?
任務(wù):即操作,你想要干什么,說白了就是一段代碼,在 GCD 中就是一個 Block,所以添加任務(wù)十分方便。任務(wù)有兩種執(zhí)行方式:同步執(zhí)行 和 異步執(zhí)行,他們之間的區(qū)別是 是否會創(chuàng)建新的線程。
?
同步執(zhí)行:只要是同步執(zhí)行的任務(wù),都會在當(dāng)前線程執(zhí)行,不會另開線程。
?
異步執(zhí)行:只要是異步執(zhí)行的任務(wù),都會另開線程,在別的線程執(zhí)行。
?
更新:
這里說的并不準(zhǔn)確,同步(sync) 和 異步(async) 的主要區(qū)別在于會不會阻塞當(dāng)前線程,直到 Block 中的任務(wù)執(zhí)行完畢!
如果是 同步(sync)操作,它會阻塞當(dāng)前線程并等待 Block 中的任務(wù)執(zhí)行完畢,然后當(dāng)前線程才會繼續(xù)往下運(yùn)行。
如果是 異步(async)操作,當(dāng)前線程會直接往下執(zhí)行,它不會阻塞當(dāng)前線程。
隊(duì)列:用于存放任務(wù)。一共有兩種隊(duì)列, 串行隊(duì)列 和 并行隊(duì)列。
?
串行隊(duì)列 中的任務(wù)會根據(jù)隊(duì)列的定義 FIFO 的執(zhí)行,一個接一個的先進(jìn)先出的進(jìn)行執(zhí)行。
?
更新:放到串行隊(duì)列的任務(wù),GCD 會 FIFO(先進(jìn)先出) 地取出來一個,執(zhí)行一個,然后取下一個,這樣一個一個的執(zhí)行。
并行隊(duì)列 中的任務(wù) 根據(jù)同步或異步有不同的執(zhí)行方式。
?
更新:放到并行隊(duì)列的任務(wù),GCD 也會 FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程。這樣由于取的動作很快,忽略不計,看起來,所有的任務(wù)都是一起執(zhí)行的。不過需要注意,GCD 會根據(jù)系統(tǒng)資源控制并行的數(shù)量,所以如果任務(wù)很多,它并不會讓所有任務(wù)同時執(zhí)行。
雖然很繞,但請看下表:
?
同步執(zhí)行? 異步執(zhí)行
串行隊(duì)列? 當(dāng)前線程,一個一個執(zhí)行? 其他線程,一個一個執(zhí)行
并行隊(duì)列? 當(dāng)前線程,一個一個執(zhí)行? 開很多線程,一起執(zhí)行
創(chuàng)建隊(duì)列
?
主隊(duì)列:這是一個特殊的 串行隊(duì)列。什么是主隊(duì)列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主隊(duì)列執(zhí)行,所以一般耗時的任務(wù)都要放到別的線程執(zhí)行。
?
? //OBJECTIVE-C
? dispatch_queue_t queue =ispatch_get_main_queue();
?
? //SWIFT
? let queue = ispatch_get_main_queue()
自己創(chuàng)建的隊(duì)列:凡是自己創(chuàng)建的隊(duì)列都是 串行隊(duì)列。 其中第一個參數(shù)是標(biāo)識符,用于 DEBUG 的時候標(biāo)識唯一的隊(duì)列,可以為空。大家可以看xcode的文檔查看參數(shù)意義。
?
更新:自己可以創(chuàng)建 串行隊(duì)列, 也可以創(chuàng)建 并行隊(duì)列。看下面的代碼(代碼已更新),它有兩個參數(shù),第一個上面已經(jīng)說了,第二個才是最重要的。
第二個參數(shù)用來表示創(chuàng)建的隊(duì)列是串行的還是并行的,傳入 DISPATCH_QUEUE_SERIAL 或 NULL 表示創(chuàng)建串行隊(duì)列。傳入 DISPATCH_QUEUE_CONCURRENT 表示創(chuàng)建并行隊(duì)列。
? //OBJECTIVE-C
? //串行隊(duì)列
? dispatch_queue_t queue =dispatch_queue_create("tk.bourne.testQueue", NULL);
? dispatch_queue_t queue =dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
? //并行隊(duì)列
? dispatch_queue_t queue =dispatch_queue_create("tk.bourne.testQueue",DISPATCH_QUEUE_CONCURRENT);
?
? //SWIFT
? //串行隊(duì)列
? let queue =dispatch_queue_create("tk.bourne.testQueue", nil);
? let queue =dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)
? //并行隊(duì)列
? let queue =dispatch_queue_create("tk.bourne.testQueue",DISPATCH_QUEUE_CONCURRENT)
全局并行隊(duì)列:這應(yīng)該是唯一一個并行隊(duì)列, 只要是并行任務(wù)一般都加入到這個隊(duì)列。這是系統(tǒng)提供的一個并發(fā)隊(duì)列。
?
? //OBJECTIVE-C
? dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
?
? //SWIFT
? let queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
創(chuàng)建任務(wù)
?
同步任務(wù): 不會另開線程 改:會阻塞當(dāng)前線程 (SYNC)
?
OBJECTIVE-C
?
? dispatch_sync(<#queue#>, ^{
????? //code here
????? NSLog(@"%@", [NSThreadcurrentThread]);
? });
SWIFT
?
? dispatch_sync(<#queue#>, { () ->Void in
????? //code here
????? println(NSThread.currentThread())
? })
異步任務(wù):會另開線程 改:不會阻塞當(dāng)前線程 (ASYNC)
?
OBJECTIVE-C
?
? dispatch_async(<#queue#>, ^{
????? //code here
????? NSLog(@"%@", [NSThreadcurrentThread]);
? });
SWIFT
?
? dispatch_async(<#queue#>, { () ->Void in
????? //code here
????? println(NSThread.currentThread())
? })
更新:
為了更好的理解同步和異步,和各種隊(duì)列的使用,下面看兩個示例:
示例一:
以下代碼在主線程調(diào)用,結(jié)果是什么?
NSLog("之前 - %@", NSThread.currentThread())
dispatch_sync(dispatch_get_main_queue(),{ () -> Void in
??????? NSLog("sync - %@",NSThread.currentThread())
})
NSLog("之后 - %@", NSThread.currentThread())
答案:
只會打印第一句:之前 - <NSThread: 0x7fb3a9e16470>{number = 1, name = main} ,然后主線程就卡死了,你可以在界面上放一個按鈕,你就會發(fā)現(xiàn)點(diǎn)不了了。
解釋:
同步任務(wù)會阻塞當(dāng)前線程,然后把 Block 中的任務(wù)放到指定的隊(duì)列中執(zhí)行,只有等到 Block 中的任務(wù)完成后才會讓當(dāng)前線程繼續(xù)往下運(yùn)行。
那么這里的步驟就是:打印完第一句后,dispatch_sync 立即阻塞當(dāng)前的主線程,然后把 Block 中的任務(wù)放到 main_queue 中,可是 main_queue 中的任務(wù)會被取出來放到主線程中執(zhí)行,但主線程這個時候已經(jīng)被阻塞了,所以 Block 中的任務(wù)就不能完成,它不完成,dispatch_sync 就會一直阻塞主線程,這就是死鎖現(xiàn)象。導(dǎo)致主線程一直卡死。
?
示例二:
以下代碼會產(chǎn)生什么結(jié)果?
?
let queue= dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL)
?
NSLog("之前 - %@", NSThread.currentThread())
?
dispatch_async(queue,{ () -> Void in
??? NSLog("sync之前- %@", NSThread.currentThread())
??? dispatch_sync(queue, { () -> Void in
???????? NSLog("sync - %@",NSThread.currentThread())
??? })
??? NSLog("sync之后- %@", NSThread.currentThread())
})
?
NSLog("之后 - %@", NSThread.currentThread())
?
**答案:**
2015-07-3002:06:51.058 test[33329:8793087] 之前 - <NSThread:0x7fe32050dbb0>{number = 1, name = main}
2015-07-3002:06:51.059 test[33329:8793356] sync之前 - <NSThread:0x7fe32062e9f0>{number = 2, name = (null)}
2015-07-3002:06:51.059 test[33329:8793087] 之后 - <NSThread:0x7fe32050dbb0>{number = 1, name = main}
很明顯 `sync- %@` 和 `sync之后 - %@` 沒有被打印出來!這是為什么呢?我們再來分析一下:
?
>**分析:**
我們按執(zhí)行順序一步步來哦:
1. 使用 `DISPATCH_QUEUE_SERIAL` 這個參數(shù),創(chuàng)建了一個 **串行隊(duì)列**。
2. 打印出 `之前 - %@` 這句。
3.`dispatch_async` 異步執(zhí)行,所以當(dāng)前線程不會被阻塞,于是有了兩條線程,一條當(dāng)前線程繼續(xù)往下打印出 `之后 - %@`這句, 另一臺執(zhí)行 Block 中的內(nèi)容打印 `sync之前 - %@` 這句。因?yàn)檫@兩條是并行的,所以打印的先后順序無所謂。
4. 注意,高潮來了。現(xiàn)在的情況和上一個例子一樣了。`dispatch_sync`同步執(zhí)行,于是它所在的線程會被阻塞,一直等到 `sync`里的任務(wù)執(zhí)行完才會繼續(xù)往下。于是 `sync` 就高興的把自己Block 中的任務(wù)放到 `queue` 中,可誰想`queue` 是一個串行隊(duì)列,一次執(zhí)行一個任務(wù),所以 `sync` 的 Block 必須等到前一個任務(wù)執(zhí)行完畢,可萬萬沒想到的是 `queue` 正在執(zhí)行的任務(wù)就是被 `sync` 阻塞了的那個。于是又發(fā)生了死鎖。所以 `sync` 所在的線程被卡死了。剩下的兩句代碼自然不會打印。
?
?
### 隊(duì)列組
?
隊(duì)列組可以將很多隊(duì)列添加到一個組里,這樣做的好處是,當(dāng)這個組里所有的任務(wù)都執(zhí)行完了,隊(duì)列組會通過一個方法通知我們。下面是使用方法,這是一個很實(shí)用的功能。
?
######OBJECTIVE-C
?
```objective-c
//1.創(chuàng)建隊(duì)列組
dispatch_group_tgroup = dispatch_group_create();
//2.創(chuàng)建隊(duì)列
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
?
//3.多次使用隊(duì)列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group,queue, ^{
?? for (NSInteger i = 0; i < 3; i++) {
?????? NSLog(@"group-01 - %@",[NSThread currentThread]);
?? }
});
?
//3.2.主隊(duì)列執(zhí)行8次循環(huán)
dispatch_group_async(group,dispatch_get_main_queue(), ^{
?? for (NSInteger i = 0; i < 8; i++) {
?????? NSLog(@"group-02 - %@",[NSThread currentThread]);
?? }
});
?
//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group,queue, ^{
?? for (NSInteger i = 0; i < 5; i++) {
?????? NSLog(@"group-03 - %@",[NSThread currentThread]);
?? }
});
?
//4.都完成后會自動通知
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
?? NSLog(@"完成 -%@", [NSThread currentThread]);
});
SWIFT
?
//1.創(chuàng)建隊(duì)列組
let group= dispatch_group_create()
//2.創(chuàng)建隊(duì)列
let queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
?
//3.多次使用隊(duì)列組的方法執(zhí)行任務(wù), 只有異步方法
//3.1.執(zhí)行3次循環(huán)
dispatch_group_async(group,queue) { () -> Void in
??? for _ in 0..<3 {
??????? NSLog("group-01 - %@",NSThread.currentThread())
??? }
}
?
//3.2.主隊(duì)列執(zhí)行8次循環(huán)
dispatch_group_async(group,dispatch_get_main_queue()) { () -> Void in
??? for _ in 0..<8 {
??????? NSLog("group-02 - %@", NSThread.currentThread())
??? }
}
?
//3.3.執(zhí)行5次循環(huán)
dispatch_group_async(group,queue) { () -> Void in
??? for _ in 0..<5 {
??????? NSLog("group-03 - %@",NSThread.currentThread())
??? }
}
?
//4.都完成后會自動通知
dispatch_group_notify(group,dispatch_get_main_queue()) { () -> Void in
??? NSLog("完成 -%@", NSThread.currentThread())
}
打印結(jié)果
?
2015-07-2803:40:34.277 test[12540:3319271] group-03 - <NSThread:0x7f9772536f00>{number = 3, name = (null)}
?
2015-07-2803:40:34.277 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number= 1, name = main}
?
2015-07-2803:40:34.277 test[12540:3319146] group-02 - <NSThread:0x7f977240ba60>{number = 1, name = main}
?
2015-07-2803:40:34.277 test[12540:3319271] group-03 - <NSThread:0x7f9772536f00>{number = 3, name = (null)}
?
2015-07-2803:40:34.278 test[12540:3319146] group-02 - <NSThread:0x7f977240ba60>{number = 1, name = main}
?
2015-07-2803:40:34.278 test[12540:3319271] group-03 - <NSThread:0x7f9772536f00>{number = 3, name = (null)}
?
2015-07-2803:40:34.278 test[12540:3319271] group-03 - <NSThread:0x7f9772536f00>{number = 3, name = (null)}
?
2015-07-2803:40:34.278 test[12540:3319146] group-02 - <NSThread:0x7f977240ba60>{number = 1, name = main}
?
2015-07-2803:40:34.277 test[12540:3319273] group-01 - <NSThread: 0x7f977272e8d0>{number= 2, name = (null)}
?
2015-07-2803:40:34.278 test[12540:3319271] group-03 - <NSThread:0x7f9772536f00>{number = 3, name = (null)}
?
2015-07-2803:40:34.278 test[12540:3319146] group-02 - <NSThread:0x7f977240ba60>{number = 1, name = main}
?
2015-07-2803:40:34.278 test[12540:3319273] group-01 - <NSThread:0x7f977272e8d0>{number = 2, name = (null)}
?
2015-07-2803:40:34.278 test[12540:3319146] group-02 - <NSThread:0x7f977240ba60>{number = 1, name = main}
?
2015-07-2803:40:34.278 test[12540:3319273] group-01 - <NSThread:0x7f977272e8d0>{number = 2, name = (null)}
?
2015-07-2803:40:34.279 test[12540:3319146] group-02 - <NSThread:0x7f977240ba60>{number = 1, name = main}
?
2015-07-2803:40:34.279 test[12540:3319146] group-02 - <NSThread: 0x7f977240ba60>{number= 1, name = main}
?
2015-07-2803:40:34.279 test[12540:3319146] 完成 - <NSThread:0x7f977240ba60>{number = 1, name = main}
這些就是 GCD 的基本功能,但是它的能力遠(yuǎn)不止這些,等講完 NSOperation 后,我們再來看看它的一些其他方面用途。而且,只要你想象力夠豐富,你可以組合出更好的用法。
?
更新:關(guān)于GCD,還有兩個需要說的:
func dispatch_barrier_async(_queue: dispatch_queue_t, _ block: dispatch_block_t):
這個方法重點(diǎn)是你傳入的 queue,當(dāng)你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數(shù)自己創(chuàng)建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當(dāng)前線程),一直等到這個 queue 中排在它前面的任務(wù)都執(zhí)行完成后才會開始執(zhí)行自己,自己執(zhí)行完畢后,再會取消阻塞,使這個 queue 中排在它后面的任務(wù)繼續(xù)執(zhí)行。
如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了。
funcdispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):
這個方法的使用和上一個一樣,傳入 自定義的并發(fā)隊(duì)列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞queue,不同的是 這個方法還會 阻塞當(dāng)前線程。
如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了。
NSOperation和NSOperationQueue
?
NSOperation是蘋果公司對 GCD 的封裝,完全面向?qū)ο?#xff0c;所以使用起來更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分別對應(yīng) GCD 的 任務(wù) 和 隊(duì)列 。操作步驟也很好理解:
?
將要執(zhí)行的任務(wù)封裝到一個 NSOperation 對象中。
將此任務(wù)添加到一個NSOperationQueue 對象中。
然后系統(tǒng)就會自動在執(zhí)行任務(wù)。至于同步還是異步、串行還是并行請繼續(xù)往下看:
?
添加任務(wù)
?
值得說明的是,NSOperation只是一個抽象類,所以不能封裝任務(wù)。但它有 2 個子類用于封裝任務(wù)。分別是:NSInvocationOperation 和 NSBlockOperation 。創(chuàng)建一個 Operation 后,需要調(diào)用 start 方法來啟動任務(wù),它會 默認(rèn)在當(dāng)前隊(duì)列同步執(zhí)行。當(dāng)然你也可以在中途取消一個任務(wù),只需要調(diào)用其 cancel 方法即可。
?
NSInvocationOperation: 需要傳入一個方法名。
?
OBJECTIVE-C
?
? //1.創(chuàng)建NSInvocationOperation對象
? NSInvocationOperation *operation =[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run)object:nil];
?
? //2.開始執(zhí)行
? [operation start];
SWIFT
?
在 Swift 構(gòu)建的和諧社會里,是容不下 NSInvocationOperation 這種不是類型安全的敗類的。蘋果如是說。這里有相關(guān)解釋
?
NSBlockOperation
?
OBJECTIVE-C
?
? //1.創(chuàng)建NSBlockOperation對象
? NSBlockOperation *operation =[NSBlockOperation blockOperationWithBlock:^{
????? NSLog(@"%@", [NSThreadcurrentThread]);
? }];
?
? //2.開始任務(wù)
? [operation start];
SWIFT
?
? //1.創(chuàng)建NSBlockOperation對象
? let operation = NSBlockOperation { () ->Void in
????? println(NSThread.currentThread())
? }
?
? //2.開始任務(wù)
? operation.start()
之前說過這樣的任務(wù),默認(rèn)會在當(dāng)前線程執(zhí)行。但是 NSBlockOperation 還有一個方法:addExecutionBlock: ,通過這個方法可以給 Operation 添加多個執(zhí)行 Block。這樣 Operation 中的任務(wù) 會并發(fā)執(zhí)行,它會 在主線程和其它的多個線程 執(zhí)行這些任務(wù),注意下面的打印結(jié)果:
?
OBJECTIVE-C
?
????? //1.創(chuàng)建NSBlockOperation對象
????? NSBlockOperation *operation =[NSBlockOperation blockOperationWithBlock:^{
????????? NSLog(@"%@", [NSThreadcurrentThread]);
????? }];
?
???? ?//添加多個Block
????? for (NSInteger i = 0; i < 5; i++) {
????????? [operation addExecutionBlock:^{
????????????? NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
????????? }];
????? }
?
????? //2.開始任務(wù)
????? [operation start];
SWIFT
?
??????? //1.創(chuàng)建NSBlockOperation對象
??????? let operation = NSBlockOperation { ()-> Void in
??????????? NSLog("%@",NSThread.currentThread())
??????? }
?
??????? //2.添加多個Block
??????? for i in 0..<5 {
??????????? operation.addExecutionBlock { ()-> Void in
? ??????????????NSLog("第%ld次 - %@", i,NSThread.currentThread())
??????????? }
??????? }
?
??????? //2.開始任務(wù)
??????? operation.start()
打印輸出
?
2015-07-2817:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
?
2015-07-2817:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
?
2015-07-2817:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3,name = (null)}
?
2015-07-2817:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}
?
2015-07-2817:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
?
2015-07-2817:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
?
?
NOTE:addExecutionBlock 方法必須在 start() 方法之前執(zhí)行,否則就會報錯:
?
‘***-[NSBlockOperation addExecutionBlock:]: blocks cannot be added after theoperation has started executing or finished'
?
?
NOTE:大家可能發(fā)現(xiàn)了一個問題,為什么我在 Swift 里打印輸出使用 NSLog() 而不是 println() 呢?原因是使用 print() / println() 輸出的話,它會簡單地使用流(stream) 的概念,學(xué)過 C++ 的都知道。它會把需要輸出的每個字符一個一個的輸出到控制臺。普通使用并沒有問題,可是當(dāng)多線程同步輸出的時候問題就來了,由于很多 println() 同時打印,就會導(dǎo)致控制臺上的字符混亂的堆在一起,而NSLog() 就沒有這個問題。到底是什么樣子的呢?你可以把上面 NSLog() 改為 println() ,然后一試便知。 更多 NSLog() 與 println() 的區(qū)別看這里
?
自定義Operation
?
除了上面的兩種 Operation以外,我們還可以自定義 Operation。自定義Operation 需要繼承 NSOperation 類,并實(shí)現(xiàn)其 main() 方法,因?yàn)樵谡{(diào)用 start() 方法的時候,內(nèi)部會調(diào)用 main() 方法完成相關(guān)邏輯。所以如果以上的兩個類無法滿足你的欲望的時候,你就需要自定義了。你想要實(shí)現(xiàn)什么功能都可以寫在里面。除此之外,你還需要實(shí)現(xiàn) cancel() 在內(nèi)的各種方法。所以這個功能提供給高級玩家,我在這里就不說了,等我需要用到時在研究它,到時候可能會再做更新。
?
創(chuàng)建隊(duì)列
?
看過上面的內(nèi)容就知道,我們可以調(diào)用一個 NSOperation 對象的 start() 方法來啟動這個任務(wù),但是這樣做他們默認(rèn)是同步執(zhí)行 的。就算是 addExecutionBlock 方法,也會在 當(dāng)前線程和其他線程 中執(zhí)行,也就是說還是會占用當(dāng)前線程。這是就要用到隊(duì)列 NSOperationQueue 了。而且,按類型來說的話一共有兩種類型:主隊(duì)列、其他隊(duì)列。只要添加到隊(duì)列,會自動調(diào)用任務(wù)的 start() 方法
?
主隊(duì)列
?
細(xì)心的同學(xué)就會發(fā)現(xiàn),每套多線程方案都會有一個主線程(當(dāng)然啦,說的是iOS中,像 pthread 這種多系統(tǒng)的方案并沒有,因?yàn)?UI線程 理論需要每種操作系統(tǒng)自己定制)。這是一個特殊的線程,必須串行。所以添加到主隊(duì)列的任務(wù)都會一個接一個地排著隊(duì)在主線程處理。
?
//OBJECTIVE-C
NSOperationQueue*queue = [NSOperationQueue mainQueue];
?
//SWIFT
let queue= NSOperationQueue.mainQueue()
其他隊(duì)列
?
因?yàn)橹麝?duì)列比較特殊,所以會單獨(dú)有一個類方法來獲得主隊(duì)列。那么通過初始化產(chǎn)生的隊(duì)列就是其他隊(duì)列了,因?yàn)橹挥羞@兩種隊(duì)列,除了主隊(duì)列,其他隊(duì)列就不需要名字了。
?
注意:其他隊(duì)列的任務(wù)會在其他線程并行執(zhí)行。
?
OBJECTIVE-C
?
//1.創(chuàng)建一個其他隊(duì)列???
NSOperationQueue*queue = [[NSOperationQueue alloc] init];
?
//2.創(chuàng)建NSBlockOperation對象
NSBlockOperation*operation = [NSBlockOperation blockOperationWithBlock:^{
??? NSLog(@"%@", [NSThreadcurrentThread]);
}];
?
//3.添加多個Block
for(NSInteger i = 0; i < 5; i++) {
??? [operation addExecutionBlock:^{
??????? NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
??? }];
}
?
//4.隊(duì)列添加任務(wù)
[queue addOperation:operation];
SWIFT
?
//1.創(chuàng)建其他隊(duì)列
let queue= NSOperationQueue()
?
//2.創(chuàng)建NSBlockOperation對象
letoperation = NSBlockOperation { () -> Void in
??? NSLog("%@",NSThread.currentThread())
}
?
//3.添加多個Block
for i in0..<5 {
??? operation.addExecutionBlock { () -> Voidin
??????? NSLog("第%ld次 - %@", i, NSThread.currentThread())
??? }
}
?
//4.隊(duì)列添加任務(wù)
queue.addOperation(operation)
打印輸出
?
2015-07-2820:26:28.463 test[18622:4443534] <NSThread: 0x7fd022c3ac10>{number = 5,name = (null)}
?
2015-07-2820:26:28.463 test[18622:4443536] 第2次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}
?
2015-07-2820:26:28.463 test[18622:4443535] 第0次 - <NSThread: 0x7fd022f237f0>{number = 4, name = (null)}
?
2015-07-2820:26:28.463 test[18622:4443533] 第1次 - <NSThread: 0x7fd022d372b0>{number = 3, name = (null)}
?
2015-07-2820:26:28.463 test[18622:4443534] 第3次 - <NSThread: 0x7fd022c3ac10>{number = 5, name = (null)}
?
2015-07-2820:26:28.463 test[18622:4443536] 第4次 - <NSThread: 0x7fd022e36d50>{number = 2, name = (null)}
OK, 這時應(yīng)該發(fā)問了,大家將 NSOperationQueue 與 GCD的隊(duì)列 相比較就會發(fā)現(xiàn),這里沒有串行隊(duì)列,那如果我想要10個任務(wù)在其他線程串行的執(zhí)行怎么辦?
?
這就是蘋果封裝的妙處,你不用管串行、并行、同步、異步這些名詞。NSOperationQueue 有一個參數(shù)maxConcurrentOperationCount 最大并發(fā)數(shù),用來設(shè)置最多可以讓多少個任務(wù)同時執(zhí)行。當(dāng)你把它設(shè)置為 1 的時候,他不就是串行了嘛!
?
NSOperationQueue還有一個添加任務(wù)的方法,- (void)addOperationWithBlock:(void(^)(void))block; ,這是不是和 GCD 差不多?這樣就可以添加一個任務(wù)到隊(duì)列中了,十分方便。
?
NSOperation有一個非常實(shí)用的功能,那就是添加依賴。比如有 3 個任務(wù):A:從服務(wù)器上下載一張圖片,B:給這張圖片加個水印,C:把圖片返回給服務(wù)器。這時就可以用到依賴了:
?
OBJECTIVE-C
?
//1.任務(wù)一:下載圖片
NSBlockOperation*operation1 = [NSBlockOperation blockOperationWithBlock:^{
??? NSLog(@"下載圖片 -%@", [NSThread currentThread]);
??? [NSThread sleepForTimeInterval:1.0];
}];
?
//2.任務(wù)二:打水印
NSBlockOperation*operation2 = [NSBlockOperation blockOperationWithBlock:^{
??? NSLog(@"打水印?? - %@", [NSThread currentThread]);
??? [NSThread sleepForTimeInterval:1.0];
}];
?
//3.任務(wù)三:上傳圖片
NSBlockOperation*operation3 = [NSBlockOperation blockOperationWithBlock:^{
??? NSLog(@"上傳圖片 -%@", [NSThread currentThread]);
??? [NSThread sleepForTimeInterval:1.0];
}];
?
//4.設(shè)置依賴
[operation2addDependency:operation1];????? //任務(wù)二依賴任務(wù)一
[operation3addDependency:operation2];????? //任務(wù)三依賴任務(wù)二
?
//5.創(chuàng)建隊(duì)列并加入任務(wù)
NSOperationQueue*queue = [[NSOperationQueue alloc] init];
[queueaddOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
SWIFT
?
//1.任務(wù)一:下載圖片
letoperation1 = NSBlockOperation { () -> Void in
??? NSLog("下載圖片 -%@", NSThread.currentThread())
??? NSThread.sleepForTimeInterval(1.0)
}
?
//2.任務(wù)二:打水印
letoperation2 = NSBlockOperation { () -> Void in
??? NSLog("打水印?? - %@", NSThread.currentThread())
??? NSThread.sleepForTimeInterval(1.0)
}
?
//3.任務(wù)三:上傳圖片
letoperation3 = NSBlockOperation { () -> Void in
??? NSLog("上傳圖片 -%@", NSThread.currentThread())
??? NSThread.sleepForTimeInterval(1.0)
}
?
//4.設(shè)置依賴
operation2.addDependency(operation1)??? //任務(wù)二依賴任務(wù)一
operation3.addDependency(operation2)??? //任務(wù)三依賴任務(wù)二
?
//5.創(chuàng)建隊(duì)列并加入任務(wù)
let queue= NSOperationQueue()
queue.addOperations([operation3,operation2, operation1], waitUntilFinished: false)
打印結(jié)果
?
2015-07-2821:24:28.622 test[19392:4637517] 下載圖片 - <NSThread:0x7fc10ad4d970>{number = 2, name = (null)}
?
2015-07-2821:24:29.622 test[19392:4637515] 打水印 - <NSThread:0x7fc10af20ef0>{number = 3, name = (null)}
?
2015-07-2821:24:30.627 test[19392:4637515] 上傳圖片 - <NSThread:0x7fc10af20ef0>{number = 3, name = (null)}
注意:不能添加相互依賴,會死鎖,比如 A依賴B,B依賴A。
可以使用removeDependency 來解除依賴關(guān)系。
可以在不同的隊(duì)列之間依賴,反正就是這個依賴是添加到任務(wù)身上的,和隊(duì)列沒關(guān)系。
其他方法
?
以上就是一些主要方法, 下面還有一些常用方法需要大家注意:
?
NSOperation
?
BOOLexecuting; //判斷任務(wù)是否正在執(zhí)行
?
BOOLfinished; //判斷任務(wù)是否完成
?
void(^completionBlock)(void); //用來設(shè)置完成后需要執(zhí)行的操作
?
-(void)cancel; //取消任務(wù)
?
-(void)waitUntilFinished; //阻塞當(dāng)前線程直到此任務(wù)執(zhí)行完畢
NSOperationQueue
?
NSUIntegeroperationCount; //獲取隊(duì)列的任務(wù)數(shù)
?
-(void)cancelAllOperations; //取消隊(duì)列中所有的任務(wù)
?
-(void)waitUntilAllOperationsAreFinished; //阻塞當(dāng)前線程直到此隊(duì)列中的所有任務(wù)執(zhí)行完畢
?
[queuesetSuspended:YES]; // 暫停queue
?
[queuesetSuspended:NO]; // 繼續(xù)queue
好啦,到這里差不多就講完了。當(dāng)然,我講的并不完整,可能有一些知識我并沒有講到,但作為常用方法,這些已經(jīng)足夠了。不過我在這里只是告訴你了一些方法的功能,只是怎么把他們用到合適的地方,就需要多多實(shí)踐了。下面我會說一些關(guān)于多線程的案例,是大家更加什么地了解。
?
其他用法
?
在這部分,我會說一些和多線程知識相關(guān)的案例,可能有些很簡單,大家早都知道的,不過因?yàn)檫@篇文章講的是多線程嘛,所以應(yīng)該盡可能的全面嘛。還有就是,我會盡可能的使用多種方法實(shí)現(xiàn),讓大家看看其中的區(qū)別。
?
線程同步
?
所謂線程同步就是為了防止多個線程搶奪同一個資源造成的數(shù)據(jù)安全問題,所采取的一種措施。當(dāng)然也有很多實(shí)現(xiàn)方法,請往下看:
?
互斥鎖 :給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。
?
OBJECTIVE-C
?
@synchronized(self){
? //需要執(zhí)行的代碼塊
}
SWIFT
?
objc_sync_enter(self)
//需要執(zhí)行的代碼塊
objc_sync_exit(self)
同步執(zhí)行 :我們可以使用多線程的知識,把多個線程都要執(zhí)行此段代碼添加到同一個串行隊(duì)列,這樣就實(shí)現(xiàn)了線程同步的概念。當(dāng)然這里可以使用 GCD 和 NSOperation 兩種方案,我都寫出來。
?
OBJECTIVE-C
?
?
//GCD
//需要一個全局變量queue,要讓所有線程的這個操作都加到一個queue中
dispatch_sync(queue,^{
??? NSInteger ticket = lastTicket;
??? [NSThread sleepForTimeInterval:0.1];
??? NSLog(@"%ld - %@",ticket,[NSThread currentThread]);
??? ticket -= 1;
??? lastTicket = ticket;
});
?
?
//NSOperation& NSOperationQueue
//重點(diǎn):1. 全局的 NSOperationQueue, 所有的操作添加到同一個queue中
//?????? 2. 設(shè)置 queue 的maxConcurrentOperationCount 為 1
//?????? 3. 如果后續(xù)操作需要Block中的結(jié)果,就需要調(diào)用每個操作的waitUntilFinished,阻塞當(dāng)前線程,一直等到當(dāng)前操作完成,才允許執(zhí)行后面的。waitUntilFinished 要在添加到隊(duì)列之后!
?
NSBlockOperation*operation = [NSBlockOperation blockOperationWithBlock:^{
??? NSInteger ticket = lastTicket;
??? [NSThread sleepForTimeInterval:1];
??? NSLog(@"%ld - %@",ticket,[NSThread currentThread]);
??? ticket -= 1;
??? lastTicket = ticket;
}];
?
[queueaddOperation:operation];
?
[operationwaitUntilFinished];
?
//后續(xù)要做的事
SWIFT
?
這里的 swift代碼,我就不寫了,因?yàn)槊烤涠家粯?#xff0c;只是語法不同而已,照著 OC 的代碼就能寫出 Swift 的。這篇文章已經(jīng)老長老長了,我就不浪費(fèi)篇幅了,又不是高中寫作文。
?
延遲執(zhí)行
?
所謂延遲執(zhí)行就是延時一段時間再執(zhí)行某段代碼。下面說一些常用方法。
?
perform
?
OBJECTIVE-C
?
// 3秒后自動調(diào)用self的run:方法,并且傳遞參數(shù):@"abc"
[selfperformSelector:@selector(run:) withObject:@"abc" afterDelay:3];
SWIFT
?
之前就已經(jīng)說過,Swift里去掉了這個方法。
GCD
?
可以使用 GCD 中的 dispatch_after 方法,OC 和 Swift 都可以使用,這里只寫 OC 的,Swift 的是一樣的。
?
OBJECTIVE-C
?
// 創(chuàng)建隊(duì)列
dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 設(shè)置延時,單位秒
doubledelay = 3;
?
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(delay * NSEC_PER_SEC)), queue, ^{
? // 3秒后需要執(zhí)行的任務(wù)
});
NSTimer
?
NSTimer 是iOS中的一個計時器類,除了延遲執(zhí)行還有很多用法,不過這里直說延遲執(zhí)行的用法。同樣只寫OC 版的,Swift 也是相同的。
?
OBJECTIVE-C
?
[NSTimerscheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:)userInfo:@"abc" repeats:NO];
單例模式
?
至于什么是單例模式,我也不多說,我只說說一般怎么實(shí)現(xiàn)。在 Objective-C 中,實(shí)現(xiàn)單例的方法已經(jīng)很具體了,雖然有別的方法,但是一般都是用一個標(biāo)準(zhǔn)的方法了,下面來看看。
?
OBJECTIVE-C
?
@interfaceTool : NSObject <NSCopying>
?
+(instancetype)sharedTool;
?
@end
?
@implementationTool
?
static id_instance;
?
+(instancetype)sharedTool {
??? static dispatch_once_t onceToken;
??? dispatch_once(&onceToken, ^{
??????? _instance = [[Tool alloc] init];
??? });
?
??? return _instance;
}
?
@end
這里之所以將單例模式,是因?yàn)槠渲杏玫搅?GCD 的 dispatch_once 方法。下面看 Swift 中的單例模式,在Swift中單例模式非常簡單!想知道怎么從 OC 那么復(fù)雜的方法變成下面的寫法的,請看這里
?
SWIFT
?
classTool: NSObject {
??? static let sharedTool = Tool()
?
??? // 私有化構(gòu)造方法,阻止其他對象使用這個類的默認(rèn)的'()'構(gòu)造方法
??? private override init() {}
}
從其他線程回到主線程的方法
?
我們都知道在其他線程操作完成后必須到主線程更新UI。所以,介紹完所有的多線程方案后,我們來看看有哪些方法可以回到主線程。
?
NSThread
?
//Objective-C
[selfperformSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
?
//Swift
//swift 取消了 performSelector 方法。
GCD
?
//Objective-C
dispatch_async(dispatch_get_main_queue(),^{
?
});
?
//Swift
dispatch_async(dispatch_get_main_queue(),{ () -> Void in
?
})
NSOperationQueue
?
//Objective-C
[[NSOperationQueuemainQueue] addOperationWithBlock:^{
?
}];
?
//Swift
NSOperationQueue.mainQueue().addOperationWithBlock{ () -> Void in
?
}
總結(jié)
?
好的吧,總算寫完了,純手敲6k多字,感動死我了。花了兩天,時間跨度有點(diǎn)大,所以可能有些地方上段不接下段或者有的地方不完整,如果你看著比較費(fèi)力或者有什么地方有問題,都可以在評論區(qū)告訴我,我會及時修改的。當(dāng)然啦,多線程的東西也不止這些,題目也就只是個題目,不要當(dāng)真。想要了解更多的東西,還得自己去網(wǎng)上挖掘相關(guān)資料。多看看官方文檔。實(shí)在是編不下去了,大家好好看~。對了,看我寫的這么賣力,不打賞的話得點(diǎn)個喜歡也是極好的。
?
更新:第一次放出來的時候,有很多地方有錯誤,很感謝有朋友提出來了。如果你看到有錯誤的地方,一定記得指出來,這樣對大家都有幫助。還有一點(diǎn)對初學(xué)者來說,遇到不懂的方法,最好的辦法就是查看官方文檔,那里是最準(zhǔn)確的,就算有幾個單詞不認(rèn)識,查一下就好了,不會影響對整體的理解。
(5)????同步,異步定義以及iOS中是如何實(shí)現(xiàn)同步的
同步:進(jìn)程之間的關(guān)系不是相互排斥臨界資源的關(guān)系,而是相互依賴的關(guān)系。進(jìn)一步的說明:就是前一個進(jìn)程的輸出作為后一個進(jìn)程的輸入,當(dāng)?shù)谝粋€進(jìn)程沒有輸出時第二個進(jìn)程必須等待。具有同步關(guān)系的一組并發(fā)進(jìn)程相互發(fā)送的信息稱為消息或事件。
其中并發(fā)又有偽并發(fā)和真并發(fā),偽并發(fā)是指單核處理器的并發(fā),真并發(fā)是指多核處理器的并發(fā)。
異步:異步和同步是相對的,同步就是順序執(zhí)行,執(zhí)行完一個再執(zhí)行下一個,需要等待、協(xié)調(diào)運(yùn)行。異步就是彼此獨(dú)立,在等待某事件的過程中繼續(xù)做自己的事,不需要等待這一事件完成后再工作。線程就是實(shí)現(xiàn)異步的一個方式。異步是讓調(diào)用方法的主線程不需要同步等待另一線程的完成,從而可以讓主線程干其它的事情。
?? 異步和多線程并不是一個同等關(guān)系,異步是最終目的,多線程只是我們實(shí)現(xiàn)異步的一種手段。異步是當(dāng)一個調(diào)用請求發(fā)送給被調(diào)用者,而調(diào)用者不用等待其結(jié)果的返回而可以做其它的事情。實(shí)現(xiàn)異步可以采用多線程技術(shù)或則交給另外的進(jìn)程來處理。
在IOS中我們一般情況下使用以下三種線程同步代碼方式:
?
第一種和第二種代碼同步的使用方法,一般情況下我們只需要使用NSLock和NSCondition申明2個屬性。然后給此屬性賦對應(yīng)的值。那么即可作為安全防控的線程手段。
?
同時也可以保證線程的資源安全。
?
1:NSLock方式
?
?
?
[xxxlocklock] //上鎖
?
同步代碼塊
?
[xxxlockunlock]//解鎖
?
?
?
2:NSCondition方式
?
[xxxConditionlock] //上鎖
?
同步代碼塊
?
[xxxConditionunlock]//解鎖
?
?
?
第三種方式:在使用synchronized的時候,括號中我們一般情況下只需要傳一個self即可。同步代碼塊 當(dāng)有線程進(jìn)去之后會把括號里面對象的鎖旗標(biāo)鎖上,其他線程會在外面等著 當(dāng)進(jìn)去的線程出去的時候會把鎖打開其余線程再進(jìn)一個。這樣才能保護(hù)線程放問資源的安全性。
?
3:@synchronized( 同一對象){
?
線程執(zhí)行代碼;
?
}
?
?
?
線程資源防控示例代碼:
?
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<spanstyle="font-size:10px;">-(void)sellTickets{
????
??? while (YES) {
??????? NSString *name = [NSThreadcurrentThread].name;
//?????? 同步代碼塊 當(dāng)有線程進(jìn)去之后會把括號里面對象的鎖旗標(biāo)鎖上,其他線程會在外面等著當(dāng)進(jìn)去的線程出去的時候會把鎖打開 其余線程再進(jìn)一個
//??????? @synchronized(self){
//??????? [self.myLock lock];
??????? [self.myCondition lock];
??????????? NSLog(@"%@開始賣%d號票",name,self.selledCount+1);
??????????? [NSThread sleepForTimeInterval:.2];
??????????? self.selledCount++;
??????????? NSLog(@"%@賣掉了%d號票,還剩%d張",name,self.selledCount,self.totalCount-self.selledCount);
//???????? [self.myLock unlock];
??????? [self.myCondition unlock];
?
??????? }
//??? }
????
}</span>
(6)????#import,#include,@class區(qū)別
1.
#include是C中用來引用文件的關(guān)鍵字,而#import是obj-c中用來代替include的關(guān)鍵字。#import可以確保同一個文件只能被導(dǎo)入一次,從而避免了使用#include容易引起的重復(fù)引用問題,即classA引用了classC,classB也引用了classC,而當(dāng)classD同時引用classA,classB的時候就會報重復(fù)引用的錯誤。
?
2.
#import""與#import<>:#import""實(shí)現(xiàn)從當(dāng)前工作目錄中找要導(dǎo)入的文件,如果沒有再到系統(tǒng)類庫中找,而#import<>是直接從系統(tǒng)類庫中找要導(dǎo)入的文件。
?
3.
#import與@class:
?
@class只是告訴編譯器,后面遇到的這個名稱是一個類名稱,至于這個類是如何實(shí)現(xiàn)的暫不用考慮。引入@class主要是用來解決引用死鎖--如果兩個類存在循環(huán)依賴關(guān)系,即A->B,B->A,如果用#import來相互包含,就會出現(xiàn)編譯錯誤:
Expectedspecifier-qualifier-list before ‘A’或者Expectedspecifier-qualifier-list before ‘B’。
?
一般情況下,在 .h文件中,只需要知道類的名字就可以了,所以用@class,而在 .m文件中通常需要知道類的成員變量即方法,所以要用#import來將類文件導(dǎo)進(jìn)來。
?
那為什么不在 .h文件中直接用#import來將類文件導(dǎo)入呢,因?yàn)槿绻麑?dǎo)入大量的頭文件,編譯器就會花大量的時間來編譯。
?
需要在 .h文件中用#import的情況:
1/如果有繼承關(guān)系的要用#import,如,A繼承B,需要在A中將B import進(jìn)來。
2/使用有category的類,需要在 .h文件中用#import將該類的category導(dǎo)進(jìn)來。
(7)????Ios中是否有多繼承,是如何實(shí)現(xiàn)的
我們都知道objectiveC不能像C++一樣支持多繼承,但是在OC的使用經(jīng)常會碰到需要使用多繼承的情況。例如,ClassA中有methodA,ClassB中methodB,而現(xiàn)在需要使用這兩個類中的方法。如何按照C++的編程思路,毫無疑問采用多繼承就搞定了,在OC就需要動動腦子了。
?
??????? 其實(shí)我們在學(xué)習(xí)設(shè)計模式的時候知道,多繼承的效率不高,而且采用組合的模式可以完全代替繼承模式。那么,這種思路完全可以用在OC中實(shí)現(xiàn)多繼承(或許OC拋棄多繼承,就是強(qiáng)迫我們使用更高效的組合設(shè)計模式吧!)。下面用實(shí)際的代碼來表示組合如何來代替多繼承。
?
?????? 現(xiàn)在ClassC需要繼承ClassA中methodA、ClassB中methodB,具體的代碼實(shí)現(xiàn)為:
?
//定義ClassA以及其methodA
@interfaceClassA : NSObject {
}
?
-(void)methodA;
?
@end
//定義ClassB以及其methodB
@interfaceClassB : NSObject {
}
?
-(void)methodB;
?
@end
//定義ClassC以及其需要的methodA,methodB
@interfaceClassC : NSObject {
? ClassA *a;
? ClassB *b;
}
?
-(id)initWithA:(ClassA*)A b:(ClassB *)B;
?
-(void)methodA;
-(void)methodB;
?
@end
?
//注意在ClassC的實(shí)現(xiàn)
?
@implementation? ClassC
?
-(id)initWithA:(ClassA*)A b:(ClassB *)B{
?
?????? a=[[ClassA alloc] initWithClassA:A];//[A copy];
?
?????? b=[[ClassB alloc] initWithClassB:B];//[B copy];
}
?
-(void)methodA{
?
????? [a methodA];
}
-(void)methodB{
?
????? [b methodB];
}
?
上面是采用組合的方式實(shí)現(xiàn)了多繼承的功能,解決了OC不能多繼承的語法。那么還有其他的方式來實(shí)現(xiàn)多繼承嗎?
?
雖然OC在語法上禁止類使用多繼承,但是在協(xié)議的遵守上卻允許使用多繼承。所以可以用協(xié)議來實(shí)現(xiàn)多繼承。但是協(xié)議只能提供接口,而沒有提供實(shí)現(xiàn)方式,如果只是想多繼承基類的接口,那么遵守多協(xié)議無疑是最好的方法,而既需要多繼承接口,又要多繼承其實(shí)現(xiàn),那么協(xié)議是無能為力了。多協(xié)議遵守比較簡單,具體的實(shí)現(xiàn)方式這里就不講了!
(8)????iOS中如何讓一個對象具有拷貝功能
若想令自己所寫的對象具有拷貝功能,則需實(shí)現(xiàn) NSCopying 協(xié)議。如果自定義的對象分為可變版本與不可變版本,那么就要同時實(shí)現(xiàn)NSCopying與 NSMutableCopying協(xié)議。
?
具體步驟:
?
需聲明該類遵從NSCopying 協(xié)議
實(shí)現(xiàn)NSCopying 協(xié)議。該協(xié)議只有一個方法:
?- (id)copyWithZone:(NSZone *)zone;
注意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法,其實(shí)真正需要實(shí)現(xiàn)的卻是 “copyWithZone” 方法。
?
至于如何重寫帶copy 關(guān)鍵字的 setter這個問題,
如果拋開本例來回答的話,如下:
?
-(void)setName:(NSString *)name {
??? //[_name release];
??? _name = [name copy];
}
?
(9)????單例的定義及實(shí)現(xiàn)
單例是全局的類實(shí)例,存放在全局內(nèi)存里,不能以任何方式復(fù)制,也不會被釋放。實(shí)例化的對象始終指向同一塊內(nèi)存。具體實(shí)現(xiàn)方式有兩種,線程鎖和GCD。代碼如下,如有錯誤歡迎大家批評指正:
線程鎖代碼:
static id_instance;
+ (User*)shareInstance {
??? @synchronized(self) {
??????? if (_instance == nil) {
??????????? _instance = [[User alloc] init];
??????? }
??? }
??? return _instance;
}
?
+(id)allocWithZone:(struct _NSZone *)zone {
??? @synchronized(self) {
??????? if (_instance == nil) {
??????????? _instance = [superallocWithZone:zone];
??????? }
??? }
??? return _instance;
}
?
-(id)copyWithZone:(NSZone *)zone? {
??? return _instance;
}
?
GCD代碼:
+(User*)shareInstance {
??? static dispatch_once_t onceToken;
??? dispatch_once(&onceToken, ^{
??????? //onceToken是GCD用來記錄是否執(zhí)行過,如果已經(jīng)執(zhí)行過就不再執(zhí)行(保證執(zhí)行一次)
???? ???_instance = [[User alloc] init];
??? });
??? return _instance;
}
?
+(id)allocWithZone:(struct _NSZone *)zone {
??? static dispatch_once_t onceToken;
??? dispatch_once(&onceToken, ^{
??????? //onceToken是GCD用來記錄是否執(zhí)行過,如果已經(jīng)執(zhí)行過就不再執(zhí)行(保證執(zhí)行一次)
??????? _instance = [super allocWithZone:zone];
??? });
??? return _instance;
}
?
-(id)copyWithZone:(NSZone *)zone? {
??? return _instance;
}
(10)? 定義一個標(biāo)準(zhǔn)宏MIN,實(shí)現(xiàn)返回輸入的兩個數(shù)中較小的數(shù)字
#define MIN(X,Y) ((X)>(Y)?(Y):(X))
define只會是純替換作用,所以X,Y均需要加括號,以防止X,Y為表達(dá)式的情況
(11)? 做過什么優(yōu)化程序的工作(這個是面試題目,不是筆試題目。)
1. 用ARC管理內(nèi)存
?
ARC(AutomaticReferenceCounting, 自動引用計數(shù))和iOS5一起發(fā)布,它避免了最常見的也就是經(jīng)常是由于我們忘記釋放內(nèi)存所造成的內(nèi)存泄露。它自動為你管理retain和release的過程,所以你就不必去手動干預(yù)了。忘掉代碼段結(jié)尾的release簡直像記得吃飯一樣簡單。而ARC會自動在底層為你做這些工作。除了幫你避免內(nèi)存泄露,ARC還可以幫你提高性能,它能保證釋放掉不再需要的對象的內(nèi)存。
?
現(xiàn)在所有的iOS程序都用ARC了,這條可以忽略。
?
?
?
2. 在正確的地方使用 reuseIdentifier
?
一個開發(fā)中常見的錯誤就是沒有給UITableViewCells, UICollectionViewCells,甚至是UITableViewHeaderFooterViews設(shè)置正確的reuseIdentifier。
?
為了性能最優(yōu)化,tableview用tableView:cellForRowAtIndexPath:為rows分配cells的時候,它的數(shù)據(jù)應(yīng)該重用自UITableViewCell。一個table view維持一個隊(duì)列的數(shù)據(jù)可重用的UITableViewCell對象。
?
不使用reuseIdentifier的話,每顯示一行table view就不得不設(shè)置全新的cell。這對性能的影響可是相當(dāng)大的,尤其會使app的滾動體驗(yàn)大打折扣。
?
自iOS6起,除了UICollectionView的cells和補(bǔ)充views,你也應(yīng)該在header和footerviews中使用reuseIdentifiers。
?
想要使用reuseIdentifiers的話,在一個table view中添加一個新的cell時在data source object中添加這個方法:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
staticNSString*CellIdentifier = @"Cell";?
UITableViewCell*cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierforIndexPath:indexPath];?
這個方法把那些已經(jīng)存在的cell從隊(duì)列中排除,或者在必要時使用先前注冊的nib或者class創(chuàng)造新的cell。如果沒有可重用的cell,你也沒有注冊一個class或者nib的話,這個方法返回nil。
?
?
?
3.盡量把views設(shè)置為透明
?
如果你有透明的Views你應(yīng)該設(shè)置它們的opaque屬性為YES。
?
原因是這會使系統(tǒng)用一個最優(yōu)的方式渲染這些views。這個簡單的屬性在IB或者代碼里都可以設(shè)定。
?
Apple的文檔對于為圖片設(shè)置透明屬性的描述是:
?
(opaque)這個屬性給渲染系統(tǒng)提供了一個如何處理這個view的提示。如果設(shè)為YES,渲染系統(tǒng)就認(rèn)為這個view是完全不透明的,這使得渲染系統(tǒng)優(yōu)化一些渲染過程和提高性能。如果設(shè)置為NO,渲染系統(tǒng)正常地和其它內(nèi)容組成這個View。默認(rèn)值是YES。
?
在相對比較靜止的畫面中,設(shè)置這個屬性不會有太大影響。然而當(dāng)這個view嵌在scroll view里邊,或者是一個復(fù)雜動畫的一部分,不設(shè)置這個屬性的話會在很大程度上影響app的性能。
?
你可以在模擬器中用Debug\ColorBlended Layers選項(xiàng)來發(fā)現(xiàn)哪些view沒有被設(shè)置為opaque。目標(biāo)就是,能設(shè)為opaque的就全設(shè)為opaque!
?
這里有一點(diǎn)需要注意,只要是有中文字符的Label,哪怕你設(shè)置成不透明,模擬器中這個Label依然會變紅,這個猜測是字符繪制的時候出的問題,這個目前沒找到好的解決方法。
?
?
?
4.避免過于龐大的XIB
?
iOS5中加入的Storyboards(分鏡)正在快速取代XIB。然而XIB在一些場景中仍然很有用。比如你的app需要適應(yīng)iOS5之前的設(shè)備,或者你有一個自定義的可重用的view,你就不可避免地要用到他們。
?
如果你不得不XIB的話,使他們盡量簡單。嘗試為每個Controller配置一個單獨(dú)的XIB,盡可能把一個View Controller的view層次結(jié)構(gòu)分散到單獨(dú)的XIB中去。
?
需要注意的是,當(dāng)你加載一個XIB的時候所有內(nèi)容都被放在了內(nèi)存里,包括任何圖片。如果有一個不會即刻用到的view,你這就是在浪費(fèi)寶貴的內(nèi)存資源了。Storyboards就是另一碼事兒了,storyboard僅在需要時實(shí)例化一個view controller.
?
當(dāng)家在XIB是,所有圖片都被chache,如果你在做OS X開發(fā)的話,聲音文件也是。Apple在相關(guān)文檔中的記述是:
?
當(dāng)你加載一個引用了圖片或者聲音資源的nib時,nib加載代碼會把圖片和聲音文件寫進(jìn)內(nèi)存。在OS X中,圖片和聲音資源被緩存在named cache中以便將來用到時獲取。在iOS中,僅圖片資源會被存進(jìn)named caches。取決于你所在的平臺,使用NSImage 或UIImage的imageNamed:方法來獲取圖片資源。
?
這個問題我深有體會,用xib寫的界面加載速度比直接用代碼寫的要慢好多。
?
?
?
5.不要阻塞主線程
?
永遠(yuǎn)不要使主線程承擔(dān)過多。因?yàn)閁IKit在主線程上做所有工作,渲染,管理觸摸反應(yīng),回應(yīng)輸入等都需要在它上面完成。
?
一直使用主線程的風(fēng)險就是如果你的代碼真的block了主線程,你的app會失去反應(yīng)。
?
大部分阻礙主進(jìn)程的情形是你的app在做一些牽涉到讀寫外部資源的I/O操作,比如存儲或者網(wǎng)絡(luò)。
?
你可以使用NSURLConnection異步地做網(wǎng)絡(luò)操作:
?
+(void)sendAsynchronousRequest:(NSURLRequest *)requestqueue:(NSOperationQueue*)queue completionHandler:(void (^)(NSURLResponse*, NSData*,NSError*))handler
?
或者使用像AFNetworking這樣的框架來異步地做這些操作。
?
如果你需要做其它類型的需要耗費(fèi)巨大資源的操作(比如時間敏感的計算或者存儲讀寫)那就用Grand Central Dispatch,或者NSOperation和 NSOperationQueues.
?
下面代碼是使用GCD的模板
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{?
??? // switch to a background thread andperform your expensive operation?
??? dispatch_async(dispatch_get_main_queue(),^{?
??????? // switch back to the main thread toupdate your UI?
??? });?
});?
發(fā)現(xiàn)代碼中有一個嵌套的dispatch_async嗎?這是因?yàn)槿魏蜺IKit相關(guān)的代碼需要在主線程上進(jìn)行。
?
?
?
6. 在Image Views中調(diào)整圖片大小
?
如果要在UIImageView中顯示一個來自bundle的圖片,你應(yīng)保證圖片的大小和UIImageView的大小相同。在運(yùn)行中縮放圖片是很耗費(fèi)資源的,特別是UIImageView嵌套在UIScrollView中的情況下。
?
如果圖片是從遠(yuǎn)端服務(wù)加載的你不能控制圖片大小,比如在下載前調(diào)整到合適大小的話,你可以在下載完成后,最好是用background thread,縮放一次,然后在UIImageView中使用縮放后的圖片。
?
?
?
7. 選擇正確的Collection
?
學(xué)會選擇對業(yè)務(wù)場景最合適的類或者對象是寫出能效高的代碼的基礎(chǔ)。當(dāng)處理collections時這句話尤其正確。
?
一些常見collection的總結(jié):
?
· Arrays:有序的一組值。使用index來lookup很快,使用value lookup很慢,插入/刪除很慢。
?
·Dictionaries: 存儲鍵值對。用鍵來查找比較快。
?
· Sets: 無序的一組值。用值來查找很快,插入/刪除很快。因?yàn)镾et用到了哈希,所以插入刪除查找速度比Array快很多
?
?
?
8. 打開gzip壓縮
?
大量app依賴于遠(yuǎn)端資源和第三方API,你可能會開發(fā)一個需要從遠(yuǎn)端下載XML, JSON, HTML或者其它格式的app。
?
問題是我們的目標(biāo)是移動設(shè)備,因此你就不能指望網(wǎng)絡(luò)狀況有多好。一個用戶現(xiàn)在還在edge網(wǎng)絡(luò),下一分鐘可能就切換到了3G。不論什么場景,你肯定不想讓你的用戶等太長時間。
?
減小文檔的一個方式就是在服務(wù)端和你的app中打開gzip。這對于文字這種能有更高壓縮率的數(shù)據(jù)來說會有更顯著的效用。
?
好消息是,iOS已經(jīng)在NSURLConnection中默認(rèn)支持了gzip壓縮,當(dāng)然AFNetworking這些基于它的框架亦然。像Google App Engine這些云服務(wù)提供者也已經(jīng)支持了壓縮輸出。
?
?
?
9. 重用和延遲加載(lazy load) Views
?
更多的view意味著更多的渲染,也就是更多的CPU和內(nèi)存消耗,對于那種嵌套了很多view在UIScrollView里邊的app更是如此。
?
這里我們用到的技巧就是模仿UITableView和UICollectionView的操作:不要一次創(chuàng)建所有的subview,而是當(dāng)需要時才創(chuàng)建,當(dāng)它們完成了使命,把他們放進(jìn)一個可重用的隊(duì)列中。
?
這樣的話你就只需要在滾動發(fā)生時創(chuàng)建你的views,避免了不劃算的內(nèi)存分配。
?
創(chuàng)建views的能效問題也適用于你app的其它方面。想象一下一個用戶點(diǎn)擊一個按鈕的時候需要呈現(xiàn)一個view的場景。有兩種實(shí)現(xiàn)方法:
?
1. 創(chuàng)建并隱藏這個view當(dāng)這個screen加載的時候,當(dāng)需要時顯示它;
?
2. 當(dāng)需要時才創(chuàng)建并展示。
?
每個方案都有其優(yōu)缺點(diǎn)。用第一種方案的話因?yàn)槟阈枰婚_始就創(chuàng)建一個view并保持它直到不再使用,這就會更加消耗內(nèi)存。然而這也會使你的app操作更敏感因?yàn)楫?dāng)用戶點(diǎn)擊按鈕的時候它只需要改變一下這個view的可見性。
?
第二種方案則相反-消耗更少內(nèi)存,但是會在點(diǎn)擊按鈕的時候比第一種稍顯卡頓。
?
?
?
10.Cache, Cache, 還是Cache!注意你的緩存
?
一個極好的原則就是,緩存所需要的,也就是那些不大可能改變但是需要經(jīng)常讀取的東西。
?
我們能緩存些什么呢?一些選項(xiàng)是,遠(yuǎn)端服務(wù)器的響應(yīng),圖片,甚至計算結(jié)果,比如UITableView的行高。
?
NSURLConnection默認(rèn)會緩存資源在內(nèi)存或者存儲中根據(jù)它所加載的HTTP Headers。你甚至可以手動創(chuàng)建一個NSURLRequest然后使它只加載緩存的值。
?
下面是一個可用的代碼段,你可以可以用它去為一個基本不會改變的圖片創(chuàng)建一個NSURLRequest并緩存它:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
+(NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {?
NSMutableURLRequest*request = [NSMutableURLRequest requestWithURL:url];?
request.cachePolicy= NSURLRequestReturnCacheDataElseLoad;// this will make sure the request alwaysreturns the cached image?
request.HTTPShouldHandleCookies= NO;?
request.HTTPShouldUsePipelining= YES;?
[requestaddValue:@"image/*"forHTTPHeaderField:@"Accept"];?
returnrequest;?
}?
注意你可以通過NSURLConnection 獲取一個URL request, AFNetworking也一樣的。這樣你就不必為采用這條tip而改變所有的networking代碼了。
?
如果你需要緩存其它不是HTTP Request的東西,你可以用NSCache。
?
NSCache和NSDictionary類似,不同的是系統(tǒng)回收內(nèi)存的時候它會自動刪掉它的內(nèi)容。
?
?
?
11.權(quán)衡渲染方法
?
在iOS中可以有很多方法做出漂亮的按鈕。你可以用整幅的圖片,可調(diào)大小的圖片,或者可以用CALayer, CoreGraphics甚至OpenGL來畫它們。當(dāng)然每個不同的解決方法都有不同的復(fù)雜程度和相應(yīng)的性能。
?
簡單來說,就是用事先渲染好的圖片更快一些,因?yàn)槿绱艘粊韎OS就免去了創(chuàng)建一個圖片再畫東西上去然后顯示在屏幕上的程序。問題是你需要把所有你需要用到的圖片放到app的bundle里面,這樣就增加了體積–這就是使用可變大小的圖片更好的地方了:你可以省去一些不必要的空間,也不需要再為不同的元素(比如按鈕)來做不同的圖。
?
然而,使用圖片也意味著你失去了使用代碼調(diào)整圖片的機(jī)動性,你需要一遍又一遍不斷地重做他們,這樣就很浪費(fèi)時間了,而且你如果要做一個動畫效果,雖然每幅圖只是一些細(xì)節(jié)的變化你就需要很多的圖片造成bundle大小的不斷增大。
?
總得來說,你需要權(quán)衡一下利弊,到底是要性能能還是要bundle保持合適的大小。
?
?
?
12.處理內(nèi)存警告
?
一旦系統(tǒng)內(nèi)存過低,iOS會通知所有運(yùn)行中app。在官方文檔中是這樣記述:
?
如果你的app收到了內(nèi)存警告,它就需要盡可能釋放更多的內(nèi)存。最佳方式是移除對緩存,圖片object和其他一些可以重創(chuàng)建的objects的strong references.
?
幸運(yùn)的是,UIKit提供了幾種收集低內(nèi)存警告的方法:
?
· 在app delegate中使用applicationDidReceiveMemoryWarning:的方法
?
· 在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
?
· 注冊并接收 UIApplicationDidReceiveMemoryWarningNotification的通知
?
一旦收到這類通知,你就需要釋放任何不必要的內(nèi)存使用。
?
例如,UIViewController的默認(rèn)行為是移除一些不可見的view,它的一些子類則可以補(bǔ)充這個方法,刪掉一些額外的數(shù)據(jù)結(jié)構(gòu)。一個有圖片緩存的app可以移除不在屏幕上顯示的圖片。
?
這樣對內(nèi)存警報的處理是很必要的,若不重視,你的app就可能被系統(tǒng)殺掉。
?
然而,當(dāng)你一定要確認(rèn)你所選擇的object是可以被重現(xiàn)創(chuàng)建的來釋放內(nèi)存。一定要在開發(fā)中用模擬器中的內(nèi)存提醒模擬去測試一下。
?
當(dāng)然,現(xiàn)在iOS設(shè)備運(yùn)行內(nèi)存越來越大,這一點(diǎn)很難出現(xiàn)了。
?
?
?
13.重用大開銷對象
?
一些objects的初始化很慢,比如NSDateFormatter和NSCalendar。然而,你又不可避免地需要使用它們,比如從JSON或者XML中解析數(shù)據(jù)。
?
想要避免使用這個對象的瓶頸你就需要重用他們,可以通過添加屬性到你的class里或者創(chuàng)建靜態(tài)變量來實(shí)現(xiàn)。
?
注意如果你要選擇第二種方法,對象會在你的app運(yùn)行時一直存在于內(nèi)存中,和單例(singleton)很相似。
?
下面的代碼說明了使用一個屬性來延遲加載一個date formatter. 第一次調(diào)用時它會創(chuàng)建一個新的實(shí)例,以后的調(diào)用則將返回已經(jīng)創(chuàng)建的實(shí)例:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
// inyour .h or inside a class extension?
@property(nonatomic, strong) NSDateFormatter *formatter;?
// insidethe implementation (.m)?
// Whenyou need, just use self.formatter?
-(NSDateFormatter *)formatter {?
??? if(!_formatter) {?
??????? _formatter = [[NSDateFormatter alloc]init];?
??????? _formatter.dateFormat = @"EEE MMMdd HH:mm:ss Z yyyy";// twitter date format?
??? }?
??? return _formatter;?
}?
還需要注意的是,其實(shí)設(shè)置一個NSDateFormatter的速度差不多是和創(chuàng)建新的一樣慢的!所以如果你的app需要經(jīng)常進(jìn)行日期格式處理的話,你會從這個方法中得到不小的性能提升。
?
?
?
14. 使用Sprite Sheets
?
Sprite sheet可以讓渲染速度加快,甚至比標(biāo)準(zhǔn)的屏幕渲染方法節(jié)省內(nèi)存。
?
?
?
15.避免反復(fù)處理數(shù)據(jù)
?
許多應(yīng)用需要從服務(wù)器加載功能所需的常為JSON或者XML格式的數(shù)據(jù)。在服務(wù)器端和客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要。在內(nèi)存中操作數(shù)據(jù)使它們滿足你的數(shù)據(jù)結(jié)構(gòu)是開銷很大的。
?
比如你需要數(shù)據(jù)來展示一個table view,最好直接從服務(wù)器取array結(jié)構(gòu)的數(shù)據(jù)以避免額外的中間數(shù)據(jù)結(jié)構(gòu)改變。
?
類似的,如果需要從特定key中取數(shù)據(jù),那么就使用鍵值對的dictionary。
?
這一點(diǎn)在處理大量數(shù)據(jù)的時候極為重要,用空間換時間的方法也許是極好的。
?
?
?
16.選擇正確的數(shù)據(jù)格式
?
從app和網(wǎng)絡(luò)服務(wù)間傳輸數(shù)據(jù)有很多方案,最常見的就是JSON和XML。你需要選擇對你的app來說最合適的一個。
?
解析JSON會比XML更快一些,JSON也通常更小更便于傳輸。從iOS5起有了官方內(nèi)建的JSON deserialization就更加方便使用了。
?
但是XML也有XML的好處,比如使用SAX來解析XML就像解析本地文件一樣,你不需像解析json一樣等到整個文檔下載完成才開始解析。當(dāng)你處理很大的數(shù)據(jù)的時候就會極大地減低內(nèi)存消耗和增加性能。
?
現(xiàn)在基本上都是JSON了。
?
?
?
17.正確設(shè)定背景圖片
?
在View里放背景圖片就像很多其它iOS編程一樣有很多方法:
?
使用UIColor的 colorWithPatternImage來設(shè)置背景色;
?
在view中添加一個UIImageView作為一個子View。
?
如果你使用全畫幅的背景圖,你就必須使用UIImageView因?yàn)閁IColor的colorWithPatternImage是用來創(chuàng)建小的重復(fù)的圖片作為背景的。這種情形下使用UIImageView可以節(jié)約不少的內(nèi)存:
?
// Youcould also achieve the same result in Interface Builder
?
UIImageView*backgroundView = [[UIImageView alloc] initWithImage:[UIImageimageNamed:@"background"]];
?
[self.viewaddSubview:backgroundView];
?
如果你用小圖平鋪來創(chuàng)建背景,你就需要用UIColor的colorWithPatternImage來做了,它會更快地渲染也不會花費(fèi)很多內(nèi)存:
?
self.view.backgroundColor= [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
?
?
?
18. 減少使用Web特性
?
UIWebView很有用,用它來展示網(wǎng)頁內(nèi)容或者創(chuàng)建UIKit很難做到的動畫效果是很簡單的一件事。
?
但是你可能有注意到UIWebView并不像驅(qū)動Safari的那么快。這是由于以JIT compilation為特色的Webkit的Nitro Engine的限制。
?
所以想要更高的性能你就要調(diào)整下你的HTML了。第一件要做的事就是盡可能移除不必要的JavaScript,避免使用過大的框架。能只用原生js就更好了。
?
另外,盡可能異步加載例如用戶行為統(tǒng)計script這種不影響頁面表達(dá)的javascript。
?
最后,永遠(yuǎn)要注意你使用的圖片,保證圖片的符合你使用的大小。使用Sprite sheet提高加載速度和節(jié)約內(nèi)存。
?
?
?
19. 設(shè)定Shadow Path
?
如何在一個View或者一個layer上加一個shadow呢,QuartzCore框架是很多開發(fā)者的選擇:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
UIView*view = [[UIView alloc] init];?
view.layer.shadowOffset= CGSizeMake(-1.0f, 1.0f);?
view.layer.shadowRadius= 5.0f;?
view.layer.shadowOpacity= 0.6;?
看起來很簡單,對吧。可是,壞消息是使用這個方法也有它的問題… Core Animation不得不先在后臺得出你的圖形并加好陰影然后才渲染,這開銷是很大的。
?
使用shadowPath的話就避免了這個問題:
?
view.layer.shadowPath= [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
?
使用shadowpath的話iOS就不必每次都計算如何渲染,它使用一個預(yù)先計算好的路徑。但問題是自己計算path的話可能在某些View中比較困難,且每當(dāng)view的frame變化的時候你都需要去updateshadow path.
?
我更喜歡用CALayer自己畫一個陰影出來,這樣可以設(shè)置陰影光柵化,節(jié)省大量CPU的運(yùn)算,壞處就是比較消耗內(nèi)存。因?yàn)槿绻oview的layer設(shè)置光柵化的話整個View都會變得模糊。
?
?
?
20. 優(yōu)化Table View
?
Tableview需要有很好的滾動性能,不然用戶會在滾動過程中發(fā)現(xiàn)動畫的瑕疵。
?
為了保證tableview平滑滾動,確保你采取了以下的措施:
?
· 正確使用reuseIdentifier來重用cells
?
· 盡量使所有的view opaque,包括cell自身
?
· 避免漸變,圖片縮放,后臺選人
?
· 緩存行高
?
· 如果cell內(nèi)現(xiàn)實(shí)的內(nèi)容來自web,使用異步加載,緩存請求結(jié)果
?
· 使用shadowPath來畫陰影
?
· 減少subviews的數(shù)量
?
· 盡量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后緩存結(jié)果
?
· 使用正確的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)
?
· 使用rowHeight, sectionFooterHeight和sectionHeaderHeight來設(shè)定固定的高,不要請求delegate
?
?
?
21.選擇正確的數(shù)據(jù)存儲選項(xiàng)
?
當(dāng)存儲大塊數(shù)據(jù)時你會怎么做?
?
你有很多選擇,比如:
?
· 使用NSUerDefaults
?
· 使用XML, JSON, 或者 plist
?
· 使用NSCoding存檔
?
· 使用類似SQLite的本地SQL數(shù)據(jù)庫
?
· 使用 Core Data
?
NSUserDefaults的問題是什么?雖然它很nice也很便捷,但是它只適用于小數(shù)據(jù),比如一些簡單的布爾型的設(shè)置選項(xiàng),再大點(diǎn)你就要考慮其它方式了
?
XML這種結(jié)構(gòu)化檔案呢?總體來說,你需要讀取整個文件到內(nèi)存里去解析,這樣是很不經(jīng)濟(jì)的。使用SAX又是一個很麻煩的事情。
?
NSCoding?不幸的是,它也需要讀寫文件,所以也有以上問題。
?
在這種應(yīng)用場景下,使用SQLite 或者 Core Data比較好。使用這些技術(shù)你用特定的查詢語句就能只加載你需要的對象。
?
在性能層面來講,SQLite和Core Data是很相似的。他們的不同在于具體使用方法。Core Data代表一個對象的graph model,但SQLite就是一個DBMS。Apple在一般情況下建議使用CoreData,但是如果你有理由不使用它,那么就去使用更加底層的SQLite吧。
?
如果你使用SQLite,你可以用FMDB(https://GitHub.com/ccgus/fmdb)這個庫來簡化SQLite的操作,這樣你就不用花很多經(jīng)歷了解SQLite的C API了。
?
?
?
23. 使用Autorelease Pool
?
NSAutoreleasePool負(fù)責(zé)釋放block中的autoreleased objects。一般情況下它會自動被UIKit調(diào)用。但是有些狀況下你也需要手動去創(chuàng)建它。
?
假如你創(chuàng)建很多臨時對象,你會發(fā)現(xiàn)內(nèi)存一直在減少直到這些對象被release的時候。這是因?yàn)橹挥挟?dāng)UIKit用光了autorelease pool的時候memory才會被釋放。好消息是你可以在你自己的@autoreleasepool里創(chuàng)建臨時的對象來避免這個行為:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
NSArray*urls = <# An array of file URLs #>;?
?? for(NSURL *url in urls) {?
??? @autoreleasepool {?
?????? NSError *error;?
?????? NSString *fileContents = [NSStringstringWithContentsOfURL:url encoding:NSUTF8StringEncodingerror:&error];?
?????? /* Process the string, creating andautoreleasing more objects. */??
??? }?
?? }?
這段代碼在每次遍歷后釋放所有autorelease對象
?
?
?
24. 選擇是否緩存圖片
?
常見的從bundle中加載圖片的方式有兩種,一個是用imageNamed,二是用imageWithContentsOfFile,第一種比較常見一點(diǎn)。
?
既然有兩種類似的方法來實(shí)現(xiàn)相同的目的,那么他們之間的差別是什么呢?
?
imageNamed的優(yōu)點(diǎn)是當(dāng)加載時會緩存圖片。imageNamed的文檔中這么說:這個方法用一個指定的名字在系統(tǒng)緩存中查找并返回一個圖片對象如果它存在的話。如果緩存中沒有找到相應(yīng)的圖片,這個方法從指定的文檔中加載然后緩存并返回這個對象。
?
相反的,imageWithContentsOfFile僅加載圖片。
?
下面的代碼說明了這兩種方法的用法:
?
UIImage*img = [UIImage imageNamed:@"myImage"];// caching
?
// or
?
UIImage*img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching
?
那么我們應(yīng)該如何選擇呢?
?
如果你要加載一個大圖片而且是一次性使用,那么就沒必要緩存這個圖片,用imageWithContentsOfFile足矣,這樣不會浪費(fèi)內(nèi)存來緩存它。
?
然而,在圖片反復(fù)重用的情況下imageNamed是一個好得多的選擇。
?
?
?
25. 避免日期格式轉(zhuǎn)換
?
如果你要用NSDateFormatter來處理很多日期格式,應(yīng)該小心以待。就像先前提到的,任何時候重用NSDateFormatters都是一個好的實(shí)踐。
?
然而,如果你需要更多速度,那么直接用C是一個好的方案。Sam Soffes有一個不錯的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)里面有一些可以用來解析ISO-8601日期字符串的代碼,簡單重寫一下就可以拿來用了。
?
嗯,直接用C來搞,看起來不錯了,但是你相信嗎,我們還有更好的方案!
?
如果你可以控制你所處理的日期格式,盡量選擇Unix時間戳。你可以方便地從時間戳轉(zhuǎn)換到NSDate:
?
-(NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
?
return[NSDatedateWithTimeIntervalSince1970:timestamp];
?
}
?
這樣會比用C來解析日期字符串還快!需要注意的是,許多web API會以微秒的形式返回時間戳,因?yàn)檫@種格式在javascript中更方便使用。記住用dateFromUnixTimestamp之前除以1000就好了。
?
?
?
轉(zhuǎn)載至:http://blog.csdn.net/youshaoduo/article/details/53841078
?
1. 用ARC管理內(nèi)存
?
ARC(AutomaticReferenceCounting, 自動引用計數(shù))和iOS5一起發(fā)布,它避免了最常見的也就是經(jīng)常是由于我們忘記釋放內(nèi)存所造成的內(nèi)存泄露。它自動為你管理retain和release的過程,所以你就不必去手動干預(yù)了。忘掉代碼段結(jié)尾的release簡直像記得吃飯一樣簡單。而ARC會自動在底層為你做這些工作。除了幫你避免內(nèi)存泄露,ARC還可以幫你提高性能,它能保證釋放掉不再需要的對象的內(nèi)存。
?
現(xiàn)在所有的iOS程序都用ARC了,這條可以忽略。
?
?
?
2. 在正確的地方使用 reuseIdentifier
?
一個開發(fā)中常見的錯誤就是沒有給UITableViewCells, UICollectionViewCells,甚至是UITableViewHeaderFooterViews設(shè)置正確的reuseIdentifier。
?
為了性能最優(yōu)化,tableview用tableView:cellForRowAtIndexPath:為rows分配cells的時候,它的數(shù)據(jù)應(yīng)該重用自UITableViewCell。一個table view維持一個隊(duì)列的數(shù)據(jù)可重用的UITableViewCell對象。
?
不使用reuseIdentifier的話,每顯示一行table view就不得不設(shè)置全新的cell。這對性能的影響可是相當(dāng)大的,尤其會使app的滾動體驗(yàn)大打折扣。
?
自iOS6起,除了UICollectionView的cells和補(bǔ)充views,你也應(yīng)該在header和footerviews中使用reuseIdentifiers。
?
想要使用reuseIdentifiers的話,在一個table view中添加一個新的cell時在data source object中添加這個方法:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
staticNSString*CellIdentifier = @"Cell";?
UITableViewCell*cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierforIndexPath:indexPath];?
這個方法把那些已經(jīng)存在的cell從隊(duì)列中排除,或者在必要時使用先前注冊的nib或者class創(chuàng)造新的cell。如果沒有可重用的cell,你也沒有注冊一個class或者nib的話,這個方法返回nil。
?
?
?
3.盡量把views設(shè)置為透明
?
如果你有透明的Views你應(yīng)該設(shè)置它們的opaque屬性為YES。
?
原因是這會使系統(tǒng)用一個最優(yōu)的方式渲染這些views。這個簡單的屬性在IB或者代碼里都可以設(shè)定。
?
Apple的文檔對于為圖片設(shè)置透明屬性的描述是:
?
(opaque)這個屬性給渲染系統(tǒng)提供了一個如何處理這個view的提示。如果設(shè)為YES,渲染系統(tǒng)就認(rèn)為這個view是完全不透明的,這使得渲染系統(tǒng)優(yōu)化一些渲染過程和提高性能。如果設(shè)置為NO,渲染系統(tǒng)正常地和其它內(nèi)容組成這個View。默認(rèn)值是YES。
?
在相對比較靜止的畫面中,設(shè)置這個屬性不會有太大影響。然而當(dāng)這個view嵌在scroll view里邊,或者是一個復(fù)雜動畫的一部分,不設(shè)置這個屬性的話會在很大程度上影響app的性能。
?
你可以在模擬器中用Debug\ColorBlended Layers選項(xiàng)來發(fā)現(xiàn)哪些view沒有被設(shè)置為opaque。目標(biāo)就是,能設(shè)為opaque的就全設(shè)為opaque!
?
這里有一點(diǎn)需要注意,只要是有中文字符的Label,哪怕你設(shè)置成不透明,模擬器中這個Label依然會變紅,這個猜測是字符繪制的時候出的問題,這個目前沒找到好的解決方法。
?
?
?
4.避免過于龐大的XIB
?
iOS5中加入的Storyboards(分鏡)正在快速取代XIB。然而XIB在一些場景中仍然很有用。比如你的app需要適應(yīng)iOS5之前的設(shè)備,或者你有一個自定義的可重用的view,你就不可避免地要用到他們。
?
如果你不得不XIB的話,使他們盡量簡單。嘗試為每個Controller配置一個單獨(dú)的XIB,盡可能把一個View Controller的view層次結(jié)構(gòu)分散到單獨(dú)的XIB中去。
?
需要注意的是,當(dāng)你加載一個XIB的時候所有內(nèi)容都被放在了內(nèi)存里,包括任何圖片。如果有一個不會即刻用到的view,你這就是在浪費(fèi)寶貴的內(nèi)存資源了。Storyboards就是另一碼事兒了,storyboard僅在需要時實(shí)例化一個view controller.
?
當(dāng)家在XIB是,所有圖片都被chache,如果你在做OS X開發(fā)的話,聲音文件也是。Apple在相關(guān)文檔中的記述是:
?
當(dāng)你加載一個引用了圖片或者聲音資源的nib時,nib加載代碼會把圖片和聲音文件寫進(jìn)內(nèi)存。在OS X中,圖片和聲音資源被緩存在named cache中以便將來用到時獲取。在iOS中,僅圖片資源會被存進(jìn)named caches。取決于你所在的平臺,使用NSImage 或UIImage的imageNamed:方法來獲取圖片資源。
?
這個問題我深有體會,用xib寫的界面加載速度比直接用代碼寫的要慢好多。
?
?
?
5.不要阻塞主線程
?
永遠(yuǎn)不要使主線程承擔(dān)過多。因?yàn)閁IKit在主線程上做所有工作,渲染,管理觸摸反應(yīng),回應(yīng)輸入等都需要在它上面完成。
?
一直使用主線程的風(fēng)險就是如果你的代碼真的block了主線程,你的app會失去反應(yīng)。
?
大部分阻礙主進(jìn)程的情形是你的app在做一些牽涉到讀寫外部資源的I/O操作,比如存儲或者網(wǎng)絡(luò)。
?
你可以使用NSURLConnection異步地做網(wǎng)絡(luò)操作:
?
+(void)sendAsynchronousRequest:(NSURLRequest *)requestqueue:(NSOperationQueue*)queue completionHandler:(void (^)(NSURLResponse*,NSData*, NSError*))handler
?
或者使用像AFNetworking這樣的框架來異步地做這些操作。
?
如果你需要做其它類型的需要耗費(fèi)巨大資源的操作(比如時間敏感的計算或者存儲讀寫)那就用Grand Central Dispatch,或者NSOperation和 NSOperationQueues.
?
下面代碼是使用GCD的模板
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{?
??? // switch to a background thread andperform your expensive operation?
??? dispatch_async(dispatch_get_main_queue(),^{?
??? ????// switch back to the main thread to updateyour UI?
??? });?
});?
發(fā)現(xiàn)代碼中有一個嵌套的dispatch_async嗎?這是因?yàn)槿魏蜺IKit相關(guān)的代碼需要在主線程上進(jìn)行。
?
?
?
6. 在Image Views中調(diào)整圖片大小
?
如果要在UIImageView中顯示一個來自bundle的圖片,你應(yīng)保證圖片的大小和UIImageView的大小相同。在運(yùn)行中縮放圖片是很耗費(fèi)資源的,特別是UIImageView嵌套在UIScrollView中的情況下。
?
如果圖片是從遠(yuǎn)端服務(wù)加載的你不能控制圖片大小,比如在下載前調(diào)整到合適大小的話,你可以在下載完成后,最好是用background thread,縮放一次,然后在UIImageView中使用縮放后的圖片。
?
?
?
7. 選擇正確的Collection
?
學(xué)會選擇對業(yè)務(wù)場景最合適的類或者對象是寫出能效高的代碼的基礎(chǔ)。當(dāng)處理collections時這句話尤其正確。
?
一些常見collection的總結(jié):
?
· Arrays:有序的一組值。使用index來lookup很快,使用value lookup很慢,插入/刪除很慢。
?
·Dictionaries: 存儲鍵值對。用鍵來查找比較快。
?
· Sets: 無序的一組值。用值來查找很快,插入/刪除很快。因?yàn)镾et用到了哈希,所以插入刪除查找速度比Array快很多
?
?
?
8. 打開gzip壓縮
?
大量app依賴于遠(yuǎn)端資源和第三方API,你可能會開發(fā)一個需要從遠(yuǎn)端下載XML, JSON, HTML或者其它格式的app。
?
問題是我們的目標(biāo)是移動設(shè)備,因此你就不能指望網(wǎng)絡(luò)狀況有多好。一個用戶現(xiàn)在還在edge網(wǎng)絡(luò),下一分鐘可能就切換到了3G。不論什么場景,你肯定不想讓你的用戶等太長時間。
?
減小文檔的一個方式就是在服務(wù)端和你的app中打開gzip。這對于文字這種能有更高壓縮率的數(shù)據(jù)來說會有更顯著的效用。
?
好消息是,iOS已經(jīng)在NSURLConnection中默認(rèn)支持了gzip壓縮,當(dāng)然AFNetworking這些基于它的框架亦然。像Google App Engine這些云服務(wù)提供者也已經(jīng)支持了壓縮輸出。
?
?
?
9. 重用和延遲加載(lazy load) Views
?
更多的view意味著更多的渲染,也就是更多的CPU和內(nèi)存消耗,對于那種嵌套了很多view在UIScrollView里邊的app更是如此。
?
這里我們用到的技巧就是模仿UITableView和UICollectionView的操作:不要一次創(chuàng)建所有的subview,而是當(dāng)需要時才創(chuàng)建,當(dāng)它們完成了使命,把他們放進(jìn)一個可重用的隊(duì)列中。
?
這樣的話你就只需要在滾動發(fā)生時創(chuàng)建你的views,避免了不劃算的內(nèi)存分配。
?
創(chuàng)建views的能效問題也適用于你app的其它方面。想象一下一個用戶點(diǎn)擊一個按鈕的時候需要呈現(xiàn)一個view的場景。有兩種實(shí)現(xiàn)方法:
?
1. 創(chuàng)建并隱藏這個view當(dāng)這個screen加載的時候,當(dāng)需要時顯示它;
?
2. 當(dāng)需要時才創(chuàng)建并展示。
?
每個方案都有其優(yōu)缺點(diǎn)。用第一種方案的話因?yàn)槟阈枰婚_始就創(chuàng)建一個view并保持它直到不再使用,這就會更加消耗內(nèi)存。然而這也會使你的app操作更敏感因?yàn)楫?dāng)用戶點(diǎn)擊按鈕的時候它只需要改變一下這個view的可見性。
?
第二種方案則相反-消耗更少內(nèi)存,但是會在點(diǎn)擊按鈕的時候比第一種稍顯卡頓。
?
?
?
10.Cache, Cache, 還是Cache!注意你的緩存
?
一個極好的原則就是,緩存所需要的,也就是那些不大可能改變但是需要經(jīng)常讀取的東西。
?
我們能緩存些什么呢?一些選項(xiàng)是,遠(yuǎn)端服務(wù)器的響應(yīng),圖片,甚至計算結(jié)果,比如UITableView的行高。
?
NSURLConnection默認(rèn)會緩存資源在內(nèi)存或者存儲中根據(jù)它所加載的HTTP Headers。你甚至可以手動創(chuàng)建一個NSURLRequest然后使它只加載緩存的值。
?
下面是一個可用的代碼段,你可以可以用它去為一個基本不會改變的圖片創(chuàng)建一個NSURLRequest并緩存它:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
+(NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {?
NSMutableURLRequest*request = [NSMutableURLRequest requestWithURL:url];?
request.cachePolicy= NSURLRequestReturnCacheDataElseLoad;// this will make sure the request alwaysreturns the cached image?
request.HTTPShouldHandleCookies= NO;?
request.HTTPShouldUsePipelining= YES;?
[requestaddValue:@"image/*"forHTTPHeaderField:@"Accept"];?
returnrequest;?
}?
注意你可以通過NSURLConnection 獲取一個URL request, AFNetworking也一樣的。這樣你就不必為采用這條tip而改變所有的networking代碼了。
?
如果你需要緩存其它不是HTTP Request的東西,你可以用NSCache。
?
NSCache和NSDictionary類似,不同的是系統(tǒng)回收內(nèi)存的時候它會自動刪掉它的內(nèi)容。
?
?
?
11.權(quán)衡渲染方法
?
在iOS中可以有很多方法做出漂亮的按鈕。你可以用整幅的圖片,可調(diào)大小的圖片,或者可以用CALayer, CoreGraphics甚至OpenGL來畫它們。當(dāng)然每個不同的解決方法都有不同的復(fù)雜程度和相應(yīng)的性能。
?
簡單來說,就是用事先渲染好的圖片更快一些,因?yàn)槿绱艘粊韎OS就免去了創(chuàng)建一個圖片再畫東西上去然后顯示在屏幕上的程序。問題是你需要把所有你需要用到的圖片放到app的bundle里面,這樣就增加了體積–這就是使用可變大小的圖片更好的地方了:你可以省去一些不必要的空間,也不需要再為不同的元素(比如按鈕)來做不同的圖。
?
然而,使用圖片也意味著你失去了使用代碼調(diào)整圖片的機(jī)動性,你需要一遍又一遍不斷地重做他們,這樣就很浪費(fèi)時間了,而且你如果要做一個動畫效果,雖然每幅圖只是一些細(xì)節(jié)的變化你就需要很多的圖片造成bundle大小的不斷增大。
?
總得來說,你需要權(quán)衡一下利弊,到底是要性能能還是要bundle保持合適的大小。
?
?
?
12.處理內(nèi)存警告
?
一旦系統(tǒng)內(nèi)存過低,iOS會通知所有運(yùn)行中app。在官方文檔中是這樣記述:
?
如果你的app收到了內(nèi)存警告,它就需要盡可能釋放更多的內(nèi)存。最佳方式是移除對緩存,圖片object和其他一些可以重創(chuàng)建的objects的strong references.
?
幸運(yùn)的是,UIKit提供了幾種收集低內(nèi)存警告的方法:
?
· 在app delegate中使用applicationDidReceiveMemoryWarning:的方法
?
· 在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
?
· 注冊并接收 UIApplicationDidReceiveMemoryWarningNotification的通知
?
一旦收到這類通知,你就需要釋放任何不必要的內(nèi)存使用。
?
例如,UIViewController的默認(rèn)行為是移除一些不可見的view,它的一些子類則可以補(bǔ)充這個方法,刪掉一些額外的數(shù)據(jù)結(jié)構(gòu)。一個有圖片緩存的app可以移除不在屏幕上顯示的圖片。
?
這樣對內(nèi)存警報的處理是很必要的,若不重視,你的app就可能被系統(tǒng)殺掉。
?
然而,當(dāng)你一定要確認(rèn)你所選擇的object是可以被重現(xiàn)創(chuàng)建的來釋放內(nèi)存。一定要在開發(fā)中用模擬器中的內(nèi)存提醒模擬去測試一下。
?
當(dāng)然,現(xiàn)在iOS設(shè)備運(yùn)行內(nèi)存越來越大,這一點(diǎn)很難出現(xiàn)了。
?
?
?
13.重用大開銷對象
?
一些objects的初始化很慢,比如NSDateFormatter和NSCalendar。然而,你又不可避免地需要使用它們,比如從JSON或者XML中解析數(shù)據(jù)。
?
想要避免使用這個對象的瓶頸你就需要重用他們,可以通過添加屬性到你的class里或者創(chuàng)建靜態(tài)變量來實(shí)現(xiàn)。
?
注意如果你要選擇第二種方法,對象會在你的app運(yùn)行時一直存在于內(nèi)存中,和單例(singleton)很相似。
?
下面的代碼說明了使用一個屬性來延遲加載一個date formatter. 第一次調(diào)用時它會創(chuàng)建一個新的實(shí)例,以后的調(diào)用則將返回已經(jīng)創(chuàng)建的實(shí)例:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
// inyour .h or inside a class extension?
@property(nonatomic, strong) NSDateFormatter *formatter;?
// insidethe implementation (.m)?
// Whenyou need, just use self.formatter?
-(NSDateFormatter *)formatter {?
??? if(!_formatter) {?
??????? _formatter = [[NSDateFormatter alloc]init];?
??????? _formatter.dateFormat = @"EEE MMMdd HH:mm:ss Z yyyy";// twitter date format?
??? }?
??? return _formatter;?
}?
還需要注意的是,其實(shí)設(shè)置一個NSDateFormatter的速度差不多是和創(chuàng)建新的一樣慢的!所以如果你的app需要經(jīng)常進(jìn)行日期格式處理的話,你會從這個方法中得到不小的性能提升。
?
?
?
14. 使用Sprite Sheets
?
Spritesheet可以讓渲染速度加快,甚至比標(biāo)準(zhǔn)的屏幕渲染方法節(jié)省內(nèi)存。
?
?
?
15.避免反復(fù)處理數(shù)據(jù)
?
許多應(yīng)用需要從服務(wù)器加載功能所需的常為JSON或者XML格式的數(shù)據(jù)。在服務(wù)器端和客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要。在內(nèi)存中操作數(shù)據(jù)使它們滿足你的數(shù)據(jù)結(jié)構(gòu)是開銷很大的。
?
比如你需要數(shù)據(jù)來展示一個table view,最好直接從服務(wù)器取array結(jié)構(gòu)的數(shù)據(jù)以避免額外的中間數(shù)據(jù)結(jié)構(gòu)改變。
?
類似的,如果需要從特定key中取數(shù)據(jù),那么就使用鍵值對的dictionary。
?
這一點(diǎn)在處理大量數(shù)據(jù)的時候極為重要,用空間換時間的方法也許是極好的。
?
?
?
16.選擇正確的數(shù)據(jù)格式
?
從app和網(wǎng)絡(luò)服務(wù)間傳輸數(shù)據(jù)有很多方案,最常見的就是JSON和XML。你需要選擇對你的app來說最合適的一個。
?
解析JSON會比XML更快一些,JSON也通常更小更便于傳輸。從iOS5起有了官方內(nèi)建的JSON deserialization就更加方便使用了。
?
但是XML也有XML的好處,比如使用SAX來解析XML就像解析本地文件一樣,你不需像解析json一樣等到整個文檔下載完成才開始解析。當(dāng)你處理很大的數(shù)據(jù)的時候就會極大地減低內(nèi)存消耗和增加性能。
?
現(xiàn)在基本上都是JSON了。
?
?
?
17.正確設(shè)定背景圖片
?
在View里放背景圖片就像很多其它iOS編程一樣有很多方法:
?
使用UIColor的 colorWithPatternImage來設(shè)置背景色;
?
在view中添加一個UIImageView作為一個子View。
?
如果你使用全畫幅的背景圖,你就必須使用UIImageView因?yàn)閁IColor的colorWithPatternImage是用來創(chuàng)建小的重復(fù)的圖片作為背景的。這種情形下使用UIImageView可以節(jié)約不少的內(nèi)存:
?
// Youcould also achieve the same result in Interface Builder
?
UIImageView*backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
?
[self.viewaddSubview:backgroundView];
?
如果你用小圖平鋪來創(chuàng)建背景,你就需要用UIColor的colorWithPatternImage來做了,它會更快地渲染也不會花費(fèi)很多內(nèi)存:
?
self.view.backgroundColor= [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
?
?
?
18. 減少使用Web特性
?
UIWebView很有用,用它來展示網(wǎng)頁內(nèi)容或者創(chuàng)建UIKit很難做到的動畫效果是很簡單的一件事。
?
但是你可能有注意到UIWebView并不像驅(qū)動Safari的那么快。這是由于以JIT compilation為特色的Webkit的Nitro Engine的限制。
?
所以想要更高的性能你就要調(diào)整下你的HTML了。第一件要做的事就是盡可能移除不必要的JavaScript,避免使用過大的框架。能只用原生js就更好了。
?
另外,盡可能異步加載例如用戶行為統(tǒng)計script這種不影響頁面表達(dá)的javascript。
?
最后,永遠(yuǎn)要注意你使用的圖片,保證圖片的符合你使用的大小。使用Sprite sheet提高加載速度和節(jié)約內(nèi)存。
?
?
?
19. 設(shè)定Shadow Path
?
如何在一個View或者一個layer上加一個shadow呢,QuartzCore框架是很多開發(fā)者的選擇:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
UIView*view = [[UIView alloc] init];?
view.layer.shadowOffset= CGSizeMake(-1.0f, 1.0f);?
view.layer.shadowRadius= 5.0f;?
view.layer.shadowOpacity= 0.6;?
看起來很簡單,對吧。可是,壞消息是使用這個方法也有它的問題… Core Animation不得不先在后臺得出你的圖形并加好陰影然后才渲染,這開銷是很大的。
?
使用shadowPath的話就避免了這個問題:
?
view.layer.shadowPath= [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
?
使用shadowpath的話iOS就不必每次都計算如何渲染,它使用一個預(yù)先計算好的路徑。但問題是自己計算path的話可能在某些View中比較困難,且每當(dāng)view的frame變化的時候你都需要去updateshadow path.
?
我更喜歡用CALayer自己畫一個陰影出來,這樣可以設(shè)置陰影光柵化,節(jié)省大量CPU的運(yùn)算,壞處就是比較消耗內(nèi)存。因?yàn)槿绻oview的layer設(shè)置光柵化的話整個View都會變得模糊。
?
?
?
20. 優(yōu)化Table View
?
Table view需要有很好的滾動性能,不然用戶會在滾動過程中發(fā)現(xiàn)動畫的瑕疵。
?
為了保證tableview平滑滾動,確保你采取了以下的措施:
?
· 正確使用reuseIdentifier來重用cells
?
· 盡量使所有的view opaque,包括cell自身
?
· 避免漸變,圖片縮放,后臺選人
?
· 緩存行高
?
· 如果cell內(nèi)現(xiàn)實(shí)的內(nèi)容來自web,使用異步加載,緩存請求結(jié)果
?
· 使用shadowPath來畫陰影
?
· 減少subviews的數(shù)量
?
· 盡量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后緩存結(jié)果
?
· 使用正確的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)
?
· 使用rowHeight, sectionFooterHeight和sectionHeaderHeight來設(shè)定固定的高,不要請求delegate
?
?
?
21.選擇正確的數(shù)據(jù)存儲選項(xiàng)
?
當(dāng)存儲大塊數(shù)據(jù)時你會怎么做?
?
你有很多選擇,比如:
?
· 使用NSUerDefaults
?
· 使用XML, JSON, 或者 plist
?
· 使用NSCoding存檔
?
· 使用類似SQLite的本地SQL數(shù)據(jù)庫
?
· 使用Core Data
?
NSUserDefaults的問題是什么?雖然它很nice也很便捷,但是它只適用于小數(shù)據(jù),比如一些簡單的布爾型的設(shè)置選項(xiàng),再大點(diǎn)你就要考慮其它方式了
?
XML這種結(jié)構(gòu)化檔案呢?總體來說,你需要讀取整個文件到內(nèi)存里去解析,這樣是很不經(jīng)濟(jì)的。使用SAX又是一個很麻煩的事情。
?
NSCoding?不幸的是,它也需要讀寫文件,所以也有以上問題。
?
在這種應(yīng)用場景下,使用SQLite 或者 Core Data比較好。使用這些技術(shù)你用特定的查詢語句就能只加載你需要的對象。
?
在性能層面來講,SQLite和Core Data是很相似的。他們的不同在于具體使用方法。Core Data代表一個對象的graph model,但SQLite就是一個DBMS。Apple在一般情況下建議使用CoreData,但是如果你有理由不使用它,那么就去使用更加底層的SQLite吧。
?
如果你使用SQLite,你可以用FMDB(https://GitHub.com/ccgus/fmdb)這個庫來簡化SQLite的操作,這樣你就不用花很多經(jīng)歷了解SQLite的C API了。
?
?
?
23. 使用Autorelease Pool
?
NSAutoreleasePool負(fù)責(zé)釋放block中的autoreleased objects。一般情況下它會自動被UIKit調(diào)用。但是有些狀況下你也需要手動去創(chuàng)建它。
?
假如你創(chuàng)建很多臨時對象,你會發(fā)現(xiàn)內(nèi)存一直在減少直到這些對象被release的時候。這是因?yàn)橹挥挟?dāng)UIKit用光了autorelease pool的時候memory才會被釋放。好消息是你可以在你自己的@autoreleasepool里創(chuàng)建臨時的對象來避免這個行為:
?
[objc]view plain copy? 在CODE上查看代碼片派生到我的代碼片
NSArray*urls = <# An array of file URLs #>;?
?? for(NSURL *url in urls) {?
??? @autoreleasepool {?
?????? NSError *error;?
?????? NSString *fileContents = [NSStringstringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];?
?????? /* Process the string, creating andautoreleasing more objects. */??
??? }?
?? }?
這段代碼在每次遍歷后釋放所有autorelease對象
?
?
?
24. 選擇是否緩存圖片
?
常見的從bundle中加載圖片的方式有兩種,一個是用imageNamed,二是用imageWithContentsOfFile,第一種比較常見一點(diǎn)。
?
既然有兩種類似的方法來實(shí)現(xiàn)相同的目的,那么他們之間的差別是什么呢?
?
imageNamed的優(yōu)點(diǎn)是當(dāng)加載時會緩存圖片。imageNamed的文檔中這么說:這個方法用一個指定的名字在系統(tǒng)緩存中查找并返回一個圖片對象如果它存在的話。如果緩存中沒有找到相應(yīng)的圖片,這個方法從指定的文檔中加載然后緩存并返回這個對象。
?
相反的,imageWithContentsOfFile僅加載圖片。
?
下面的代碼說明了這兩種方法的用法:
?
UIImage*img = [UIImage imageNamed:@"myImage"];// caching
?
// or
?
UIImage*img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching
?
那么我們應(yīng)該如何選擇呢?
?
如果你要加載一個大圖片而且是一次性使用,那么就沒必要緩存這個圖片,用imageWithContentsOfFile足矣,這樣不會浪費(fèi)內(nèi)存來緩存它。
?
然而,在圖片反復(fù)重用的情況下imageNamed是一個好得多的選擇。
?
?
?
25. 避免日期格式轉(zhuǎn)換
?
如果你要用NSDateFormatter來處理很多日期格式,應(yīng)該小心以待。就像先前提到的,任何時候重用NSDateFormatters都是一個好的實(shí)踐。
?
然而,如果你需要更多速度,那么直接用C是一個好的方案。Sam Soffes有一個不錯的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)里面有一些可以用來解析ISO-8601日期字符串的代碼,簡單重寫一下就可以拿來用了。
?
嗯,直接用C來搞,看起來不錯了,但是你相信嗎,我們還有更好的方案!
?
如果你可以控制你所處理的日期格式,盡量選擇Unix時間戳。你可以方便地從時間戳轉(zhuǎn)換到NSDate:
?
-(NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
?
return[NSDatedateWithTimeIntervalSince1970:timestamp];
?
}
?
這樣會比用C來解析日期字符串還快!需要注意的是,許多web API會以微秒的形式返回時間戳,因?yàn)檫@種格式在javascript中更方便使用。記住用dateFromUnixTimestamp之前除以1000就好了。
(12)? “https:www|.baidu.com”輸出|前后兩部分的字符串
根據(jù)字符串中的某個字符(A)來分割字符串
//3.分隔字符串
NSString*string =@"sdfsfsfsAdfsdf";
???
NSArray*array = [string componentsSeparatedByString:@"A"]; //從字符A中分隔成2個元素的數(shù)組
NSLog(@"array:%@",array); //結(jié)果是adfsfsfs和dfsdf
?
總結(jié)
以上是生活随笔為你收集整理的iOS面试题目及答案总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Unity Shader 实现透明护盾效
- 下一篇: 产品经理 - 路漫漫其修远兮