@qidiandasheng
2022-08-07T21:42:01.000000Z
字数 19273
阅读 605
iOS实战
如果在主线程中存储数据,数据量比较大时会阻塞主线程造成页面卡顿。需要新开线程在后台处理。
然而新开的线程过多也会引起过多的内存和线程上下文切换的性能损耗问题,所以类似数据库这种需要频繁读写磁盘操作的任务,尽量使用串行队列来管理,避免因为多线程并发而出现内存问题。
在进行数据读写操作时,总是需要一段时间来等待磁盘响应的,如果在这个时候通过 GCD 发起了一个任务,那么 GCD 就会本着最大化利用 CPU 的原则,会在等待磁盘响应的这个空档,再创建一个新线程来保证能够充分利用 CPU。
而如果 GCD 发起的这些新任务,都是类似于数据存储这样需要等待磁盘响应的任务的话,那么随着任务数量的增加,GCD 创建的新线程就会越来越多,从而导致内存资源越来越紧张,等到磁盘开始响应后,再读取数据又会占用更多的内存。结果就是,失控的内存占用会引起更多的内存问题。
Hermes项目里使用异步串行队列来进行数据库的写入。
//创建串行队列
static let writeQueue = DispatchQueue(label: "chat.rocket.realm.write", qos: .background)
public func execute(_ execution: @escaping (Realm) -> Void, completion: VoidCompletion? = nil) {
var backgroundTaskId: UIBackgroundTaskIdentifier?
//创建后台任务,当app退入后台时也能继续执行一小段时间
backgroundTaskId = UIApplication.shared.beginBackgroundTask(withName: "chat.rocket.realm.background") {
backgroundTaskId = UIBackgroundTaskInvalid
}
if let backgroundTaskId = backgroundTaskId {
let config = self.configuration
//在异步串行队列里执行数据库写入任务
Realm.writeQueue.async {
if let realm = try? Realm(configuration: config) {
try? realm.write {
execution(realm)
}
}
if let completion = completion {
DispatchQueue.main.async {
completion()
}
}
UIApplication.shared.endBackgroundTask(backgroundTaskId)
}
}
}
SDImageCache
是SDWebImage
的缓存处理模块。SDImageCache
分为内存缓存和磁盘缓存。
内存缓存通过一个信号量锁来处理多线程安全问题:
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
[super setObject:obj forKey:key cost:g];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key && obj) {
// Store weak cache
LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
UNLOCK(self.weakCacheLock);
}
}
- (id)objectForKey:(id)key {
id obj = [super objectForKey:key];
if (!self.config.shouldUseWeakMemoryCache) {
return obj;
}
if (key && !obj) {
// Check weak cache
LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
UNLOCK(self.weakCacheLock);
if (obj) {
// Sync cache
NSUInteger cost = 0;
if ([obj isKindOfClass:[UIImage class]]) {
cost = SDCacheCostForImage(obj);
}
[super setObject:obj forKey:key cost:cost];
}
}
return obj;
}
- (void)removeObjectForKey:(id)key {
[super removeObjectForKey:key];
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key) {
// Remove weak cache
LOCK(self.weakCacheLock);
[self.weakCache removeObjectForKey:key];
UNLOCK(self.weakCacheLock);
}
}
磁盘缓存通过一个异步串行队列来读写数据:
//创建串行队列
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
//写入本地磁盘
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// if memory cache is enabled
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}
if (toDisk) {
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && image) {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
SDImageFormat format;
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
[self _storeImageDataToDisk:data forKey:key];
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}
//从本地磁盘读取
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
BOOL exists = [self _diskImageDataExistsWithKey:key];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}
如果使用多次数的循环语句,或者是使用非常耗时的api时,会影响到主线程导致卡顿。可以新开线程在后台处理,然后如果有需要刷新UI则在主线程中同步。
Hermes项目里websocket长链接会接到很多消息的回调,这些回调可能会拖慢主线程的时间,所以放入一个异步线程串行的执行消息回调。
//创建一个串行队列,优先级为background
static let messageHandlerQueue = DispatchQueue(label: "chat.rocket.websocket.handler", qos: .background)
public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
let json = JSON(parseJSON: text)
// JSON is invalid
guard json.exists() else {
Log.debug("[WebSocket] \(socket.currentURL)\n - did receive invalid JSON object:\n\(text)")
return
}
if let raw = json.rawString() {
Log.debug("[WebSocket] \(socket.currentURL)\n - did receive JSON message:\n\(raw)")
}
//收到websocket的消息时异步的串行执行回调任务
SocketManager.messageHandlerQueue.async {
self.handleMessage(json, socket: socket)
}
}
static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
//最大队列数量
#define MAX_QUEUE_COUNT 16
//队列数量
static int queueCount;
//使用栈区的数组存储队列
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
static dispatch_once_t onceToken;
static int32_t counter = 0;
dispatch_once(&onceToken, ^{
//要点 1 :串行队列数量和处理器数量相同
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
//要点 2 :创建串行队列,设置优先级
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);
}
} else {
for (NSUInteger i = 0; i < queueCount; i++) {
queues[i] = dispatch_queue_create("com.ibireme.yykit.render", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
}
}
});
//要点 3 :轮询返回队列
int32_t cur = OSAtomicIncrement32(&counter);
if (cur < 0) cur = -cur;
return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
}
dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
//绘制
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
.....
UIGraphicsEndImageContext();
//回到主线程显示
dispatch_async(dispatch_get_main_queue(), ^{
if (isCancelled()) {
if (task.didDisplay) task.didDisplay(self, NO);
} else {
self.contents = (__bridge id)(image.CGImage);
if (task.didDisplay) task.didDisplay(self, YES);
}
});
}
串行队列数量和处理器数量相同
设置多个串行队列,串行队列的数量等于处理器核心的数量,这样就可以进行真正的并行执行任务了。当你开辟的线程过多,超过了处理器核心数量,实际上某些并行的线程之间就可能竞争同一个处理器的资源,频繁的切换上下文也会消耗处理器资源。
创建串行队列,设置优先级
队列的优先级为 QOS_CLASS_USER_INITIATED
,低于用户交互相关的QOS_CLASS_USER_INTERACTIVE
。主队列的优先级是大于或等于QOS_CLASS_USER_INTERACTIVE
的,让这些串行队列的优先级低于主队列,避免框架创建的线程和主线程竞争资源。
为什么不用一个并发队列
因为并行队列无法精确的控制线程数量,很有可能创建过多的线程,导致 CPU 线程调度过于频繁,影响交互性能。
或者我认为可以使用NSOperationQueue
:
#define MAX_QUEUE_COUNT 16
static int queueCount;
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = queueCount;
NSBlockOperation *p = [NSBlockOperation blockOperationWithBlock:^{
//绘制
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
.....
UIGraphicsEndImageContext();
//回到主线程显示
dispatch_async(dispatch_get_main_queue(), ^{
if (isCancelled()) {
if (task.didDisplay) task.didDisplay(self, NO);
} else {
self.contents = (__bridge id)(image.CGImage);
if (task.didDisplay) task.didDisplay(self, YES);
}
});
}];
[queue addOperation:p];
原因一:
如果我们放到主线程去做,势必要这么写:
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]
这样NSURLConnection
的回调会被放在主线程中NSDefaultRunLoopMode
中,这样我们在其它类似UITrackingRunLoopMode
模式下,我们是得不到网络请求的结果的,这显然不是我们想要的,那么我们势必需要调用:
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
把它加入NSRunLoopCommonModes
中,试想如果有大量的网络请求,同时回调回来,就会影响我们的UI体验了。
原因二:
如果我们请求数据返回,势必要进行数据解析,解析成我们需要的格式,那么这些解析都在主线程中做,给主线程增加额外的负担。
又或者我们回调回来开辟一个新的线程去做数据解析,那么我们有n个请求回来开辟n条线程带来的性能损耗,以及线程间切换带来的损耗,是不是一笔更大的开销。
如果开发n条线程做请求的话,那就需要设置runloop保活住线程,等待结果回调。为了等待不确定的请求结果,阻塞住线程,白白浪费n条线程的开销。
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
.....
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
.....
}
- (NSURLSession *)session {
@synchronized (self) {
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
}
}
return _session;
}
这个operationQueue
就是我们代理回调的queue。这里把代理回调的线程并发数设置为1了。
这里的并发数仅仅是回调代理的线程并发数。而不是请求网络的线程并发数。请求网络是由NSUrlSession
来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket
去发送请求和接收数据。这些线程是并发的。
static dispatch_queue_t url_session_manager_processing_queue() {
static dispatch_queue_t af_url_session_manager_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_url_session_manager_processing_queue;
}
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
#并发队列数据解析
......
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}
按照初始化设置,这里的回调是串行回调回来的,只会开辟一条线程,没必要为不确定的网络请求回调开辟多条线程,如果网络一直在等待的话那就会浪费线程资源。
当数据串行回调回来之后,就使用并发队列创建多条线程来进行数据的解析了,最后放到一个GCD group
里面,使用者可以自己设置group,那就可以监听所有的回调完成。
我们一开始初始化sessionManager的时候,一般都是在主线程
然后我们调用get或者post等去请求数据,接着会进行request拼接,AF代理的字典映射,progress的KVO添加等等,到NSUrlSession的resume之前这些准备工作,仍旧是在主线程中的。
然后我们调用NSUrlSession的resume,接着就跑到NSUrlSession内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。
紧接着数据请求完成后,回调回来在我们一开始生成的并发数为1的NSOperationQueue
中,这个时候会是多线程串行的回调回来的。
然后我们到返回数据解析那一块,我们自己又创建了并发的多线程,去对这些数据进行了各种类型的解析。
最后我们如果有自定义的completionQueue,则在自定义的queue中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。
SDWebImageDownloader
为图片下载器对象,里面主要管理着SDWebImageDownloaderOperation
进行对图片的下载。
每一个图片下载都是一个SDWebImageDownloaderOperation
操作,SDWebImageDownloaderOperation
继承自NSOperation
,内部创建NSURLSessionTask
来进行图片下载的请求(NSURLSession
由SDWebImageDownloader
传入)。
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_request = [request copy];
_shouldDecompressImages = YES;
_options = options;
_callbackBlocks = [NSMutableArray new];
_executing = NO;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
_callbacksLock = dispatch_semaphore_create(1);
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
if (!session) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
}
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
if (self.dataTask) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
if ([self.dataTask respondsToSelector:@selector(setPriority:)]) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
}
}
#pragma clang diagnostic pop
[self.dataTask resume];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]];
[self done];
return;
}
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
SDWebImageDownloader
维护着一个下载队列downloadQueue
,默认最大并发数为6,也可以自定义。
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
SDHTTPHeadersMutableDictionary *headerDictionary = [SDHTTPHeadersMutableDictionary dictionary];
NSString *userAgent = nil;
.......
_HTTPHeaders = headerDictionary;
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
_downloadTimeout = 15.0;
//创建NSURLSession
[self createNewSessionWithConfiguration:sessionConfiguration];
}
return self;
}
//创建对应的operation放入下载队列中(初始化创建的NSURLSession传入每一个operation中)
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
LOCK(self.operationsLock);
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
// There is a case that the operation may be marked as finished, but not been removed from `self.URLOperations`.
if (!operation || operation.isFinished) {
operation = [self createDownloaderOperationWithUrl:url options:options];
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
[self.downloadQueue addOperation:operation];
}
UNLOCK(self.operationsLock);
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
在SDWebImageDownloader
中实现NSURLSessionDataDelegate
和NSURLSessionTaskDelegate
委托方法,然后根据回调的taskid
找到对应的operation
,执行对应的operation
里的回调方法:
#pragma mark NSURLSessionDataDelegate
.......
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
可使用任务组和栅栏处理多个异步操作完成之后再执行后面的操作的情况。比如获取两个异步请求数据后才渲染界面的情况。
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"A");
dispatch_group_leave(group);
}] ;
[task resume];
dispatch_group_enter(group);
NSURLSessionDataTask *task2 = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"B");
dispatch_group_leave(group);
}] ;
[task2 resume];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"all end");
});
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
__block BOOL isExecuted = NO;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"A");
isExecuted = YES;
}] ;
[task resume];
while (isExecuted == NO) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
});
dispatch_async(queue, ^{
__block BOOL isExecuted = NO;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"B");
isExecuted = YES;
}] ;
[task resume];
while (isExecuted == NO) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
});
//让barrier之前的线程执行完成之后才会执行barrier后面的操作
dispatch_barrier_async(queue, ^{
__block BOOL isExecuted = NO;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"拿到了A的值");
isExecuted = YES;
}] ;
[task resume];
while (isExecuted == NO) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
});