KWBackgroundTask.m 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. //
  2. // Licensed under the terms in License.txt
  3. //
  4. // Copyright 2010 Allen Ding. All rights reserved.
  5. //
  6. #import "KWBackgroundTask.h"
  7. NSString *const NSTaskDidTerminateNotification;
  8. static NSString *const KWTaskDidTerminateNotification = @"KWTaskDidTerminateNotification";
  9. static NSString *const KWBackgroundTaskException = @"KWBackgroundTaskException";
  10. @implementation KWBackgroundTask
  11. - (instancetype)initWithCommand:(NSString *)command arguments:(NSArray *)arguments {
  12. if (self = [super init]) {
  13. _command = command;
  14. _arguments = arguments;
  15. }
  16. return self;
  17. }
  18. - (void)dealloc {
  19. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:nil];
  20. }
  21. - (NSString *)description {
  22. return [NSString stringWithFormat:@"%@ `%@ %@`", [super description], self.command, [self.arguments componentsJoinedByString:@" "]];
  23. }
  24. // Run this task for 10 seconds
  25. // if it times out raise an exception
  26. - (void)launchAndWaitForExit {
  27. CFRunLoopRef runLoop = [NSRunLoop currentRunLoop].getCFRunLoop;
  28. __weak KWBackgroundTask *weakSelf = self;
  29. CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 10.0, 0, 0, 0, ^(CFRunLoopTimerRef timer) {
  30. [NSException raise:KWBackgroundTaskException format:@"Task %@ timed out", weakSelf];
  31. CFRunLoopStop(runLoop);
  32. });
  33. CFRunLoopAddTimer(runLoop, timer, kCFRunLoopDefaultMode);
  34. id taskObserver = [[NSNotificationCenter defaultCenter] addObserverForName:KWTaskDidTerminateNotification object:self queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
  35. CFRunLoopStop(runLoop);
  36. }];
  37. [NSThread detachNewThreadSelector:@selector(launch) toTarget:self withObject:nil];
  38. CFRunLoopRun();
  39. CFRunLoopRemoveTimer(runLoop, timer, kCFRunLoopDefaultMode);
  40. [[NSNotificationCenter defaultCenter] removeObserver:taskObserver];
  41. }
  42. #pragma mark - Private
  43. - (void)launch {
  44. __block id<NSTask_KWWarningSuppressor> task = [[NSClassFromString(@"NSTask") alloc] init];
  45. [task setEnvironment:[NSDictionary dictionary]];
  46. [task setLaunchPath:_command];
  47. [task setArguments:_arguments];
  48. NSPipe *standardOutput = [NSPipe pipe];
  49. [task setStandardOutput:standardOutput];
  50. // Consume standard error but don't use it
  51. NSPipe *standardError = [NSPipe pipe];
  52. [task setStandardError:standardError];
  53. _task = task;
  54. _standardError = standardError;
  55. _standardOutput = standardOutput;
  56. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidTerminate:) name:NSTaskDidTerminateNotification object:task];
  57. @try {
  58. [_task launch];
  59. } @catch (NSException *exception) {
  60. [NSException raise:KWBackgroundTaskException format:@"Task %@ failed to launch", self];
  61. }
  62. CFRunLoopRun();
  63. }
  64. - (void)taskDidTerminate:(NSNotification *)note {
  65. if ([_task terminationStatus] != 0) {
  66. [NSException raise:KWBackgroundTaskException format:@"Task %@ terminated with non 0 exit code", self];
  67. } else {
  68. _output = [[_standardOutput fileHandleForReading] readDataToEndOfFile];
  69. }
  70. [[NSNotificationCenter defaultCenter] postNotificationName:KWTaskDidTerminateNotification object:self];
  71. [NSThread exit];
  72. }
  73. @end