- 代碼塊:一個可以增強(qiáng)函數(shù)功能的Objective-C特性
- 并發(fā)性:如何讓現(xiàn)代設(shè)備同時執(zhí)行多個任務(wù)
1.代碼塊
- 代碼塊對象(簡稱代碼塊):是對C語言中函數(shù)的擴(kuò)展玻粪。除了函數(shù)中的代碼,代碼塊還包含變量綁定结窘。代碼塊有時也被稱為閉包隧枫。
- 代碼塊包含兩種類型的綁定:自動型與托管型谓苟。自動綁定使用的是棧中內(nèi)存涝焙,而托管綁定是通過堆創(chuàng)建的。
1.1代碼塊和指針函數(shù)
代碼塊借鑒了函數(shù)指針的語法赤兴,所以如果你知道如何聲明函數(shù)指針桶良,也就知道了如何聲明一個代碼塊
與函數(shù)指針類似艺普,代碼塊具有以下特征:
- 返回類型可以手動聲明鉴竭,也可以由編譯器推導(dǎo)
- 具有指定類型的參數(shù)列表
- 擁有名稱
void (*my_func)(void);
這是非巢妫基礎(chǔ)的函數(shù)指針璧眠,他沒有參數(shù)和返回結(jié)果。只要把*
換成^
就可以把它轉(zhuǎn)換成一個代碼塊的定義了袁滥。
void (^my_func)(void);
在聲明代碼塊變量和代碼塊實現(xiàn)的開頭位置都要使用冪操作符题翻。與函數(shù)中一樣嵌赠,代碼塊的代碼要放在{}中。
示例:
int (^square_black)(int number) = ^(int number) {
return (number*number);
};
int result = square_black(5);
printf("Result = %d\n",result);
這個代碼塊獲取了一個整型參數(shù)并返回了這個數(shù)字的平方齿税。等號前面是代碼塊的定義凌箕,等號后面是實現(xiàn)內(nèi)容牵舱。
我們一般可以用如下關(guān)系來表示它們:
<returntype> (^blockname)(list of arguements) = ^(arguements){body;};
編譯器可以通過代碼塊的內(nèi)容推導(dǎo)出返回內(nèi)容仆葡,所以你可以省略它志笼。如果代碼塊沒有參數(shù)纫溃,也可以省略紊浩。
1.使用代碼塊
int result = square_block(5);
int value = 6;
int (^multiply_block)(int number) = ^(int number){
return value*number;
};
int result = multiply_block(7);
printf("Result = %d\n",result);
- 因為你將代碼塊聲明成了變量坊谁,所以你可以像函數(shù)一樣使用它滑臊。
- 你可能注意到了雇卷,這行代碼中沒有冪符號关划,是因為只有在定義代碼塊的時候才需要使用它。
- 代碼塊還有一個非晨泗妫酷的特性可用來代替原先的函數(shù):代碼塊可以訪問與它相同的(本地)有效范圍內(nèi)聲明的變量岛都,也就是說代碼塊可以訪問與它同時創(chuàng)建的有效變量臼疫。
2.直接使用代碼塊
NSArray *array = [NSArray arrayWithObjects:@"Amir",@"Mishal",@"Irrum",@"Adam",nil]
NSLog("Unsorted Array %@",array);
NSArray *sortedArray = [array sortedArrayUsingComparator:^(NSString *object1,NSString *object2){
return [object1 compare:object2];
}];
NSLog("Sorted Array %@",sortedArray);
- 使用代碼塊時通常不需要創(chuàng)建一個代碼塊變量烫堤,而是在代碼中內(nèi)聯(lián)代碼塊的內(nèi)容。
- 通常拔创,你會需要一個將代碼塊作為參數(shù)的方法或函數(shù)剩燥。
3.使用typedef關(guān)鍵字
typedef double (^MKSampleMultiply2BlockRef)(double c,double d);
MKSampleMultiply2BlockRef multiply2 = ^(double c,double d){return c*d;};
printf("%f %f",multiply2(4,5),multiply2(5,2));
- 如此長的定義語句讓人生畏灭红,而且易錯变擒,但是我們有typedef寝志。
- 我們定義了一個名稱為MKSampleMultiply2BlockRef的代碼塊變量材部,它包含兩個雙浮點型參數(shù)并返回一個雙浮點型數(shù)值乐导。
4.代碼塊和變量
代碼塊被捕捉到后會捕捉創(chuàng)建點時的狀態(tài)兽叮。代碼塊可以訪問函數(shù)用到的標(biāo)準(zhǔn)類型的變量:
- 全局變量鹦聪,包括在封閉范圍聲明的本地靜態(tài)變量。
- 全局函數(shù)(明顯不是真實的變量)淘太。
- 封閉范圍內(nèi)的參數(shù)蒲牧。
- 函數(shù)級別(即與代碼塊聲明時相同的級別)的_block變量冰抢。他們是可以修改的變量挎扰。
- 封閉范圍內(nèi)的非靜態(tài)變量會被獲取為常量。
- Objective-C的實例變量尽超。
- 代碼塊內(nèi)部的本地變量似谁。
5.本地變量
typedef double (^MKSampleMultiplyBlockRef)(void);
double a = 10,b = 20;
MKSampleMultiplyBlockRef multiply = ^(void){return a*b;};
NSLog(@"%f ",multiply());
a = 20;
b = 50;
NSLog(@"%f ",multiply());
- 本地變量就是與代碼塊在同一范圍內(nèi)聲明的變量巩踏。
- 因為變量是本地的蛀缝,代碼塊會在定義時復(fù)制并保存它們的狀態(tài),所以NSLog的輸出都是200嗤练。
6.全局變量
typedef double (^MKSampleMultiplyBlockRef)(void);
static double a = 10,b = 20;
MKSampleMultiplyBlockRef multiply = ^(void){return a*b;};
NSLog(@"%f ",multiply());
a = 20;
b = 50;
NSLog(@"%f ",multiply());
- 在上面的變量示例中煞抬,我們說過變量與代碼塊擁有相同的有效范圍革答,你可以根據(jù)需要把變量標(biāo)記為靜態(tài)的(全局)
- 這樣残拐,第一次輸出 200溪食,第二次輸出1000
7.參數(shù)變量
typedef double (^MKSampleMultiply2BlockRef)(double c,double d);
MKSampleMultiply2BlockRef multiply2 = ^(double c,double d){return c*d;};
printf("%f %f",multiply2(4,5),multiply2(5,2));
代碼塊中的參數(shù)變量與函數(shù)中的參數(shù)變量具有相同的作用
8._block變量
double c = 3;
MKSampleMultiplyBlockRef multiply = ^(double a,double b){c = a*b};
報錯:Variable is not assignable (missing_block type specifier)
_block double c = 3;
MKSampleMultiplyBlockRef multiply = ^(double a,double b){c = a*b};
- 本地變量會被代碼作為常量獲取到。如果你想要修改它們的值雀瓢,必須將它們聲明為可修改的刃麸,否則編譯會出錯
- 如果想要修復(fù)這個編譯錯誤嫌蚤,你需要聽取前面編譯器的友好建議断傲,將變量c標(biāo)記為_block认罩。
- 有些變量無法聲明為_block類型的垦垂,它們有兩個限制:沒有長度可變的數(shù)組劫拗,沒有包含可變長度數(shù)組的結(jié)構(gòu)體页慷。
9.代碼塊內(nèi)部的本地變量
void (^MKSampleBlockRef)(void) = ^(void){
double a = 4;
double c = 2;
NSLog(@"%f",a*c);
};
MKSampleBlockRef();
**這些變量與函數(shù)中的本地變量具有相同的作用酒繁。**
1.2Objective-C變量
代碼塊是OC語言中的優(yōu)公民州袒,你可以像使用其它對象一樣使用它郎哭。使用時會遇到的最大問題就是內(nèi)存管理。
我們之前也說過邦蜜,在代碼塊中訪問OC變量時必須小心畦徘,以下規(guī)則能幫助你處理內(nèi)存管理:
- 如果引用了一個OC對象井辆,必須要保留它杯缺。
- 如果通過引用訪問了一個實例變量萍肆,要保留一次self(即執(zhí)行方法的對象)。
- 如果通過數(shù)值訪問了一個實例變量包雀,變量需要保留才写。
示例:
NSString *string1 = ^{
return [_theString stringByAppendingString:_theString];
};
_theString是聲明了代碼塊的類里面的實例變量赞草。因為在代碼塊中直接訪問了實例變量,所以包含它的對象(self)需要保留吆鹤。
NSString *localObject = _theString;
NSString *string2 = ^{
return [localObject stringByAppendingString:localObject];
};
在這個例子中厨疙,我們是間接訪問:創(chuàng)建了一個指向?qū)嵗兞康谋镜匾貌⒃诖a塊里面使用。因此這次要保留的對象是localObject疑务,而不是self沾凄。
MKSampleVoidBlockRef block1 = ^{
NSLog(@"Block1");
};
block1();
MKSampleVoidBlockRef block2 = ^{
NSLog(@"Block2");
};
block2();
Block_release(block2);
block2 = Block_copy(block1);
block2();
因為代碼塊是對象,所以可以向它發(fā)送任何與內(nèi)存管理有關(guān)的消息暑始。在C語言級別中婴削,必須使用Block_copy()和Block_release()函數(shù)來適當(dāng)?shù)墓芾韮?nèi)存廊镜。
2并發(fā)性
- 到目前為止,我們所討論過的所有代碼都是一個接著一個按照順序執(zhí)行的唉俗。
- 用來運行Xcode的Mac電腦的處理器至少擁有兩個核心嗤朴,也可能更多。現(xiàn)在的最新iOS設(shè)備都是多核的虫溜。這意味著你可以在同一時間進(jìn)行多項任務(wù)雹姊。
- 蘋果公司提供了多種可以利用多核特性的API。能夠在同一時間執(zhí)行多項任務(wù)的程序稱為并發(fā)的程序衡楞。
- 利用并發(fā)性最基礎(chǔ)的方法是使用POSIX線程來處理程序的不同部分使其能夠獨立執(zhí)行吱雏。
- 編寫并發(fā)性程序需要創(chuàng)建多個線程,因為線程是級別較低的API,你必須手動管理歧杏。根據(jù)硬件和其他軟件運行的環(huán)境镰惦,需要的線程數(shù)量會發(fā)生變化。
- 為了減輕在多核上編程的負(fù)擔(dān)犬绒,蘋果公司引入了GCD(Grand Central Dispatch)旺入。這個技術(shù)減少了不少線程管理的麻煩,因此你可以集中精力編碼凯力。
- 如果想要使用GCD茵瘾,你需要提交代碼塊或函數(shù)作為線程來運行。GCD是一個系統(tǒng)級別(system-level)的技術(shù)咐鹤,因此你可以在任意級別的代碼中使用它拗秘。GCD決定與要多少線程并安排它們運行的進(jìn)度。
- 因為它們是運行在系統(tǒng)級別上的祈惶,所以可以平衡應(yīng)用程序所有內(nèi)容的加載聘殖,這樣可以提高計算機(jī)或者設(shè)備的執(zhí)行效率。
2.1同步
- 我們該如何在由多核組成的通路中管理交通呢行瑞?可以使用同步裝置奸腺,比如在通道入口立一個標(biāo)記(flag)和一個互斥(mutex)。
- mutex是mutual exclusion的縮寫血久,它指的是確保兩個線程不會在同一時間進(jìn)入臨界區(qū)突照。
@synchronized(theObject)
{
//Critical section.
}
- OC提供了一個語言級別的(language-level)關(guān)鍵字@synchronized。這個關(guān)鍵字擁有一個參數(shù)氧吐,通常這個對象是可以修改的讹蘑。它可以確保不同的線程會連續(xù)的訪問臨界區(qū)的代碼。
- 若你定義了一個屬性并且沒有指定關(guān)鍵字nonatomic作為屬性的特性筑舅,編譯器會生成強(qiáng)制彼此互斥的getter和setter方法座慰。
- 編譯器生成了@synchronized(mutex,atomic)語句來確保彼此互斥翠拣,這樣設(shè)置代碼和變量會產(chǎn)生一些消耗版仔,它會比直接訪問更慢一些。
- 如果你不在意误墓,或者知道這些屬性值不會被多個線程訪問的話蛮粮,可以添加nonatomic特性,以提高性能谜慌。
1.選擇性能
如果你只想讓一些代碼在后臺執(zhí)行然想,NSObject也提供了方法。
這些方法的名字都有performSelector:欣范,最簡單的就是performSelectorInBackground:withObject:了变泄,他能在后臺執(zhí)行一個方法
它通過一個創(chuàng)建一個線程來運行方法令哟。定義這些方法時必須遵從以下限制
1.這些方法運行在各自的線程里面,因此你必須為這些Cocoa對象創(chuàng)建一個自動釋放池妨蛹,而主動釋放池是與主線程相關(guān)的励饵。
2.這些方法不能有返回值,并且要么沒有參數(shù)滑燃,要么只有一個參數(shù)對象
//你只能使用以下代碼格式中的一種
- (void) myMethod;
- (void) myMethod:(id) myObject;
記住這些限制役听,我們實現(xiàn)的代碼應(yīng)該如下:
- (void) myBackgroundMethod {
@autoreleasepool{
NSLog(@"My Background Method");
}
}
或者如下所示:
- (void) myOtherBackgroundMethod (id)myObject {
@autoreleasepool{
NSLog(@"My Background Method %@",myObject);
}
}
如果想要在后臺執(zhí)行你的方法,只需要調(diào)動performSelectorInBackground:withObject:方法表窘,就像下面這樣:
[self performSelectorInBackground:@selector(myBackgroundMethod) withObject:nil];
[self performSelectorInBackground:@selector(myOtherBackgroundMethod) withObject:argumentObject];
當(dāng)方法執(zhí)行結(jié)束后典予,OC運行時會特地清理并棄掉線程。需要注意乐严,方法執(zhí)行結(jié)束后并不會通知你瘤袖。
2.調(diào)度隊列
GCD可以使用調(diào)度隊列(dispatch queue),它與線程很相似但使用起來更簡單昂验。只需要寫下你的代碼捂敌,把它指派為一個隊列,系統(tǒng)就會執(zhí)行它既琴。你可以同步或異步執(zhí)行任意代碼占婉。
一共有一下三種類型的隊列:
- 連續(xù)隊列:每個連續(xù)隊列都會根據(jù)指派的順序執(zhí)行任務(wù)。你可以按自己的想法創(chuàng)建任意數(shù)量的隊列甫恩,它們會并行執(zhí)行任務(wù)逆济。
- 并發(fā)隊列:每個并發(fā)隊列都能并發(fā)執(zhí)行一個或多個任務(wù)。任務(wù)會根據(jù)指派到隊列的順序開始執(zhí)行磺箕。你無法連續(xù)創(chuàng)建隊列奖慌,只能從系統(tǒng)提供的3個隊列內(nèi)選擇一個來使用。
- 主隊列:它是應(yīng)用程序中有效的主隊列松靡,執(zhí)行的是應(yīng)用程序的主線程任務(wù)简僧。
死鎖:指的是兩個或多個任務(wù)在等待它方運行結(jié)束。
1.連續(xù)隊列
dispatch_queue_t my_serial_queue;
my_serial_queue = dispatch_queue_create("com.apress.MySerialQueue1",NULL);
- 有時候有一連串任務(wù)需要按照一定的順序執(zhí)行雕欺,這時便可以使用連續(xù)隊列岛马。任務(wù)執(zhí)行順序為先入先出(FIFO):只要任務(wù)是異步提交的,隊列會確保任務(wù)根據(jù)預(yù)定順序執(zhí)行阅茶。這些隊列都是不會發(fā)生死鎖的蛛枚。
- 第一個參數(shù)是隊列的名稱,第二個負(fù)責(zé)提供隊列的特性(現(xiàn)在用不到脸哀,所以必須時NULL)。
- 當(dāng)隊列創(chuàng)建好之后扭吁,就可以給它指派任務(wù)撞蜂。因為隊列是引用計數(shù)的對象盲镶,所以等下我們還要討論內(nèi)存管理。
2.并發(fā)隊列
dispatch_queue_t myQueue蝌诡;
myQueue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
- 并發(fā)調(diào)度隊列適用于那些可以并行運行的任務(wù)溉贿。并發(fā)隊列也遵從先入先出(FTFO)的規(guī)范,且任務(wù)可以在前一個任務(wù)結(jié)束前就開始執(zhí)行浦旱。
- 一次運行的任務(wù)數(shù)量是無法預(yù)測的宇色,它會根據(jù)其它運行的任務(wù)在不同的時間變化所以你每運行同一個程序,并發(fā)任務(wù)的數(shù)量可能會是不一樣的颁湖。(如果需要確保每次運行的任務(wù)數(shù)量都是一樣的宣蠕,可以通過線程API來手動管理線程。)
- 每個應(yīng)用程序都有三種并發(fā)隊列可以使用:高優(yōu)先級(high)甥捺,默認(rèn)優(yōu)先級(default)和低優(yōu)先級(low)抢蚀。如果想要引用它們,可以調(diào)用dispatch_get_global方法镰禾。
- 其他優(yōu)先級的選項分別是DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW皿曲。第二個參數(shù)暫時都用0。
- 因為它們都是全局的吴侦,所以無需為它們管理內(nèi)存屋休。你不需要保留這些隊列的引用,在需要的時候使用函數(shù)來訪問就好了备韧。
3.主隊列
dispatch_queue_get_t main_queue = dispatch_get_current_queue(void);
- 使用dispatch_get_main_queue 可以訪問與應(yīng)用程序主線程線管的連續(xù)隊列博投。
- 因為這個隊列與主線程相關(guān),所以必須小心安排這個隊列中的任務(wù)順序盯蝴,否則它們可能會阻塞主應(yīng)用程序運行毅哗。
- 通常要以同步方式使用這個隊列,提交多個任務(wù)并在它們操作完畢后執(zhí)行一些動作捧挺。
4.獲取當(dāng)前隊列
dispatch_queue_t myQueue = dispatch_get_current_queue();
你可以用過調(diào)用dispatch_get_current_queue()來找出當(dāng)前運行的隊列代碼塊虑绵。如果你在代碼塊對象之外調(diào)用了這個函數(shù),則它將會返回主隊列闽烙。
2.2隊列也要內(nèi)存管理
2.調(diào)度程序
添加任務(wù)的最簡單的的方法是通過代碼翅睛。代碼塊必須是dispatch_block_t這樣的類型,要定義沒有參數(shù)和返回值才行黑竞。
typedef void(^dispatch_block_t)(void)
先添加異步代碼塊捕发。這個函數(shù)擁有兩個參數(shù),分別是隊列和代碼塊很魂。
dispatch_async(_serial_queue,^{NSLog(@"Serial Task 1");});
如果你愿意的話扎酷,也可以定義一個代碼塊對象,而不是通過內(nèi)聯(lián)方式來創(chuàng)建遏匆。
dispatch_block_t myBlock = ^{NSLog(@"My Predfined block");};
dispatch_async(_serial_queue,myBlock);
之前說過法挨,我們也可以向隊列中添加函數(shù)谁榜。函數(shù)的標(biāo)準(zhǔn)原型必須要像下面這樣:
void function_name(void *arguement)
以下是示例函數(shù):
void myDispatchFuction(void *arguement){
NSLog(@"Serial Task %@",(_bridge NSNumber *)arguement);
NSMutableDictionary *context = (_bridge NSMutableDictionary *)dispatch_get_context(dispatch_get_current_queue());
NSNumber *value = [context objectForKey:@"value"];
NSLog(@"value = %@",value);
}
接下來需要向隊列添加這個函數(shù)。調(diào)度函數(shù)擁有三個參數(shù):隊列凡纳,需要傳遞的任意上下文以及函數(shù)窃植。如果你沒有信息要發(fā)送給還是你函數(shù),也可以只傳遞一個NULL值荐糜。
dispatch_async_f(_serial_queue,(_bridge void *)[NSNumber numberWithInt:3],(dispatch_function_t)myDispatchFuction);
隊列創(chuàng)建完之后巷怜,就做好接收任務(wù)的準(zhǔn)備了。當(dāng)我們添加了一個任務(wù)暴氏,隊列就會安排好它的進(jìn)度延塑。如果想要以同步方式添加到隊列中,請調(diào)用dispatch_sync_f函數(shù)偏序。
如果出于某個原因你想要暫停隊列页畦,請調(diào)用dispatch_susend()函數(shù)并傳遞隊列名稱。
dispatch_suspend(_serial_queue);
隊列暫停之后研儒,可以調(diào)用dispatch_resume()函數(shù)來重新啟用豫缨。這些全都有你來掌控。
dispatch_resume(_serial_queue);
2.3操作隊列
- 這些東西的層級都很低端朵,你一定想要知道怎樣能讓它在OC中使用好芭。
- 實際上,有一些被稱為操作的API冲呢,可以讓隊列在OC層級上使用起來更加簡單舍败。
-
如果想要使用操作,首先需要創(chuàng)建一個操作對象敬拓,然后將其指派給操作隊列邻薯,并讓隊列執(zhí)行它。一共有三種創(chuàng)建操作的方式乘凸。
1.NSInvocationOperation:如果你已經(jīng)擁有一個可以完成工作的類厕诡,并且想要在隊列上執(zhí)行,可以嘗試使用這種方法营勤。
2.NSBlockOperation:這有些像包含了需要執(zhí)行代碼塊的dispatch_async函數(shù)灵嫌。
3.自定義的操作:如果你需要更靈活的操作類型,可以創(chuàng)建自己的自定義類型葛作。你必須通過NSOperation子類來定義你的操作寿羞。
創(chuàng)建調(diào)用操作
NSInvocationOperation會為執(zhí)行任務(wù)的類調(diào)用選擇器。因此赂蠢,如果你擁有一個包含所需方法的類绪穆,使用這種方式創(chuàng)建會非常方便。
@implementation MyCustomClass
- (NSOperation *)operationWithData:(id)data{
return[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myWorkerMethod:) object:data];
}
//This is the method that does the actual work
- (void)myWorkerMethod:(id)data{
NSLog(@"My Worker Method %@",data);
}
@end
一旦向隊列添加了操作,任務(wù)即將執(zhí)行時便會調(diào)用類里面的myWorkermethod:方法霞幅。
-
創(chuàng)建代碼塊操作
如果你有一個需要執(zhí)行的代碼塊漠吻,那么可以創(chuàng)建這個操作并讓隊列執(zhí)行它量瓜。
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
//Do my work
}];
這些操作的代碼塊與我們在調(diào)度隊列中使用的是同一種類型司恳。一旦創(chuàng)建了第一個代碼塊操作,你便可以通過addExecutionBlock:方法繼續(xù)添加更多的代碼塊绍傲。根據(jù)隊列的類型(連續(xù)的或并發(fā)的)代碼塊會分別以連續(xù)或并行的方式運行扔傅。
[blockOperation addExecutionBlock:^{
//do my work
}];
-
向隊列中添加操作
1.一旦創(chuàng)建了操作,你就需要向隊列中添加代碼塊烫饼。這次我們將使用NSOperationQueue來取代之前使用的dispatch_queue_t函數(shù)猎塞。NSOperationQueue一般會并發(fā)執(zhí)行操作。它具有相關(guān)性杠纵,因此如果某操作是基于其它操作的荠耽,它們會相應(yīng)的執(zhí)行。
2.如果要確保你的操作是連續(xù)執(zhí)行的比藻,可以設(shè)置最大并發(fā)操作數(shù)為1铝量,這樣任務(wù)將會按照先入先出的規(guī)范執(zhí)行。在向隊列添加操作之前银亲,需要某個方法來引用到那個隊列慢叨。你可以創(chuàng)建一個新的隊列或使用之前已經(jīng)定義過的隊列(比如當(dāng)前運行的隊列):NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];
或主隊列:NSOperationQueue *mainQueue = [NSOperationQueue mainqueue];
以下就是我們創(chuàng)建自己隊列的代碼:
NSOperationQueue *_operationQueue = [[NSOperationQueue alloc] init];
現(xiàn)在我們擁有了一個隊列,可以使用addOperation:來添加操作:
[theQueue addOperation:blockOperation];
也可以添加需要執(zhí)行的代碼塊來代替操作對象:
[theQueue addOperationWithBlock:^{
NSLog(@"My Block");
}];
一旦向隊列中添加了操作务蝠,他就會被安排進(jìn)度并執(zhí)行拍谐。