Keine Versand Warteschlangen oder Betrieb Warteschlangen werden hier benötigt.
Sie müssen nur in der Lage sein, eine asynchrone Download-Sitzung mit NSURLSession
zu starten, und wenn der Download erfolgreich ist, starten Sie die asynchrone AVAudioPlayer
. Und weil diese asynchronen Aufgaben sind, können Sie entweder cancel
oder stop
diese jeweils.
hier ein triviales Beispiel ist:
@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
So können Sie sehen, dass downloadAndPlay
asynchron initiiert herunterladen und, wenn das erledigt ist, beginnt die asynchrone Wiedergabe der Spur. Die cancel
-Methode bricht den Download ab, wenn sie ausgeführt wird, und stoppt die Wiedergabe, wenn sie ausgeführt wird.
Und dann kann man es wie so verwenden:
@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
nun klar, das ist eine triviale Implementierung (Sie wollen nicht wirklich NSAssert
tun, wenn Sie Fehler haben, sondern behandeln sie anmutig, Sie möchten eine Reihe von Song
Objekte behandeln, möchten Sie vielleicht Download vom Spielen entkoppeln, so dass Sie Song 2 herunterladen können, während Song 1 spielt, etc.), aber es veranschaulicht das breitere Konzept des Abbrechens entweder einen Download oder das Abspielen von ein Lied, die beide bereits asynchrone Aufgaben sind, so dass keine Versandwarteschlangen oder Vorgangswarteschlangen benötigt werden. Sie können das tun, wenn Sie Lust haben, aber das ist ein fortgeschrittenes Thema.
By the way, ist NSURLSession
ziemlich streng über das Verbot nicht-HTTPS-Anfragen wegen des Sicherheitsrisikos stellen sie, aber Sie können Ihre info.plist
(rechts klicken Sie darauf und dann sagen: „Öffnen als“ - „Quellcode“) bearbeiten und geben Sie dann Folgendes ein:
<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>
Der Dispatchblock in GCD kann nicht abgebrochen werden, aber der Block in NSOperationQueue kann abgebrochen werden. – EricXuan
@EricXuan: Ja, ich weiß 'NSOperationQueue' kann abgebrochen werden Aber wenn ich' NSOperationQueue' verwendet habe, ist meine Anwendung fest –
@EricXuan - Eigentlich können Sie jetzt in GCD abbrechen. (Es ist einfach nicht so elegant wie die Abbruchlogik in 'NSOperation'.) Das Problem ist' dataWithContentsOfURL', da das nicht abbrechen kann. Er sollte "NSURLSession" verwenden, was kündbar ist. Und er sollte GCD und/oder "NSOperation" vollständig verlieren, oder wenn er es behalten möchte, sollte er die abbrechbare benutzerdefinierte asynchrone "NSOperation" Unterklasse verwenden und die 'cancel' Methode die Aufgabe abbrechen (während dies eine reichhaltigere Lösung ist als nur "NSURLSession" an sich, es ist auch ein bisschen mehr Arbeit). – Rob