如何停止或取消调度块的执行
How to stop or cancel execution of dispatch block
我想同时执行多个调度块。因此,当我 运行 第二个调度块同时进行任何调度块时,我想停止执行前一个调度块。
我正在使用下面的代码:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:item.URL];
dispatch_async(dispatch_get_main_queue(), ^(void){
NSError *error = nil;
self.player =[[AVAudioPlayer alloc] initWithData:data fileTypeHint:AVFileTypeMPEGLayer3 error:&error];
NSLog(@"%@",error);
});
});
我也试过下面的代码。但是如果我使用下面的代码可以取消之前的块但是我的应用程序挂起
//Use NSOperationQueue
myQueue = [NSOperationQueue mainQueue];
[myQueue addOperationWithBlock:^{
// Background work
NSData *data = [NSData dataWithContentsOfURL:item.URL];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Main thread work (UI usually)
NSError *error = nil;
self.player =[[AVAudioPlayer alloc] initWithData:data fileTypeHint:AVFileTypeMPEGLayer3 error:&error];
NSLog(@"%@",error);
}];
}];
提前致谢,
这里不需要调度队列或操作队列。
您只需要能够使用 NSURLSession
启动异步下载会话,并在下载成功后启动异步 AVAudioPlayer
。因为这些是异步任务,所以您可以分别 cancel
或 stop
它们。
这是一个简单的例子:
@class Song;
@protocol SongDelegate <NSObject>
- (void)song:(Song *)song didFinishPlayingSuccessfully:(BOOL)flag;
- (void)song:(Song *)song didFinishDownloadWithError:(NSError *)error;
@end
@interface Song: NSObject <AVAudioPlayerDelegate>
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, weak) NSURLSessionTask *downloadTask;
@property (nonatomic, strong) NSURL *localURL;
@property (nonatomic, strong) AVAudioPlayer *player;
@property (nonatomic, weak) id<SongDelegate> delegate;
@end
@implementation Song
+ (instancetype)songWithURL:(NSURL *)url delegate:(id<SongDelegate>)delegate {
Song *song = [[Song alloc] init];
song.url = url;
song.delegate = delegate;
return song;
}
- (void)downloadAndPlay {
self.downloadTask = [[NSURLSession sharedSession] downloadTaskWithURL:self.url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate song:self didFinishDownloadWithError:error];
});
NSURL *documentsURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error];
NSAssert(documentsURL, @"URLForDirectory failed: %@", error);
NSURL *fileURL = [documentsURL URLByAppendingPathComponent:self.url.lastPathComponent];
NSError *moveError;
BOOL success = [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&moveError];
NSAssert(success, moveError.localizedDescription);
// note, the only reason we dispatch the following is that this completion handler runs on background queue and we want to update properties and start the player from the main queue
dispatch_async(dispatch_get_main_queue(), ^{
self.localURL = fileURL;
NSError *playError;
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&playError];
self.player.delegate = self;
[self.player play];
NSAssert(playError == nil, playError.localizedDescription);
});
}];
[self.downloadTask resume];
}
- (void)cancel {
[self.downloadTask cancel]; // if download still in progress, stop it
[self.player stop]; // if playing, stop it
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
self.player = nil;
[self.delegate song:self didFinishPlayingSuccessfully:flag];
}
@end
因此您可以看到 downloadAndPlay
启动异步下载,完成后,开始异步播放曲目。 cancel
方法取消下载并停止播放。
然后你可以像这样使用它:
@interface ViewController () <SongDelegate>
@property (nonatomic, strong) Song *song;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.song = [Song songWithURL:[NSURL URLWithString:@"http://dl.last.fm/static/1464677535/131211148/70b3b5a9d048c7939d5bb9ec87a2c5d58d6ee528828f5c6a5b7b1eddd69f4553/Death+Grips+-+Get+Got.mp3"] delegate:self];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)didTapPlayButton:(id)sender {
[self.song downloadAndPlay];
}
- (IBAction)didTapStopButton:(id)sender {
[self.song cancel];
}
- (void)song:(Song *)song didFinishPlayingSuccessfully:(BOOL)flag {
NSLog(@"did finish playing %@", flag ? @"successfully" : @"unsuccessfully");
}
- (void)song:(Song *)song didFinishDownloadWithError:(NSError *)error {
NSLog(@"did finish download with error %@", error.localizedDescription);
}
@end
现在,很明显,这是一个微不足道的实现(如果你有任何错误,你真的不想做 NSAssert
,而是优雅地处理它,你想处理一系列 Song
对象,您可能希望将下载与播放分离,以便您可以在播放歌曲 1 的同时开始下载歌曲 2,等等),但它说明了取消下载或播放歌曲的更广泛概念,两者都是已经是异步任务,因此不需要调度队列或操作队列。如果你想变得花哨,你可以这样做,但这是一个更高级的话题。
顺便说一下,NSURLSession
非常严格地禁止非 https 请求,因为它们会带来安全风险,但您可以编辑 info.plist
(右键单击它然后说 "Open As" - "Source code") 然后输入如下内容:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>dl.last.fm</key>
<dict>
<!--Include to allow subdomains-->
<key>NSIncludesSubdomains</key>
<true/>
<!--Include to allow HTTP requests-->
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--Include to specify minimum TLS version-->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
我想同时执行多个调度块。因此,当我 运行 第二个调度块同时进行任何调度块时,我想停止执行前一个调度块。
我正在使用下面的代码:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:item.URL];
dispatch_async(dispatch_get_main_queue(), ^(void){
NSError *error = nil;
self.player =[[AVAudioPlayer alloc] initWithData:data fileTypeHint:AVFileTypeMPEGLayer3 error:&error];
NSLog(@"%@",error);
});
});
我也试过下面的代码。但是如果我使用下面的代码可以取消之前的块但是我的应用程序挂起
//Use NSOperationQueue
myQueue = [NSOperationQueue mainQueue];
[myQueue addOperationWithBlock:^{
// Background work
NSData *data = [NSData dataWithContentsOfURL:item.URL];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Main thread work (UI usually)
NSError *error = nil;
self.player =[[AVAudioPlayer alloc] initWithData:data fileTypeHint:AVFileTypeMPEGLayer3 error:&error];
NSLog(@"%@",error);
}];
}];
提前致谢,
这里不需要调度队列或操作队列。
您只需要能够使用 NSURLSession
启动异步下载会话,并在下载成功后启动异步 AVAudioPlayer
。因为这些是异步任务,所以您可以分别 cancel
或 stop
它们。
这是一个简单的例子:
@class Song;
@protocol SongDelegate <NSObject>
- (void)song:(Song *)song didFinishPlayingSuccessfully:(BOOL)flag;
- (void)song:(Song *)song didFinishDownloadWithError:(NSError *)error;
@end
@interface Song: NSObject <AVAudioPlayerDelegate>
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, weak) NSURLSessionTask *downloadTask;
@property (nonatomic, strong) NSURL *localURL;
@property (nonatomic, strong) AVAudioPlayer *player;
@property (nonatomic, weak) id<SongDelegate> delegate;
@end
@implementation Song
+ (instancetype)songWithURL:(NSURL *)url delegate:(id<SongDelegate>)delegate {
Song *song = [[Song alloc] init];
song.url = url;
song.delegate = delegate;
return song;
}
- (void)downloadAndPlay {
self.downloadTask = [[NSURLSession sharedSession] downloadTaskWithURL:self.url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate song:self didFinishDownloadWithError:error];
});
NSURL *documentsURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error];
NSAssert(documentsURL, @"URLForDirectory failed: %@", error);
NSURL *fileURL = [documentsURL URLByAppendingPathComponent:self.url.lastPathComponent];
NSError *moveError;
BOOL success = [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&moveError];
NSAssert(success, moveError.localizedDescription);
// note, the only reason we dispatch the following is that this completion handler runs on background queue and we want to update properties and start the player from the main queue
dispatch_async(dispatch_get_main_queue(), ^{
self.localURL = fileURL;
NSError *playError;
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&playError];
self.player.delegate = self;
[self.player play];
NSAssert(playError == nil, playError.localizedDescription);
});
}];
[self.downloadTask resume];
}
- (void)cancel {
[self.downloadTask cancel]; // if download still in progress, stop it
[self.player stop]; // if playing, stop it
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
self.player = nil;
[self.delegate song:self didFinishPlayingSuccessfully:flag];
}
@end
因此您可以看到 downloadAndPlay
启动异步下载,完成后,开始异步播放曲目。 cancel
方法取消下载并停止播放。
然后你可以像这样使用它:
@interface ViewController () <SongDelegate>
@property (nonatomic, strong) Song *song;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.song = [Song songWithURL:[NSURL URLWithString:@"http://dl.last.fm/static/1464677535/131211148/70b3b5a9d048c7939d5bb9ec87a2c5d58d6ee528828f5c6a5b7b1eddd69f4553/Death+Grips+-+Get+Got.mp3"] delegate:self];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)didTapPlayButton:(id)sender {
[self.song downloadAndPlay];
}
- (IBAction)didTapStopButton:(id)sender {
[self.song cancel];
}
- (void)song:(Song *)song didFinishPlayingSuccessfully:(BOOL)flag {
NSLog(@"did finish playing %@", flag ? @"successfully" : @"unsuccessfully");
}
- (void)song:(Song *)song didFinishDownloadWithError:(NSError *)error {
NSLog(@"did finish download with error %@", error.localizedDescription);
}
@end
现在,很明显,这是一个微不足道的实现(如果你有任何错误,你真的不想做 NSAssert
,而是优雅地处理它,你想处理一系列 Song
对象,您可能希望将下载与播放分离,以便您可以在播放歌曲 1 的同时开始下载歌曲 2,等等),但它说明了取消下载或播放歌曲的更广泛概念,两者都是已经是异步任务,因此不需要调度队列或操作队列。如果你想变得花哨,你可以这样做,但这是一个更高级的话题。
顺便说一下,NSURLSession
非常严格地禁止非 https 请求,因为它们会带来安全风险,但您可以编辑 info.plist
(右键单击它然后说 "Open As" - "Source code") 然后输入如下内容:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>dl.last.fm</key>
<dict>
<!--Include to allow subdomains-->
<key>NSIncludesSubdomains</key>
<true/>
<!--Include to allow HTTP requests-->
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--Include to specify minimum TLS version-->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>