BoltsFramework / Bolts-ObjC

Bolts is a collection of low-level libraries designed to make developing mobile apps easier.
Other
5.64k stars 576 forks source link

forCompletionOfAllTasksWithResults: exception 'Cannot set the result on a completed task.' #140

Closed driver733 closed 9 years ago

driver733 commented 9 years ago

Hello, I am trying to implement the following logic:

  1. Get user`s posts from Parse
  2. For each post, get a URL of the movie poster from iTunes (func getMovieInfoByITunesID)
  3. After URLs for all posts are received, report completion.

However I get the exception: *\ Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot set the result on a completed task.'

The exception rises in this line: tempPost.timeSincePosted = self.getTimeSincePostedfromDate(post.createdAt!)

What does the setResult in the (self.getTimeSincePostedfromDate(post.createdAt!)) has to do with the main task?

func loadFeedPosts() -> BFTask {
    let mainTask = BFTaskCompletionSource()

    let user = PFUser.currentUser()!
    let relation = user.relationForKey("feed")
    let query = relation.query()

    query?.addDescendingOrder("createdAt")

    query?.findObjectsInBackground().continueWithBlock({ (task: BFTask!) -> AnyObject! in
      var tasks = [BFTask]()
      if task.error == nil, let result = task.result {
        let posts = result as! [PFObject]

        for post in posts {
          var tempPost = Post()
          tempPost.userName = (post["createdBy"] as! PFUser).username
          tempPost.timeSincePosted = self.getTimeSincePostedfromDate(post.createdAt!)
          tempPost.profileImageURL = (post["createdBy"] as! PFUser)["smallProfileImage"] as? String

          tasks.append(self.getMovieInfoByITunesID(post["trackID"] as! Int))
         }

      }

      return BFTask(forCompletionOfAllTasksWithResults: tasks)
    }).continueWithBlock({ (task: BFTask!) -> AnyObject! in

      for var i = 0; i < Post.sharedInstance.feedPosts.count; ++i {
         Post.sharedInstance.feedPosts[i].bigPosterImageURL = self.getBigPosterImageURL((task.result as! JSON)[i]["artworkUrl100"].stringValue)
      }

      mainTask.setResult(nil)
      return nil
    })

    return mainTask.task

    }

func getMovieInfoByITunesID(iTunesID: Int) -> BFTask {
      let task = BFTaskCompletionSource()
      ITunesApi.lookup(iTunesID).request({ (responseString: String?, error: NSError?) -> Void in
        if
          //        error == nil,
          let responseString = responseString, let dataFromString = responseString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
            let json = JSON(data: dataFromString)
            task.setResult(json["results"].arrayObject!)
        } else {
          // process error
        }
      })
      return task.task
    }
nlutsenko commented 9 years ago

Hey @driver733, nothing seems suspicious from this code, though it's super helpful to have it. Can you add a stack trace by any chance? That exception is being thrown when you are trying to set a result of a task via setResult: or property access if the task is already completed.

The safer method is trySetResult:

nlutsenko commented 9 years ago

Another piece of feedback about the code - you can get rid of mainTask and simply return a huge chain of tasks from the method, instead of setting the result manually on a task completion source.

driver733 commented 9 years ago

Thanks! I have created a simple method for testing purposes (it raises the same exception)

func test() -> BFTask {
      var tasks = [BFTask]()
      tasks.append(self.getMovieInfoByITunesID(270711065))
      return BFTask(forCompletionOfAllTasksWithResults: tasks)
    }

Here`s the stack trace (if I am correctly understanding what you mean :) )

