塊和大中樞派發(fā)
第37條:理解”塊“這一概念
Block的了解還是很有必要的屁桑,它和函數類似拯爽,只不過是直接定義在另一個函數里的最蕾,和定義它的那個函數共享同一個范圍的東西。用
^
來表示源内,后面接一對花括號,括號里是blcok
的實現代碼份殿。
void(^someBlcok)() = ^{
NSLog(@"there has someBlock");
};
someBlcok(); // print:“there has someBlock”
格式: 返回類型 (^Block Name)(參數){};
int (^addBlock)(int a, int b) = ^(int a ,int b){
return a + b;
};
int testAdd = addBlock(7,5);
NSLog(@"testAdd === %d",testAdd); // print: “testAdd === 12”
int additonal = 5;
int (^minusBlock)(int a, int b) = ^(int a,int b){
return additonal - a - b;
};
int testMinus = minusBlock(2,6);
NSLog(@"minus === %d",testMinus); //print :"minus === -3"
注意當要改變塊里面的變量的時候膜钓,需要加上
__block
修飾符
NSArray * array = @[@0,@1,@2,@3,@4,@5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(NSNumber * number,NSUInteger idx,BOOL *stop){
if([number compare:@4] == NSOrderedAscending)
{
count++;
}
}];
NSLog(@"count ==== %lu",count); // print : "count ==== 4"
注意還可以使用反序和順序
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts
usingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block
typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
NSEnumerationConcurrent = (1UL << 0), // 順序
NSEnumerationReverse = (1UL << 1), // 倒序
};
定義
block
的時候,所占的內存區(qū)域是分配在棧中的卿嘲,也就是block
只在定它的那個范圍內有效颂斜。所以我們我們需要對其對象發(fā)送copy
消息以拷貝值,這樣的話拾枣,就可以把塊從棧復制到堆了沃疮,塊這樣就成了帶引用計數的對象啦。
第38條:為常用的塊類型創(chuàng)建typedef
由于在定義塊變量時梅肤,需要把變量名放在類型之中司蔬,而不要放在右側,這樣非常難記姨蝴,也非常難懂俊啼,鑒于此,我們應該為常用的塊類型起個別名左医,此時
typedef
關鍵字就用到了授帕。
typedef int (^YPQBlcok)(BOOL flag,int value);
第39條:用handler塊降低代碼分散程度
當用Handler塊的時候,可以直接將塊和相關對象放在一起浮梢。這樣代碼更清晰而緊湊跛十。
#import <Foundation/Foundation.h>
typedef void(^YPQNetworkCompletionHandler)(NSData * data);
@interface YPQNetworkFetcher: NSObject
@property (nonatomic, readonly, strong) NSURL * url;
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion;
@end
注意實現文件中 的 copy
#import "YPQNetworkFetcher.h"
@interface YPQNetworkFetcher()
@property (nonatomic, readwrite, strong)NSURL * url;
@property (nonatomic, copy)YPQNetworkCompletionHandler completionHandle;
@property (nonatomic, strong) NSData * downloadedData;
@end
@implementation YPQNetworkFetcher
- (id)initWithURL:(NSURL *)url
{
if(self = [super init])
{
_url = url;
}
return self;
}
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion
{
self.completionHandle = comletion;
// 開始 request
// downloadedData 獲值
// 當申請完成,調用 [self p_requestCompleted];
}
- (void)p_requestCompleted
{
if(_completionHandle)
{
_completionHandle(_downloadedData);
}
}
@end
第40條:用塊引用其所屬對象時不要出現保留環(huán)
就是我們使用Block的時候很容易出現循環(huán)引用秕硝。
#import "ViewController.h"
#import "YPQNetworkFetcher.h"
@interface ViewController ()
{
YPQNetworkFetcher * _networkFetcher;
}
@property (nonatomic ,strong) NSData * downloadData;
@end
- (void)downloadTheData
{
NSURL * url = [NSURL URLWithString:@"https://www.example.com/...."];
_networkFetcher = [[YPQNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData * data){
self.downloadData = data;
}];
}
這樣看起來沒問題芥映,但是已經形成循環(huán)引用啦
-
block
保留了viewController
實例; -
viewController
實例則通過_networkFetcher
保留YPQNetworkFetcher
; -
_completionHandle
又保留了block
屏轰。
這樣環(huán)就形成了颊郎,打破環(huán)就得打破其中某一個環(huán)節(jié),比較好用的方法可以通過;
// 打破上述的第一環(huán)節(jié)
__weak __typeof(self) weakSelf = self;
[_networkFetcher startWithCompletionHandler:^(NSData * data){
weakSelf.downloadData = data;
}];
或者
// 打破上述的第二環(huán)節(jié)
[_networkFetcher startWithCompletionHandler:^(NSData * data){
self.downloadData = data;
_networkFetcher = nil;
}];
第41條:多用派發(fā)隊列霎苗,少用同步鎖
如果多個線程要執(zhí)行同一份代碼的時候姆吭,通常都要使用鎖實現某種同步機制。
@synchronized(self) {
//safe
}
_lock = [[NSLock alloc] init];
[_lock lock];
//safe
[_lock unlock];
但是這兩種方式效率不高唁盏,而且也無法提供絕對的線程安全内狸。有種簡單而高效的辦法可以代替他們,那即是使用”串行同步隊列“厘擂,將讀取操作和寫入操作都安排在同一個隊列中昆淡。
_syncQuene = dispatch_queue_create("com.yang.test", NULL); - (NSString *)testGCDString
{
__block NSString * localString;
dispatch_sync(_syncQuene, ^{
localString = _testGCDString;
});
return localString;
}
- (void)setTestGCDString:(NSString *)testGCDString
{
dispatch_sync(_syncQuene, ^{
_testGCDString = testGCDString;
});
}
當然還可以繼續(xù)優(yōu)化, 將同步派發(fā)改為異步派發(fā),并且由于多個獲取方法可以并發(fā)執(zhí)行刽严,而獲取方法和設置方法之間不能并發(fā)執(zhí)行昂灵,此時改用并發(fā)隊列。
_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
__block NSString * localString;
dispatch_sync(_syncQuene, ^{
localString = _testGCDString;
});
return localString;
}
- (void)setTestGCDString:(NSString *)testGCDString
{
dispatch_async(_syncQuene, ^{
_testGCDString = testGCDString;
});
}
但是像上面這樣舞萄,還是無法正確實現同步眨补。讀取和寫入可以隨時執(zhí)行,為了不讓其任意執(zhí)行倒脓,此時dispatch_barrier_async
就出現啦
_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
__block NSString * localString;
dispatch_sync(_syncQuene, ^{
localString = _testGCDString;
});
return localString;
}
- (void)setTestGCDString:(NSString *)testGCDString
{
dispatch_barrier_async(_syncQuene, ^{
_testGCDString = testGCDString;
});
}
dispatch_barrier_async
必須單獨執(zhí)行撑螺,不能與其他塊并行。這只對并發(fā)隊列有意義崎弃,因為串行隊里中塊總是按順序逐個來執(zhí)行的甘晤。并發(fā)隊列如果發(fā)現接下來要處理的塊是個barrier block
,那么就一直要等到當前所有并發(fā)塊都執(zhí)行完畢,才會單獨執(zhí)行這個barrier block
饲做。待barrier block
執(zhí)行過后线婚,再按正常方式繼續(xù)向下處理。
在上面這個隊列中艇炎,在寫入操作用了
dispatch_barrier_async
來實現后酌伊,對屬性的讀取操作依然可以并行,但寫入操作必須單獨執(zhí)行缀踪。當然設置函數中居砖,我們也可以用dispatch_barrier_sync
同步來實現,有時可能更高效驴娃,看具體場景吧奏候。
將同步與異步派發(fā)結合起來,實現與普通加鎖機制一樣的同步行為唇敞,也不會阻塞執(zhí)行異步派發(fā)的線程蔗草,所以是 OK 的咒彤。
第42條:多用GCD,少用performSelector
在我們用到推遲執(zhí)行方法的時候咒精,我們可以有種選擇镶柱。
// performSelector
[self performSelector:@selector(afterThreeSecondBeginAction)
withObject:nil
afterDelay:3.0f];
// dispatch after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
[self afterThreeSecondBeginAction];
});
performSelector
系列方法在內存管理方面容易疏失,并且所處理的選擇子太過局限了模叙,GCD 作為純C的API歇拆,一般擔憂還是沒必要的,所以說少用performSelector
還是有理由的范咨。
第43條:掌握GCD及操作隊列的使用時機
簡單的說故觅,不要過度使用GCD,要合理使用它渠啊,這個可能需要項目經驗的積累的输吏。像
NSOperationQueue
類也可以多了解下。
第44條:通過Dispatch Group 機制,根據系統(tǒng)資源狀況執(zhí)行任務
dispatch group
是GCD的一項特性替蛉,能夠把任務分組贯溅。
這里是GCD的詳細介紹,推薦一篇文章@ 少君的GCD
第45條:使用dispatch_once 來執(zhí)行只需要運行一次的線程安全代碼
static id _instace;
+ (instancetype)sharedInstanceWithSome
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[self alloc] init];
});
return _instace;
}
使用
dispatch_once
可以簡化代碼并且徹底保證線程安全灭返,我們根本無須擔心加鎖或同步盗迟,另外它沒有使用重量級的同步機制,所以也更高效熙含。
第46條:不要使用dispatch_get_current_queue
其實這個已經在iOS6.0之后被棄用了,它可以做調試艇纺,但實際也用的不多怎静,所以一般我想應該是不會用到吧。
持續(xù)筆記中····