URLSession.shared.dataTaskPublisher not working on IOS 13.3

6.7k views Asked by At

When trying to make a network request, I'm getting an error

finished with error [-999] Error Domain=NSURLErrorDomain Code=-999 "cancelled"

If I use URLSession.shared.dataTask instead of URLSession.shared.dataTaskPublisher it will work on IOS 13.3.

Here is my code :

return  URLSession.shared.dataTaskPublisher(for : request).map{ a in
    return a.data
}
.decode(type: MyResponse.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()

This code worked on IOS 13.2.3.

3

There are 3 answers

2
Procrastin8 On

You have 2 problems here: 1. like @matt said, your publisher isn't living long enough. You can either store the AnyCancellable as an instance var, or what I like to do (and appears to be a redux best practice) is use store(in:) to a Set<AnyCancellable> to keep it around and have it automatically cleaned up when the object is dealloced. 2. In order to kick off the actual network request you need to sink or assign the value.

So, putting these together:

var cancellableSet: Set<AnyCancellable> = []

func getMyResponse() {
  URLSession.shared.dataTaskPublisher(for : request).map{ a in
    return a.data
  }
  .decode(type: MyResponse.self, decoder: JSONDecoder())
  .receive(on: DispatchQueue.main)
  .replaceError(with: MyResponse())
  .sink { myResponse in print(myResponse) }
  .store(in: &cancellableSet)
}
0
matt On

You have not shown enough code, but based on the symptom it is clear what the problem is: your publisher / subscriber objects are not living long enough. I would venture to say that your code was always wrong and it was just a quirk that it seemed to succeed. Make sure that your publisher and especially your subscriber are retained in long-lived objects, such as instance properties, so that the network communication has time to take place.

Here's a working example of how to use a data task publisher:

class ViewController: UIViewController {
    let url = URL(string:"https://apeth.com/pep/manny.jpg")!
    lazy var pub = URLSession.shared.dataTaskPublisher(for: url)
        .compactMap {UIImage(data: $0.data)}
        .receive(on: DispatchQueue.main)
    var sub : AnyCancellable?
    override func viewDidLoad() {
        super.viewDidLoad()
        let sub = pub.sink(receiveCompletion: {_ in}, receiveValue: {print($0)})
        self.sub = sub
    }
}

That prints <UIImage:0x6000008ba490 anonymous {180, 206}>, which is correct (as you can see by going to that URL yourself).

The point I'm making is that if you don't say self.sub = sub, you get exactly the error you are reporting: the subscriber sub, which is merely a local, goes out of existence immediately and the network transaction is prematurely cancelled (with the error you reported).

EDIT I think that code was written before the .store(in:) method existed; if I were writing it today, I'd use that instead of a sub property. But the principle is the same.

0
user1195358 On

I needed to move my cancellable set "above" the scope of the function where my subscriber was executing. This worked fine in iOS 13.2 when the cancellable set had the same scope as the function of the subscriber, but stop working in 13.3. The dataTaskPublisher cancels with the error sited above. It makes sense that the cancellable set should "out live" the subscriber. Developer error. Lesson learned.