




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
iOS程序員面試分類模擬13簡答題1.
如何使用NSURLConnection進行網(wǎng)絡(luò)請求?正確答案:NSURLConnection是iOS中最經(jīng)典的網(wǎng)絡(luò)請求方案。雖然在蘋果公司推出NSURLSe(江南博哥)ssion后已經(jīng)不推薦使用NSURLConnection了(NSURLConnection在iOS9被宣布棄用),但是在一些早先構(gòu)建的項目和框架中可能仍然使用了NSURLConnection的技術(shù),所以了解NSURLConnection的基本操作仍然是有必要的。使用NSURLConnection發(fā)送請求通常需要使用以下類:
1)NSURL,主要用于創(chuàng)建網(wǎng)絡(luò)請求地址。一個NSURL對象代表了一個表示遠程服務(wù)器資源或者本地文件的URL。開發(fā)者可以直接使用經(jīng)過UTF-8編碼后的字符串創(chuàng)建一個NSURL:
+URLWithString:
-initWithString:
2)NSURLRequest,代表網(wǎng)絡(luò)請求對象。它包含了發(fā)送一個請求所需要的一系列信息,如NSURL對象、請求方式、請求體、請求頭等。使用NSURL對象創(chuàng)建NSURLRequest對象:
+(instancetype)requestWithURL:(NSURL*)URL
+(instancetype)requestWithURL:(NSURL*)URLcachePolicy:
(NSURLRequestCachePolicy)cachePolicytimeoutInterval:(NSTimeInterval)timeoutInterval
3)NSURLConnection,作為CoreFoundation/CFNetwork框架的API之上的一個抽象,在2003年隨著第一版的Safari就發(fā)布了。它作為iOS7之前的網(wǎng)絡(luò)基礎(chǔ)架構(gòu),可以發(fā)送同步或異步請求,可以直接接收數(shù)據(jù)也可以使用代理監(jiān)聽請求。
NSURLConnection、NSURL、NSURLRequest之間的關(guān)系如圖所示。
三者關(guān)系
NSURLConnection的使用步驟如下:
1)創(chuàng)建一個NSURL對象,用于設(shè)置請求路徑。
2)創(chuàng)建一個NSURLRequest對象,并設(shè)置請求頭、請求體等請求參數(shù)。
3)創(chuàng)建一個NSURLResponse對象用于接收響應(yīng)數(shù)據(jù),一般使用NSURLResponse的子類NSHTTPURLResponse。
4)使用NSURLConnection發(fā)送同步或異步請求。
下面的代碼將演示如何使用NSURLConnection發(fā)送請求并獲得數(shù)據(jù)。
(1)同步GET請求方法
sendSynchronousRequest:requestretumingResponse:&responseerror:
示例代碼如下:
-(void)connectionSyncGet{
/*創(chuàng)建NSURL對象*/
NSURL*url=[NSURLURLVCithString:IMAGEURL];
/*創(chuàng)建請求對象,默認為GET請求*/
NSURLRequest*request=[NSURLRequestrequestWithURL:url];
/*創(chuàng)建請求響應(yīng)對象*/
NSHTTPURLResponse*response=nil;
NSError*error=nil;
/*發(fā)送請求,同步請求會阻塞當前線程*/
NSData*data=[NSURLConnectionsendSynchronousRequest:requestreturningResponse:&responseerror:&error];
/*解析返回的數(shù)據(jù)*/
_image=[UIImageimageWithData:data];
/*顯示圖片*/
[selfchangeBg];
}
-(void)changeBg{
self.view.layer.contents=(id)_image.CGImage;
}
(2)異步POST請求方法
+(void)sendAsynchronousRequest:(NSURLRequest*)request
queue:(NSOperationQueue*)queue
completionHandler:(void(^)fNSURLResponse*_Nullableresponse,NSData*_Nullabledata,NSError*_NuliableconnectionError))handler
示例代碼如下:
-(void)connectionAsyncPost{
NSURL*url=[NSURLURLWithString:IMAGEURL];
/*創(chuàng)建請求對象*/
NSMutableURLRequest*request=[NSMutableURLRequestrequestWithURL:url];
/*設(shè)置請求方式為POST*/
request.HTTPMethod=@"POST";
/*設(shè)置請求體,在請求體中設(shè)置參數(shù)*/
request.HTTPBody=[@"usemame=520it&pwd=520&type=JSON"
dataUsingEncoding:NSUTF8StringEncoding];
/*設(shè)置清求超時*/
request.timeoutInterval=15;
/*設(shè)置請求頭*/
[requestsetValue:@"iOS"forHTTPHeaderField:@"User-Agent"];
/*發(fā)送請求*/
[NSURLConnectionsendAsynchronousRequest:requestqueue:[NSOperationQueue
mainQueue]completionHandler:^(NSURLResponse*_Nullableresponse,NSData*
_Nullabledata,NSError*NullableconnectionError){
NSLog(@"NSThread=%@",[NSThreadcurrentThread]);
if(connectionError){
NSLog(@"error=%@",connectionError.userInfo);
}else{
/*解析返回的數(shù)據(jù)*/
_image=[UIImageimageWithData:data];
/*顯示圖片*/
[selfchangeBg];
}
}];
}
(3)NSURLConnectiOnDelegate的使用
可以使用NSURLConnectionDelegate監(jiān)聽網(wǎng)絡(luò)請求的響應(yīng)。示例代碼如下:
-(void)connectGETDelegate{
/*創(chuàng)建NSURL對象*/
NSURL*url=[NSURLURLWithString:IMAGEURL];
/*創(chuàng)建請求對緣,默認為GET請求*/
NSURLRequest*request=[NSURLRequestrequestWithURL:url];
/*設(shè)置代理*/
[NSURLConnectionconnectionWithRequest:requestdelegate:self];
}
/*當接收到服務(wù)器響應(yīng)的時候調(diào)用。第一個參數(shù)connection:監(jiān)聽的是哪個
NSURLCormection對象;第二個參數(shù)response:接收到的服務(wù)器返回的響應(yīng)頭信息*/
-(void)connection:(nonnullNSURLConnection*)connectiondidReceiveResponse:(nonnullNSURLResponse*)response{
_imageData=[NSMutableDatadata];
}
/*當接收到數(shù)據(jù)的時候調(diào)用,該方法會被調(diào)用多次。第一個參數(shù)connection:監(jiān)聽的
是哪個NSURLConnection對象;第二個參數(shù)data:本次接收到的服務(wù)端返回的二進制數(shù)
據(jù)(可能是片段)*/
-(void)connection:(nonnullNSURLConnection*)connectiondidReceiveData:(nonnullNSData*)data{
[_imageDataappendData:data];
}
/*服務(wù)端返回的數(shù)據(jù)接收完畢之后會調(diào)用。通常在該方法中解析服務(wù)器返回的數(shù)據(jù)*/
-(void)IconnectionDidFinishLoading:(nonnullNSURLConnection*)connection{
/*解析返回的數(shù)據(jù)*/
_image=[UIImageimageWithData:_imageData];
/*顯示圖片*/
[selfchangeBg];
}
/*當請求錯誤的時候調(diào)用(例如請求超時)。第一個參數(shù)connection:NSURLCormection
對象。第二個參數(shù):網(wǎng)絡(luò)請求的錯誤信息,如果請求失敗,那么error有值*/
-(void)connection:(nonnullNSURLConnection*)connectiondidFailWithError:(nonnullNSError*)error{
}
2.
如何使用NSURLSession進行網(wǎng)絡(luò)請求?正確答案:在2013年的WWDC上,蘋果公司推出了NSURLConnection的替代方案:NSURLSession。和NSURLConnection一樣,NSURLSession指的也不僅是同名類NSURLSession,它還包括一系列相關(guān)聯(lián)的類。NSURLSession包括了與之前相同的組件:NSURLRequest與NSURLCache,但是將NSURLConnection替換成了NSURLSession、NSURLSessionConfiguration及NSURLSessionTask的3個子類:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。
與NSURLConnection相比,NSURLSession最直接的改進就是可以配置每個session的緩存、協(xié)議、cookie,以及證書策略(CredentialPolicy),甚至跨進程共享這些信息。這將允許程序和網(wǎng)絡(luò)基礎(chǔ)框架之間相互獨立,不會發(fā)生干擾。每個NSURLSession對象都由一個NSURLSessionConfiguration對象進行初始化,后者指定了剛才提到的那些策略以及一些用來增強移動設(shè)備上性能的新選項。
NSURLSessionTask負責處理數(shù)據(jù)的加載以及文件的數(shù)據(jù)在客戶端與服務(wù)器之間的上傳和下載。它是一個抽象類,一般使用其子類:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。這3個子類封裝了現(xiàn)代程序3個最基本的網(wǎng)絡(luò)任務(wù):獲取數(shù)據(jù)(如JSON或者XML),上傳文件和下載文件。
NSURLSession相關(guān)類的關(guān)系如圖所示。
NSURLSession相關(guān)類的關(guān)系
如何使用NSURLSession像NSURLConnection那樣發(fā)送一個請求呢?基本步驟如下:
1)創(chuàng)建NSURLSessionConfiguration對象對NSURLSession進行配置。
2)創(chuàng)建NSURLSession對象。
3)利用上一步創(chuàng)建好的NSURLSession對象創(chuàng)建NSURLSessionTask的子類對象。
4)執(zhí)行請求任務(wù)。
下面的示例展示了NSURLSession的基本用法,代碼如下:
-(void)sessionGet{
/*創(chuàng)建NSURL對象*/
NSURL*url=[NSURLURLWithString:IMAGEURL];
/*創(chuàng)建請求對象,默認為GET請求*/
NSURLRequest*request=[NSURLRequestrequestWithURL:url];
/*創(chuàng)建配置*/
NSURLSessionConfiguration*config=[NSURLSessionConfigurationdefaultSessionConfigtwation];
/*創(chuàng)建NSURLSession對象*/
NSURLSession*session=[NSURLSessionsessionWithConfiguration:config];
/*創(chuàng)建任務(wù)*/
NSURLSessionDataTask*task=[sessiondataTaskWithRequest:requestcompletionHandler:^(NSData*_Nullabledata,NSURLResponse*_Nullableresponse,NSError*_Nullableerror){
if(error){
return;
}
/*解析返回的數(shù)據(jù)*/
_image=[UIImageimageWithData:data];
/*顯示圖片,注意!此時是異步線程需要在主線程中顯示圖片*/
[selfchangeBg];
}];
[taskresume];
}
3.
block有哪幾種定義的方式?正確答案:在Objective-C中,block定義包含了block的類型聲明和實現(xiàn),基本形式如下:
返回值類型(^block名稱)(參數(shù)類型)=^(參數(shù)類型和參數(shù)名){};
其中,返回值類型和參數(shù)可以是空。如果有參數(shù),那么在定義block的時候,必須要標明參數(shù)的類型和參數(shù)名。所以,block大致有3種細分的定義方式。
1)沒有返回值,沒有參數(shù)的定義方式。
void(^myBlock)()=^{
//代碼
};
2)有返回值,有參數(shù)的定義方式。
int(^myBlock)(int)=^(inta){
returna;
};
3)有返回值,沒有參數(shù)的定義方式。
int(^myBlock)()=^{
return100;
};
當然,block也有屬于自己的類型,就像在Objective-C中,字符串對象屬于NSString類型一樣。block類型的格式就是:
返回值類型(^)(參數(shù)類型)
也就是說,上面第一種定義方式的block類型就是void(^)(),myBlock不是變量名,而是這種block類型的別名。在Objective-C中,可以使用typedef關(guān)鍵字定義block類型,也可以直接使用inline提示符來自動生成block格式。示例代碼如下:
/*使用typedef關(guān)鍵字定義block類型*/
typedefvoid(^myBloek)();
myBlockblock=^{
};
/*使用inline提示符來自動生成block格式*/
<#retumType#>(^<#blockName#>)(<#parameterTypes#>)=^(<#parameters#>){
<#statements#>
};
4.
在ARC環(huán)境下,是否需要使用copy關(guān)鍵字來修飾block?正確答案:先要明確的是,block其實包含兩個組成部分,一部分是block所執(zhí)行的代碼,這一部分在編譯的時候已經(jīng)確定;另一部分是block執(zhí)行時所需要的外部變量值的數(shù)據(jù)結(jié)構(gòu)。根據(jù)block在內(nèi)存中的位置,系統(tǒng)將block分為3類。
1)NSGlobalBlock:該類型的block類似函數(shù),內(nèi)存地址位于內(nèi)存全局區(qū)。只要block沒有對作用域中局部變量進行引用,此block會被系統(tǒng)設(shè)置為該類型。示例代碼如下:
-(void)test{
void(^gBlock1)(int,int)=^(inta,intb){
NSLog(@"a+b=%d",a+b);
};
NSLog(@"%@",gBlock1);
}
以上代碼的輸出結(jié)果是:<__NSGlobalBlock__:0x1025e8110>
事實上,對于NSGlobalBlock類型的block,無需做更多的處理,不需要使用retain和copy進行修飾。即使使用了copy,系統(tǒng)也不會改變block的內(nèi)存地址,操作是無效的。
2)NSStackBlock:該類型的block內(nèi)存位于棧,其生命周期由函數(shù)決定,函數(shù)返回后block將無效。
在MRC環(huán)境下,若block內(nèi)部引用了局部變量,此block就會被系統(tǒng)設(shè)置為該類型。對于NSStackBlock類型的block,使用retain和release操作都是無效的,必須調(diào)用Block_copy()方法,或者使用copy進行修飾,其作用就是將block的內(nèi)存從棧轉(zhuǎn)移到堆,此時block就會轉(zhuǎn)變?yōu)镹SMallocBlock類型,這也是一直使用copy修飾block的原因。
在ARC環(huán)境下,若block內(nèi)部引用了局部變量,系統(tǒng)默認使用了copy對block進行修飾,使其變成NSMallocBlock類型。所以在ARC環(huán)境下,不需要手動使用copy關(guān)鍵字來修飾block。
3)NSMallocBlock:當對NSStackBlock類型的block進行copy操作后,block就會轉(zhuǎn)為此類型。在MRC環(huán)境下,可以使用retain、release等方法手動管理此類型block的生命周期。在ARC環(huán)境下,系統(tǒng)會幫助管理此類型block的生命周期。
5.
在block內(nèi)如何修改block外部變量?正確答案:在block內(nèi)部修改block外部變量會造成編譯錯誤,提示變量缺少block修飾,不可賦值。要想在block內(nèi)部修改block外部變量,則必須在外部定義變量時,前面加上block修飾符。示例代碼如下:
/*block外部變量*/
__blockintvar1=0;
intvar2=0;
/*定義block*/
void(^block)(void)=^{
/*試圖修改block外部變量*/
var1=100;
/*編譯錯誤,在block內(nèi)部不可對var2賦值*/
//var2=1:
};
/*執(zhí)行block*/
block();
NSLog(@"修改后的var1:%d",var1);
//修改后的var1:100
block內(nèi)部為何不能直接修改外部變量呢?因為當外部變量沒有使用__block修飾符修飾時,block在截獲外部的自動變量時會在內(nèi)部新創(chuàng)建一個新的變量val來保存所截獲的外部變量的瞬時值,新變量val成為block的成員變量(Objective-C中block也是對象),之后在block代碼中修改的值是成員變量val的值,而不是截獲的外部變量的值,所以外部變量的值不會受影響。此時,修改外部變量是先取值并賦值給成員變量val,然后修改val的值??捎孟露拇a模擬其原理,假設(shè)block對外部變量var進行了加1操作,block使用一個名為block的函數(shù)來表示。
intvar=1;
voidblock(){
intval=var;
val+=1;
}
當外部變量使用了__block修飾符進行修飾的時候則是另外一種情形了,此時block并不是截獲外部自動變量的瞬時值并保存到自己的新成員變量中,而是保存了對外部變量的指引引用,因此對指針變量的修改會直接影響外部變量的值。此時使用代碼模擬其原理如下,依然是block對外部變量var進行加1操作。
__blockintVar=1;
voidblock(){
int*ptr=&vat;
*ptr+=1;
}
因此,block內(nèi)部不可以直接修改外部變量,如果要修改外部變量,那么該外部變量必須使用__block修飾符進行修飾,否則編譯器會直接進行報錯提示。
需要注意的是,此處討論的是自動變量,而靜態(tài)變量由于默認傳給block的就是地址值,所以是可以直接修改的。另外,全局變量和靜態(tài)全局變量由于作用域很廣,也是可以在block中直接被修改的,編譯器也不會報錯。
6.
在block中使用self關(guān)鍵字是否一定導致循環(huán)引用?正確答案:在block中使用self關(guān)鍵字并不總會引起循環(huán)引用。事實上,只有當block和self相互持有時,才會導致循環(huán)引用。由于block會對block中的對象進行持有操作,就相當于持有了其中的對象,此時如果block中的對象又持有了該block,那么就會造成循環(huán)引用。典型的場景就是當block作為self的屬性使用時,又在block內(nèi)部調(diào)用了self的屬性或者方法。示例代碼如下:
typedefvoid(^block)();
@property(copy,nonatomic)blockmyBlock;
@property(copy,nonatomic)NSString*blockString;
-(void)testBlock{
self.myBlock=^(){
/*其實注釋中的代碼,同樣會造成循環(huán)引用*/
NSString*localString=self.blockString;
};
}
在上面這個例子中,myBlock和self相互引用了對方。此時,self的銷毀依賴于myBlock的銷毀,而myBlock的銷毀又依賴self的銷毀,這樣就造成了循環(huán)引用,即使在外界已經(jīng)沒有任何指針能夠訪問到它們了,它們也無法被釋放,如圖所示。
block循環(huán)引用
解決循環(huán)引用的關(guān)鍵是斷開引用鏈。在實際開發(fā)中,主要使用弱引用(weakreference)的方法來避免循環(huán)引用的產(chǎn)生。在ARC環(huán)境下,使用__weak修飾符定義一個__weakself的引用,并且在里面使用這個弱引用。使用這種方式對示例代碼修改如下:
typedefvoid(^block)();
@property(copy,nonatomie)blockmyBlock;
@property(copy,nonatomic)NSString*blockString;
-(void)testBlock{
__weaktypeOf(self)weakSelf=self;
self.myBlock=^(){
/*其實注釋中的代碼,同樣會造成循環(huán)引用*/
NSString*localString=weakSelf.blockString;
};
}
當使用__weak修飾的弱類型self時,block便不會再持有self的引用了,也就不會再產(chǎn)生循環(huán)引用了。
下面是不會造成循環(huán)引用的幾種情況:
1)大部分GCD方法。示例代碼如下:
/*使用GCD異步執(zhí)行主隊列任務(wù)*/
dispatch_async(dispatch_get_main_queue(),^{
[selfdoSomething];
});
在例子中,因為self并沒有對GCD的block進行持有,只有block持有了self的引用,所以不會造成循環(huán)引用。
2)block作為臨時變量。在這種情況下,同樣self并沒有持有block,所以也不會造成循環(huán)引用。
3)block執(zhí)行過程中self對象被釋放。事實上,block的具體執(zhí)行時間不確定,當block被執(zhí)行的時候block中被__weak修飾的self對象有可能已經(jīng)被釋放了(例如,控制器對象已經(jīng)被POP了)。當在并發(fā)執(zhí)行,涉及異步服務(wù)的時候,這種情況有可能會出現(xiàn)。
對于這種情況,應(yīng)該在block中使用__strong修飾符修飾self對象,使得在block期間對對象持有,當block執(zhí)行結(jié)束后,解除其持有。示例代碼如下:
-(void)testBlock{
__weaktypeOf(self)weakSelf=self;
self.myBlock=^(){
__strongtypeOf(self)strongSelf=weakSelf;
NSString*localString=strongSelf.blockString;
};
}
7.
GCD中有哪幾種隊列?正確答案:在GCD中,派發(fā)隊列(DispatchQueue)是最重要的概念之一。派發(fā)隊列是一個對象,它可以接受任務(wù),并將任務(wù)以FIFO(先進先出)的順序來執(zhí)行。派發(fā)隊列可以是并發(fā)的或串行的。并發(fā)隊列可以執(zhí)行多任務(wù),串行隊列同一時間只執(zhí)行單一任務(wù)。在GCD中,有3種類型的派發(fā)隊列。
1)串行隊列。串行隊列中的任務(wù)按先后順序逐個執(zhí)行,通常用于同步訪問一個特定的資源。使用dispatch_queue_create函數(shù),可以創(chuàng)建串行隊列。
2)并發(fā)隊列。在GCD中也稱為全局并發(fā)隊列,可以并發(fā)地執(zhí)行一個或者多個任務(wù)。并發(fā)隊列有高、中、低、后臺4個優(yōu)先級別,中級是默認級別??梢允褂胐ispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)函數(shù)來獲取全局并發(fā)隊列對象。串行隊列和異步隊列的區(qū)別在于同步執(zhí)行和異步執(zhí)行時的表現(xiàn)(見表)。串行隊列和異步隊列的區(qū)別
同步執(zhí)行異步執(zhí)行串行隊列在當前線程中,F(xiàn)IFO執(zhí)行在其它線程,F(xiàn)IFO執(zhí)行并發(fā)隊列在當前線程中,F(xiàn)IFO執(zhí)行多條線程,同步執(zhí)行
3)主隊列。它是一種特殊的串行隊列。它在應(yīng)用程序的主線程中用于更新UI。其他的兩種隊列不能更新UI。使用dispatch_get_main_queue函數(shù),可以獲得主隊列對象。
8.
如何理解GCD死鎖?正確答案:所謂死鎖,通常指兩個操作相互等待對方完成,造成死循環(huán),于是兩個操作都無法完成,就產(chǎn)生了死鎖。下面是一個死鎖的代碼示例。
intmain(intargc,constchar*argv[]){
@autoreleasepool{
dispatch_sync(dispatch_get_main_queue(),^(void){
NSLog(@"這里死鎖了");
});
}
return0;
}
這個程序就是典型的死鎖。程序?qū)⒅麝犃泻鸵粋€block傳入GCD的同步函數(shù)dispatch_sync中,等待同步函數(shù)執(zhí)行,直到同步函數(shù)返回。但是事實上,這個block永遠不會被執(zhí)行。因為main函數(shù)是在主隊列中的,它是正在被執(zhí)行的任務(wù),而主隊列中同時只能有一個任務(wù)在執(zhí)行,也就是說只有隊頭的任務(wù)才能被執(zhí)行。由于主隊列是一個特殊的串行隊列,它嚴格遵循FIFO的原則,所以block中的任務(wù)必須等到main函數(shù)執(zhí)行完,才能被執(zhí)行。另外,dispatch_sync函數(shù)的特性是,只有block中的任務(wù)被執(zhí)行完畢,才會返回。因此,只要block不被執(zhí)行,它就不會返回。所以,在這段代碼中,main函數(shù)等待dispatch_sync函數(shù)返回,而dispatch_sync的返回又依賴block執(zhí)行完畢,block的執(zhí)行又需要等待main函數(shù)的執(zhí)行結(jié)束。這樣就造成了三方循環(huán)等待,即死鎖。
可以總結(jié)出GCD死鎖的原因大體有以下兩點:
1)GCD函數(shù)未返回,會阻塞正在執(zhí)行的任務(wù)。這里需要強調(diào)的是,阻塞(blocking)和死鎖(deadlock)是不同的意思。阻塞表示A任務(wù)的執(zhí)行需要等待B任務(wù)的完成,稱作B會阻塞A,通俗來講就是強制等待的意思。而死鎖表示A任務(wù)和B任務(wù)相互等待,形成阻塞閉環(huán)。
2)隊列中的任務(wù)無法并發(fā)執(zhí)行。
以上兩點,如果同時出現(xiàn),那么就會產(chǎn)生阻塞閉環(huán),形成死鎖。所以針對以上情況,只需要消除其中任何一個因素,就可以打破這個閉環(huán),避免死鎖。
解決GCD死鎖的方法有以下幾種方式:
1)使用dispatch_async函數(shù)。dispatch_async函數(shù)是異步函數(shù),具備開啟新線程的能力,但是不一定會開啟新線程。如果傳入的隊列參數(shù)是主隊列,那么任務(wù)仍然會在主線程中等待執(zhí)行,函數(shù)不會立即返回。如果傳入的隊列是普通的串行隊列或者并發(fā)隊列,那么該函數(shù)就會立即返回。
2)將有可能形成阻塞閉環(huán)的任務(wù)分別放到不同的隊列中執(zhí)行。如案例中,可以新建一個串行隊列,將block放入自己的串行隊列中,不再和main函數(shù)除以一個隊列,就能夠解決隊列阻塞,因此避免了死鎖問題。示例代碼如下:
intmain(intargc,constchar*argv[]){
@autoreleasepool{
dispatch_queueserialQueue=dispatch_queue_create("這是一個串行隊列",DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue,^(void){
NSLog(@"這里不會死鎖了");
});
}
return0;
}
另外,有些面試題,如“是否在主線程使用sync函數(shù)就會造成死鎖”或者“是否在主線程使用sync函數(shù),同時傳入串行隊列就會死鎖”,答案都是否定的,只要能夠真正了解GCD死鎖的原理,就能很好地回答類似問題了。
9.
如何使用GCD實現(xiàn)線程之間的通信?正確答案:在iOS應(yīng)用程序的開發(fā)中,一般需要在主線程中進行UI刷新。例如,響應(yīng)單擊、滾動或者拖曳等事件,所以主線程一般也被稱為UI線程。在主線程中,應(yīng)該盡量避免在主線程中執(zhí)行一些耗時的操作,如文件的上傳和下載等。應(yīng)該將這些耗時操作放到子線程中執(zhí)行,等子線程的耗時操作執(zhí)行完成后,再通知主線程更新相應(yīng)的UI。要完成這樣的操作,就必須實現(xiàn)線程之間的通信,GCD是實現(xiàn)線程之間通信的常用方式。示例代碼如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
/*執(zhí)行耗時操作*/
for(inti=0;i<10000;i++){
NSLog(@"i=%i",i);
}
/*回到主線程*/
dispatch_async(dispatch_get_main_queue(),^{
//更新UI
}):
});
先將需要執(zhí)行的耗時操作放入全局并發(fā)隊列中,再使用dispatch_async異步函數(shù)執(zhí)行并發(fā)隊列,這樣就會開啟新的線程執(zhí)行耗時操作,不會阻塞主線程的任務(wù)。當耗時任務(wù)完成后,通過主隊列回到主線程執(zhí)行相應(yīng)的UI更新操作。需要強調(diào)的是,當使用主隊列時,無論是使用dispatch_async異步函數(shù),還是使用dispatch_sync同步函數(shù),執(zhí)行的結(jié)果是一樣的。因為主隊列是一種特殊的串行隊列,在主隊列中任務(wù)總會在主線程中執(zhí)行。
10.
GCD如何實現(xiàn)線程同步?正確答案:NSOperation可以通過使用addDependency函數(shù)直接設(shè)置操作之間的依賴關(guān)系來調(diào)整操作之間的執(zhí)行順序從而實現(xiàn)線程同步,還可以使用setMaxConcurrentOperationcount函數(shù)來直接設(shè)置并控制最大并發(fā)數(shù)量,那么在GCD中如何實現(xiàn)呢?
GCD實現(xiàn)線程同步的方法有以下3種:
1)組隊列(dispatch_group)。
2)阻塞任務(wù)(dispatch_barrier_(a)sync)。
3)信號量機制(dispatch_semaphore)
信號量機制主要是通過設(shè)置有限的資源數(shù)量來控制線程的最大并發(fā)數(shù)量及阻塞線程實現(xiàn)線程同步等。
GCD中使用信號量需要用到3個函數(shù):
1)dispatch_semaphore_create用來創(chuàng)建一個semaphore信號量并設(shè)置初始信號量的值。
2)dispatch_semaphore_signal發(fā)送一個信號讓信號量增加1(對應(yīng)PV操作的V操作)。
3)dispatch_semaphore_wait等待信號使信號量減1(對應(yīng)PV操作的P操作)。
11.
GCD多線程編程中什么時候會創(chuàng)建新線程?正確答案:對于是否會開啟新線程的情景主要有如下幾種情況:串行隊列中提交異步任務(wù)、串行隊列中提交同步任務(wù)、并發(fā)隊列中提交異步任務(wù)、并發(fā)隊列中提交同步任務(wù)。
(其中主隊列是典型的串行隊列,全局隊列是典型的并發(fā)隊列)
/*創(chuàng)建一個串行隊列*/
dispatch_queue_tserialQueue=dispatch_queue_create("serial.queue".DISPATCH_QUEUE_SERIAL);
/*創(chuàng)建一個并發(fā)隊列*/
dispatch_queue_tconcurrentQueue=dispatch_queue_create("concurrent.queue",DISPATCH_QUEUE_CONCURRENT);
1)串行隊列中提交同步任務(wù):不會開啟新線程,直接在當前線程同步地串行執(zhí)行這些任務(wù)。
/*1串行隊列添加同步任務(wù):沒有開肩新線程,全部在主線程串行執(zhí)行*/
dispatch_sync(serialQueue,^{
NSLog(@"SERIAL_SYN_A%@",[NSThreadcurrentThread]);
});
dispatch_sync(serialQueue,^{
NSLog(@"SERIAL_SYN_B%@",[NSThreadcurrentThread]);
});
dispatch_sync(serialQueue,^{
NSLog(@"SERIAL_SYN_C%@",[NSThreadcurrentThread]);
});
2)串行隊列中提交異步任務(wù):會開啟一個新線程,在新子線程異步地串行執(zhí)行這些任務(wù)。
/*2串行隊列添加異步任務(wù):開啟了一個新子線程并共用,串行執(zhí)行*/
dispatch_async(serialQueue,^{
NSLog(@"SERIAL_ASYN_A%@",[NSThreadcurrentThread]);
});
dispatch_async(serialQueue,^{
NSLog(@"SERfAL_ASYN_B%@",[NSThreadcurrentThread]);
});
dispatch_async(serialQueue,^{
NSLog(@"SERIAL_ASYN_C%@",[NSThreadcurrentThread]);
});
3)并發(fā)隊列中提交同步任務(wù):不會開啟新線程,效果和“串行隊列中提交同步任務(wù)”一樣,直接在當前線程同步地串行執(zhí)行這些任務(wù)。
/*3并發(fā)隊列添加同步任務(wù):沒有開啟新線程,全部在主線程串行執(zhí)行*/
dispatch_sync(concurrentQueue,^{
NSLog(@"CONCURRENT_SYN_A%@",[NSThreadcurrentThread]);
});
dispatch_sync(concurrentQueue,^{
NSLog(@"CONCURRENT_SYN_B%@",(NSThreadcurrentThread]);
});
dispatch_sync(concurrentQueue,^{
NSLog(@"CONCURRENT_SYN_C%@",[NSThreadcurrentThread]);
});
4)并發(fā)隊列中提交異步任務(wù):會開啟多個子線程,在子線程異步地并發(fā)執(zhí)行這些任務(wù)。
/*4并發(fā)隊列添加異步任務(wù):開啟多個子線程,并發(fā)執(zhí)行多個任務(wù)*/
dispatch_async(concurrentQueue,^{
NSLog(@"CONCURRENT_ASYN_A%@",[NSThreadcurrentThread]);
});
dispatch_async(concurrentQueue,^{
NSLog(@"CONCURRENT_ASYN_B%@",[NSThreadcurrentThread]);
});
dispatch_async(concurrentQueue,^{
NSLog(@"CONCURRENT_ASYN_C%@",[NSThreadcurrentThread]);
});
下圖展示了上面例子中線程的執(zhí)行順序。
線程執(zhí)行順序
總結(jié):
只有異步提交任務(wù)時才會開啟新線程,異步提交到串行隊列會開啟一個新線程,異步提交到并發(fā)隊列可能會開啟多個線程。
同步提交任務(wù)無論提交到并發(fā)隊列還是串行隊列,都不會開啟新線程,都會直接在當前線程依次同步執(zhí)行。
注意,如果當前線程是主線程,那么不可在當前線程提交同步任務(wù),否則會造成線程死鎖而報錯。
12.
iOS中如何觸發(fā)定時任務(wù)或延時任務(wù)?正確答案:定時任務(wù)指周期性地調(diào)用某個方法,實現(xiàn)任務(wù)的反復執(zhí)行,如倒計時等;延時任務(wù)指等待一定的時間后再執(zhí)行某個任務(wù),如頁面的延時跳轉(zhuǎn)等。iOS中控制任務(wù)的延時或定時執(zhí)行的方法有很多,使用中要注意是同步還是異步,是否會阻塞主線程等問題。延時和定時的實現(xiàn)方法依次如下。
1.performSelector實現(xiàn)延時任務(wù)
延時任務(wù)可以通過當前UIViewController的perfonnSelector隱式創(chuàng)建子線程實現(xiàn),不會阻塞主線程。
/*延遲10s執(zhí)行任務(wù)*/
[selfperformSelector:@selector(task)withObject:nilafterDelay:10];
-(void)task
{
//delaytask
}
2.利用sleep實現(xiàn)后面任務(wù)的等待
慎用,會阻塞主線程NSThreadsleepForTimeInterval:10.0];
3.GCD實現(xiàn)延時或定時任務(wù)
通過GCD實現(xiàn)block代碼塊的延時執(zhí)行。
dispatch_time_tdelay=dispatch_time(DiSPATCH_TIME_NOW,10*NSEC_PER_SEC);
dispatch_after(delay,dispatch_get_main_queue0,^{
//delaytask
});
GCD還可以用來實現(xiàn)定時器功能,還能設(shè)置延時開啟計時器,使用中注意一定要定義強引用指針來指向計時器對象才可讓計時器生效。
/*必須要用強引用指針,計時器才會生效*/
@property(nonatomic,strong)dispatch_source_ttimer;
/*在指定線程上定義計時器*/
dispatch_queue_tqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatc_souree_t_timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,queue);
/*開始的時間*/
dispatch_time_twhen=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1.0*NSEC_PER_SEC));
/*設(shè)置計時器*/
dispatch_source_set_timer(_timer,when,1.0*NSEC_PER_SEC,0);
/*計時器回調(diào)block*/
dispatch_source_set_event_handler(_timer,^{
NSLog(@"dispatch_source_set_timerisworking!");
});
/*開啟計時器*/
dispatch_resume(_timer);
/*強引用計時器對象*/
self.timer=_timer;
4.NSTimer實現(xiàn)定時任務(wù)
NSTimer主要用于開啟定時任務(wù),但要正確使用才能保證它能夠正常有效地運行。尤其要注意以下兩點:
1)確保NSTimer已經(jīng)添加到當前RunLoop。
2)確保當前RunLoop已經(jīng)啟動。
創(chuàng)建NSTimer有兩種方法,代碼如下:
+(NSTimer*)timerWithTimeInterval:(NSTimeInterval)titarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(nullableid)userInforepeats:(BOOL)yesOrNo;
+(NSTimer*)scheduledTimerWithTimeInterval:(NSTimeInterval)titarget:(id)aTargetselector:(SEL)aSelectoruserInfo:(nullableid)userInforepeats:(BOOL)yesOrNo;
這兩種方法的主要區(qū)別為,使用timerWithTimeInterval創(chuàng)建的timer不會自動添加到當前RunLoop中,需要手動添加并指定RunLoop的模式:[[NSRunLoopcurrentRunLoop]addTimer:mainThreadTimerforMode:NSDefaultRtmLoopMode];而使用scheduledTimerWithTimeInterval創(chuàng)建的RunLoop會默認添加到當前RunLoop中。
NSTimer可能在主線程中創(chuàng)建,也可能在子線程中創(chuàng)建。主線程中的RunLoop默認是啟動的,所以timer只要添加到主線程RunLoop中就會被執(zhí)行;而子線程中的RunLoop默認是不啟動的,所以timer添加到子線程RunLoop中后,還要手動啟動RunLoop才能使timer被執(zhí)行。
NSTimer只有添加到啟動起來的RunLoop中才會正常運行。NSTimer通常不建議添加到主線程中執(zhí)行,因為界面的更新在主線程中進行,這會影響NSTimer的準確性。
以下代碼為4種情形下NSTimer的正確使用方法。
-(void)viewDidLoad{
[superviewDidLoad];
/*第一種,主線程中創(chuàng)建timer,需要手動添加到RunLoop中*/
NSTimer*mainThreadTimer=[NSTimertimerWithTimeInterval:10.0target:selfselector:@selector(mainThreadTimer_SEL)userInfo:nilrepeats:YES];
/*第二種,主線程中創(chuàng)建timer,不需要手動添加到RunLoop中*/
NSTimer*mainThreadSchduledTimer=[NSTimer
scheduledTimerWithTimeInterval:10.0target:selfselector:@selector(mainThreadSchduledTimer_SEL)userInfo:nilrepeats:YES];
/*將mainThreadTimer添加到主線程runloop*/
[[NSRunLoopcurrentRunLoop]addTimer:mainThreadTimer
forMode:NSDefaultRunLoopMode];
dispatch_asyne(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{
/*第三種,子線程中創(chuàng)建timer,需要手動添加到RunLoop中*/
NSTimer*subThreadTimer=[NSTimertimerWithTimeInterval:10.0target:selfselector:@selector(subThreadTimer_SEL)userInfo:nilrepeats:YES];
/*第三種,子線程中創(chuàng)建timer,不需要手動添加到RunLoop中*/
NSTimer*subThreadSchduledTimer=[NSTimerscheduledTimerWithTimeInterval:10.0target:selfselector:@selector(subThreadSchduledTimer_SEL)userInfo:nilrepeats:YES];
/*將subThreadTimer添加到子線程runloop*/
[[NSRunLoopcurrentRunLoop]addTimer:subThreadTimerforMode:NSDefaultRunLoopMode];
/*啟動子線程mnloop*/
[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];
});
}
-(void)mainThreadTimer_SEL{
NSLog(@"mainThreadTimerisworking!");
}
-(void)mainThreadSchduledTimer_SEL{
NSLog(@"mainThreadSchduledTimerisworking!");
}
-(void)su
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 市場競爭對手分析數(shù)據(jù)表
- 智能制造技術(shù)生產(chǎn)流水線操作手冊
- 三農(nóng)村公共服務(wù)智能化提升方案
- 交通物流行業(yè)綠色運輸策略方案
- 物流行業(yè)無人配送技術(shù)推廣方案
- 附件3醫(yī)院護類人員年終理論考試500題練習卷附答案
- 鄉(xiāng)村綠化美化服務(wù)方案
- 三農(nóng)產(chǎn)品電商助力農(nóng)業(yè)新興業(yè)態(tài)培育與發(fā)展方案
- 餐飲行業(yè)餐飲企業(yè)營銷策略及實施方案
- 高效率辦公軟件使用簡明教程
- 腹部CT應(yīng)用入門
- 2019版外研社高中英語選擇性必修二Unit 1 Growing up 單詞表
- 路基接觸網(wǎng)基礎(chǔ)技術(shù)交底
- 氣瓶充裝安全及培訓課件PPT幻燈片
- (高清版)輻射供暖供冷技術(shù)規(guī)程JGJ142-2012
- JTT 1295—2019道路大型物件運輸規(guī)范_(高清-最新)
- 土壤固化土施工技術(shù)導則
- VAR模型Johansen協(xié)整檢驗在eviews中的具體操作步驟及結(jié)果解釋
- 冷凍面團項目市場分析
- 加油站法律法規(guī)符合性評價
- 5外科--丹毒下肢丹毒中醫(yī)診療方案2017年版
評論
0/150
提交評論