2015-09-05 20:27:45.381 Moviethete[32571:6036338] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot set the result on a completed task.'
*** First throw call stack:
(
    0   CoreFoundation                      0x035bd746 __exceptionPreprocess + 182
    1   libobjc.A.dylib                     0x03961a97 objc_exception_throw + 44
    2   CoreFoundation                      0x035bd66d +[NSException raise:format:] + 141
    3   Bolts                               0x0081cc7e -[BFTask setResult:] + 142
    4   Bolts                               0x0081fdf7 -[BFTaskCompletionSource setResult:] + 119
    5   Moviethete                          0x000a448b _TFFV10Moviethete4Post22getMovieInfoByITunesIDFS0_FSiCSo6BFTaskU_FTGSqSS_GSqCSo7NSError__T_ + 1499
    6   Moviethete                          0x0009facd _TPA__TFFV10Moviethete4Post22getMovieInfoByITunesIDFS0_FSiCSo6BFTaskU_FTGSqSS_GSqCSo7NSError__T_ + 77
    7   ITunesSwift                         0x03446a9b _TFFC11ITunesSwift15ITunesLookupApi7requestFS0_FFTGSqSS_GSqCSo7NSError__T_T_U_FTGSqCSo12NSURLRequest_GSqCSo17NSHTTPURLResponse_GO9Alamofire6ResultSS__T_ + 683
    8   ITunesSwift                         0x034460d4 _TPA__TFFC11ITunesSwift15ITunesLookupApi7requestFS0_FFTGSqSS_GSqCSo7NSError__T_T_U_FTGSqCSo12NSURLRequest_GSqCSo17NSHTTPURLResponse_GO9Alamofire6ResultSS__T_ + 100
    9   Alamofire                           0x00739ece _TTRXFo_oGSqCSo12NSURLRequest_oGSqCSo17NSHTTPURLResponse_oGO9Alamofire6ResultSS__dT__XFo_oGSqS__oGSqS0__iGS2_SS__dT__ + 94
    10  Alamofire                           0x007372e4 _TPA__TTRXFo_oGSqCSo12NSURLRequest_oGSqCSo17NSHTTPURLResponse_oGO9Alamofire6ResultSS__dT__XFo_oGSqS__oGSqS0__iGS2_SS__dT__ + 100
    11  Alamofire                           0x00738ea5 _TFFFC9Alamofire7Request8responseu0_Rq_S_18ResponseSerializerzq0_qq_S1_16SerializedObject_FS0_FT5queueGSqPSo17OS_dispatch_queue__18responseSerializerq_17completionHandlerFTGSqCSo12NSURLRequest_GSqCSo17NSHTTPURLResponse_GOS_6Resultq0___T__DS0_U_FT_T_U0_FT_T_ + 517
    12  Alamofire                           0x00737fa6 _TPA__TFFFC9Alamofire7Request8responseu0_Rq_S_18ResponseSerializerzq0_qq_S1_16SerializedObject_FS0_FT5queueGSqPSo17OS_dispatch_queue__18responseSerializerq_17completionHandlerFTGSqCSo12NSURLRequest_GSqCSo17NSHTTPURLResponse_GOS_6Resultq0___T__DS0_U_FT_T_U0_FT_T_ + 166
    13  Alamofire                           0x00700c68 _TTRXFo__dT__XFdCb__dT__ + 40
    14  libdispatch.dylib                   0x044775ea _dispatch_call_block_and_release + 15
    15  libdispatch.dylib                   0x04499bef _dispatch_client_callout + 14
    16  libdispatch.dylib                   0x0447f6bb _dispatch_main_queue_callback_4CF + 993
    17  CoreFoundation                      0x035168ee __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 14
    18  CoreFoundation                      0x034d45f0 __CFRunLoopRun + 2256
    19  CoreFoundation                      0x034d3a5b CFRunLoopRunSpecific + 443
    20  CoreFoundation                      0x034d388b CFRunLoopRunInMode + 123
    21  GraphicsServices                    0x066d82c9 GSEventRunModal + 192
    22  GraphicsServices                    0x066d8106 GSEventRun + 104
    23  UIKit                               0x020c90b6 UIApplicationMain + 1526
    24  Moviethete                          0x000aa89c main + 140
    25  libdyld.dylib                       0x044c4ac9 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
nlutsenko commented 9 years ago

It's crazy hard to read a stack trace from Swift, it's all mangled... My rough assumption from the stack trace is that setting the result of the task inside getMovieInfoByITunesID happens twice in your code.

Two things that come to my mind:

driver733 commented 9 years ago

You were 100% correct with your second guess. Thank you very much!