如何停止或取消调度块的执行

时间:2016-05-31 06:04:54

标签: ios objective-c nsoperationqueue dispatch-async

我想同时执行多个调度块。因此,当我运行第二个调度块时,当任何调度块同时进行时,我想停止执行先前的调度块。

我正在使用以下代码:

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);
        }];
    }];

提前致谢,

1 个答案:

答案 0 :(得分:2)

此处不需要调度队列或操作队列。

您只需要能够使用NSURLSession启动异步下载会话,并在下载成功时启动异步AVAudioPlayer。由于这些是异步任务,您可以分别cancelstop

这是一个简单的例子:

@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(右键单击它然后说“打开为” “ - ”源代码“)然后输入类似的内容:

<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>