1.INTERVIEW 共勉
2.iOS developers 方向
3.INTERVIEW QUESTION
1.深copy和淺copy
-
淺拷貝:
1.對內(nèi)存地址的復制,讓目標對象指針和源對象指向同一片內(nèi)存空間.
2.內(nèi)存銷毀的時候,指向這片空間的指針需要重新定義才可以使用,要不然會成為野指針
3.拷貝指向原來對象的指針,使原對象的引用計數(shù)加+1
4.相當于創(chuàng)建了一個指向原對象的新指針,并沒有創(chuàng)建一個新的對象.
深拷貝:
1.拷貝對象的具體內(nèi)容,而內(nèi)存地址是自主分配的
2.拷貝結(jié)束之后,兩個對象存在的值是相同的,內(nèi)存地址是不一樣的
3.兩個對象沒有任何關(guān)系本質(zhì)區(qū)別:
1.深拷貝是內(nèi)容拷貝,淺拷貝是指針拷貝
2.是否有新的內(nèi)存地址
3.是否影響內(nèi)存地址的引用計數(shù).案例一
NSString * str1 = @"copyStr";
NSMutableString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"str1:%p--%@",str1,str1);
NSLog(@"str1:%p--%@",str2,str2);
NSLog(@"str1:%p--%@",str3,str3);
2018-04-14 14:50:54.117652+0800 MutyCopy-Copy[2644:63575] str1:0x109a48068--copyStr
2018-04-14 14:50:54.117885+0800 MutyCopy-Copy[2644:63575] str1:0x109a48068--copyStr
2018-04-14 14:50:54.118010+0800 MutyCopy-Copy[2644:63575] str1:0x600000259a40--copyStr
1.str1,str2地址相同,而Str3地址不同
2.NSString的copy是淺拷貝,copy返回的對象是不可變對象
3.mutablecopy是深拷貝
*案例二:
NSMutableString * str1 = [NSMutableString stringWithString:@"mutableStr"];
NSMutableString * str2 = [str1 copy];
NSMutableString * str3 = [str1 mutableCopy];
NSLog(@"str:%p-----%@",str1,str1);
NSLog(@"str:%p-----%@",str2,str2);
NSLog(@"str:%p-----%@",str3,str3);
2018-04-14 15:04:50.092820+0800 MutyCopy-Copy[2685:70866] str:0x60000025b210-----mutableStr
2018-04-14 15:04:50.093059+0800 MutyCopy-Copy[2685:70866] str:0x60000022ca40-----mutableStr
2018-04-14 15:04:50.093217+0800 MutyCopy-Copy[2685:70866] str:0x60000025b540-----mutableStr
1.str1,str2,str3地址都不同
2.NSMutableString對象copy與mutableCopy都是深拷貝
3.copy返回的對象是不可變對象
4.2 iOS程序的啟動過程
- 首先找到程序入口,執(zhí)行main函數(shù)
- main -->> UIApplicationMain
- 創(chuàng)建UIApplication對象
- 創(chuàng)建UIApplication的代理對象,給UIApplication對象代理屬性賦值
- 開啟主運行循環(huán),作用接收事件,讓程序一直運行
- 加載info.plist,判斷有沒有指定main.storyboard,如果指定就去加載.
4.3 loadView
- 什么時候被調(diào)用?
每次訪問VC的view而且view為nil,loadView方法被調(diào)用 - 作用
loadView方法是用來負責創(chuàng)建VC的view. - 默認實現(xiàn)是怎樣的?
默認實現(xiàn)即[spuer loadView]
1.它會先去查找與UIViewController相關(guān)聯(lián)的xib文件,通過加載xib文件來創(chuàng)建VC的view.
2.如果在初始化VC指定了xib文件名,就會根據(jù)傳入的xib文件名加載對應的xib文件.如果沒有明顯xib文件名,就會加載跟自己同名的xib文件.
3.如果沒有找到關(guān)聯(lián)的xib文件,就會創(chuàng)建一個空白的UIView,然后賦值給VC的view屬性
4.4 單例模式
4.5 多線程
進程
1.進程是指系統(tǒng)中正在運行的一個應用程序
2.每個進程之間是獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi).線程
1.1個進程要想執(zhí)行任務,必須得有線程,每1個進程至少要有1條線程
2.線程是進程的基本執(zhí)行單元
3.一個進程的素有任務都在線程中執(zhí)行多線程
1.1個進程中可以開啟多條線程,每條線程可以并行(同時)執(zhí)行不同的任務
2.進程--工廠,線程--工廠工人
3.多線程可以提高程序的執(zhí)行效率
比如,我們同時開啟2條線程下載文件A,文件B.
- 多線程的原理
1.同一時間,CPU只能處理1條線程,只有1條線程在工作
2.多線程并發(fā)執(zhí)行,是CPU快速地在多條線程之間調(diào)度切換
注意:如果線程非常非常多,會發(fā)生什么情況?
cpu會在多個多線程之間進行調(diào)度,消耗大量的CPU資源.這樣的話,每條線程被調(diào)度執(zhí)行的頻次會降低,線程執(zhí)行效率降低.
多線程的優(yōu)缺點
1.優(yōu)點:
1.1.能適當?shù)奶岣叱绦虻膱?zhí)行效率
1.2.能適當調(diào)高資源利用率
2.缺點:
2.1.開啟線程需要占用一定的內(nèi)存空間,如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
2.2線程越多,CPU在調(diào)度線程上的開銷就越大.多線程應用
1.什么是主線程?
一個iOS程序運行后,默認會開啟1條線程 ,稱為主線程
2.主線程的主要作用?
2.1.顯示/刷新UI界面
2.2.處理UI事件
3.主線程使用注意?
3.1.別將比較耗時的操作放到主線程中
3.2.耗時操作會卡住主線程,嚴重影響UI的流暢度
4.多線程實現(xiàn)技術(shù)方案?
pthread,NSThread,GCD,NSOperation四中方案.
4.6 NSThread
- 創(chuàng)建,啟動線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFun) object:nil];
[thread start];
線程一啟動,就會告訴CPU準別就緒,可以隨時接受CPU調(diào)度.CPU調(diào)度當前線程之后,就會在線程thread中執(zhí)行self的run方法
-
主線程用法
- 其他方式創(chuàng)建線程
創(chuàng)建線程后自動啟動線程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
隱式創(chuàng)建并啟動線程
[self performSelectorInBackground:@selector(run) withObject:nil];
- 線程狀態(tài)
1.啟動線程,start.就緒狀態(tài)-->>運行狀態(tài).當新廠任務執(zhí)行完畢,自動進入死亡狀態(tài)
2.阻塞(暫停)線程,進入阻塞狀態(tài)
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
3.強制停止線程
進入死亡狀態(tài)
注意:一旦線程停止了,就不能再次開啟任務.
- 多線程的安全隱患
1.資源共享
一塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源.當多線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題.
2.如圖,
如果,多個線程訪問同一塊資源的話,會造成數(shù)據(jù)錯亂的.
我們應該怎么解決呢?
image
3.如圖,
線程A和線程B同時訪問資源變量Integer,
為了防止搶奪資源,
線程A在讀取資源變量Integer之前先加一把鎖,
然后讀取Integer的數(shù)據(jù)并在線程A中完成數(shù)據(jù)操作(17+1=18),
然后把數(shù)據(jù)寫入Integer中,
最后開鎖Unlock.在線程A對Integer操作的過程中,
線程B是無權(quán)訪問Integer的,
只有線程A_Unlock后,線程B才可以訪問資源變量Integer.
4.互斥鎖使用格式
@synchronized(self){//需要鎖定的代碼}
注意: 鎖定1分代碼只用1把鎖,用多把鎖是無效的
5.互斥鎖的優(yōu)缺點
互斥鎖的使用前提:多條線程搶奪同一塊資源
優(yōu)點:能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點:需要消耗大量的CPU
6.nonatomic和atomic
atomic:
原子屬性,為setter方法加鎖(默認就是atomic)
線程安全,需要消耗大量的資源
nonatomic:
非原子屬性,不會為setter方法加鎖
非線程安全,適合內(nèi)存小的移動設備
4.7 GCD
- 什么是GCD?
全程Grand Central Dispatch,中樞調(diào)度器
純C語言,提供了非常多強大的函數(shù) - GCD的優(yōu)勢
1.GCD是蘋果公司為多核的并行運算提出的解決方案
2.GCD會自動利用更多的CPU內(nèi)核
3.GCD自動管理線程的生命周期(創(chuàng)建線程,調(diào)度任務,銷毀線程) - GCD有2個核心概念
1.任務:執(zhí)行什么操作
2.隊列;用來存放任務 -
任務和隊列
1.執(zhí)行任務
GCD中有2個用來執(zhí)行任務的函數(shù)
1.1用同步的方式執(zhí)行任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊列
block:任務
1.2用異步的方式執(zhí)行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
1.3同步和異步的區(qū)別
同步:只能在當前線程中執(zhí)行任務,不具備開啟新線程的能力
異步:可以再新的線程中執(zhí)行任務,具備開啟新線程的能力
- 隊列的類型
GCD的隊列可以分為2大類型
并發(fā)隊列:可以讓多個任務并發(fā)執(zhí)行(并發(fā)功能只能在異步函數(shù)下才有效)
串行隊列:讓任務一個接著一個地執(zhí)行 -
容易混淆的術(shù)語
有4個術(shù)語比較容易混淆:
同步,異步,并發(fā),串行
注意: 同步函數(shù) + 主隊列 == 死鎖
- 并發(fā)隊列
GCD默認已經(jīng)提供了全局的并發(fā)隊列粤攒,供整個應用使用卤橄,不需要手動創(chuàng)建
使用dispatch_get_global_queue函數(shù)獲得全局的并發(fā)隊列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, 隊列的優(yōu)先級
unsigned long flags);
全局并發(fā)隊列
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 串行隊列
GCD中獲得串行的2中途徑
1.使用dispatch_queue_create函數(shù)創(chuàng)建串行隊列
dispatch_queue_t =
dispatch_queue_create(const char*label, 隊列名稱
dispatch_queue_attr_t attr); 隊列屬性,一般用NULL即可
2.使用主隊列
放在主隊列中的任務,都會放到主線程中執(zhí)行
使用dispatch_get_main_queue()獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
- 從子線程回到主線程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
執(zhí)行耗時的異步操作...
dispatch_async(dispatch_get_main_queue(), ^{
回到主線程积锅,執(zhí)行UI刷新操作
});
});
- 延時執(zhí)行
設定好延遲的時間后,它會先執(zhí)行后邊的代碼,2秒后再調(diào)用self的run方法(并且不會卡主線程,在主線程調(diào)最后會回到主線程,在子線程調(diào)最后會回到子線程)
withObject:參數(shù)
afterDelay:延遲的時間
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
使用GCD函數(shù)(2秒后自動開啟新線程 執(zhí)行block中的代碼,不會卡主當前的線程,在主/子線程調(diào)用都可以使用)
DISPATCH_TIME_NOW:現(xiàn)在開始的意
2.0 * NSEC_PER_SEC:設置的秒數(shù)(直接更改數(shù)字即可)
dispatch_get_main_queue():主隊列的意思
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
2秒后執(zhí)行這里的代碼... 在哪個線程執(zhí)行江耀,跟隊列類型有關(guān)
});
3.會卡住主線程
[NSThread sleepForTimeInterval:3]
- 只執(zhí)行一次
使用dispatch_once函數(shù)能保證某段代碼在程序運行過程中只被執(zhí)行1次
在設計模式中,單例模式也會用到
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
程序運行過程中,永遠只執(zhí)行1次的代碼(這里面默認是線程安全的)
});
- 隊列組
需求:1.分別異步執(zhí)行2個耗時的操作,其次,等2個異步操作都執(zhí)行完畢后,再回到主線程執(zhí)行操作
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
執(zhí)行1個耗時的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
執(zhí)行1個耗時的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
等前面的異步操作都執(zhí)行完畢后,回到主線程...
});
GCD的創(chuàng)建和釋放
在iOS6.0之前,在GCD中每當使用帶creat單詞的函數(shù)創(chuàng)建對象之后,都應該對其進行一次release操作.
在iOS6.0之后,GCD被納入到了ARC的內(nèi)存管理機制中,在使用GCD的時候我們就像對待普通OC對象一樣對待GCD,因此不再需要我們調(diào)用release方法.GCD 的基本使用
image
1.異步函數(shù)+并發(fā)隊列
1.創(chuàng)建隊列(并發(fā)隊列)
dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_CONCURRENT);
異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
2.異步函數(shù)+串行隊列
1.創(chuàng)建隊列(串行隊列)
dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_SERIAL);
異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
3.同步函數(shù)+串行隊列
1.創(chuàng)建隊列(串行隊列)
dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_SERIAL);
同步函數(shù)
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
4.同步函數(shù)+并發(fā)隊列
//獲得全局并發(fā)隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 同步函數(shù)
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
5.異步函數(shù)+主隊列
1.獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
異步函數(shù)
dispatch_async(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
6.同步函數(shù)+主隊列
1.獲得主隊列
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"1---%@",[NSThread currentThread]);
});
4.8 NSOperation
- NSOperation作用?
配合使用NSOperation 和NSOperationQueue也能實現(xiàn)多線程編程 -
NSOperation 和NSOperationoQueue實現(xiàn)多線程的具體步驟
NSOperation的子類
NSOperation是個抽象類,并不具備封裝操作的能力袱巨,必須使用它的子類
子類的方式有3中:
1.NSInvocationOperation
2.NSBlockOperation
3.自定義子類繼承NSOperation,實現(xiàn)內(nèi)部響應的方法NSInvocationOperation
1.創(chuàng)建對象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;
2.調(diào)用start方法開始執(zhí)行操作
- (void)start;
一旦執(zhí)行操作,就會調(diào)用target的sel方法
默認情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當前線程同步執(zhí)行操作;只有將NSOperation放到一個NSOperationQueue中阁谆,才會異步執(zhí)行操作
- NSBlockOperation
1.創(chuàng)建NSBlockOperation對象
+ (id)blockOperationWithBlock:(void (^)(void))block;
通過addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
只要NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作
- NSOperationQueue作用
如果將NSOperation添加到NSOperationQueue(操作隊列)中愉老,系統(tǒng)會自動異步執(zhí)行NSOperationQueue中的操作
- (void)addOperation:(NSOperation *)operation;
- (void)addOperationWithBlock:(void (^)(void))block;
- 最大并發(fā)數(shù)
同時執(zhí)行的任務數(shù)
最大并發(fā)數(shù)相關(guān)的方法
-(NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
- 自定義NSOperation
重寫-(void)main方法,在里面實現(xiàn)想執(zhí)行的任務
重寫-(void)main方法的注意點:自動創(chuàng)建自動釋放池,如果是異步操作,無法訪問主線程的自動釋放池
4.9 RunLoop
- 如果沒有RunLoop,程序輸出后就退出了
int main(int argc, char * argv[]) {
NSLog(@"main");
return 0;
}
- 如果有了RunLoop,由于main函數(shù)里面啟動了RunLoop,所以程序并不會馬上退出,保持持續(xù)運行狀態(tài)
int main(int argc, char * argv[]) {
BOOL run = YES;
do{
//執(zhí)行各種任務,處理各種事件
}while(run);
return 0;
}
- main函數(shù)中的RunLoop,UIApplicationMaiin函數(shù)內(nèi)部就啟動了一個RunLoop,所以UIApplicationMain函數(shù)一直沒有返回,保持了程序的持續(xù)運行,這個默認啟動的RunLoop跟主線程相關(guān)聯(lián)
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RunLoop與線程
1.每條線程都有唯一的與之對應的RunLoop對象
2.主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
3.RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀-
獲得RunLoop對象
- RunLoop相關(guān)類
Core Foundation中關(guān)于RunLoop的5個類:
CFRunLoopRef:它自己,也就代表一個RunLoop對象
CFRunLoopModeRef:RunLoop的運行模式
CFRunLoopSourceRef:事件源
CFRunLoopTimerRef:時間的觸發(fā)器
CFRunLoopbaserverRef:觀察者 監(jiān)聽CFRunLoopRef的狀態(tài)
CFRunLoopModeRef
系統(tǒng)默認注冊了5個Mode模式:
kCFRunLoopDefaultMode:App的默認Mode场绿,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動嫉入,保證界面滑動時不受其他 Mode 影響
UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode焰盗,啟動完成后就不再使用
GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
kCFRunLoopCommonModes: 這是一個占位用的Mode咒林,不是一種真正的ModeRunLoop處理邏輯
image
1.通知觀察者,即將進入Loop
2.通知觀察者,將要處理定時器
3.通知觀察者,將要處理非基于端口的源
4.處理非基于端口的源
5.如果有基于端口的源準備好并處于等待狀態(tài),立即啟動,跳到第9步
6.通知觀察者,線程即將休眠
7.休眠,等待喚醒
8.通知觀察者,線程剛被喚醒
9.處理喚醒時收到的消息,之后跳到第2步
10.通知觀察者,即將推出Loop
- RunLoop應用
- RunLoop面試題
1.什么是RunLoop?
字面意思運行循環(huán)
其實它內(nèi)部就是do-while循環(huán),這個循環(huán)內(nèi)部不斷處理各種任務(比如Source,Timer,Observer);
一個線程對應一個RunLoop,主線程的RunLoop默認啟動,子線程的RunLoop手動啟動;
RunLoop只能選擇一個Mode啟動,如果當前Mode中沒有任何Source,Timer,那么就直接退出RunLoop.
4.10 HTTP通信過程-請求/響應
HTTP協(xié)議規(guī)定:1個完整的由客戶端發(fā)給服務器的HTTP請求中包含以下內(nèi)容