iOS
NSURLErrorDomain
error code -999
troubleshooting
iOS development

How to fix NSURLErrorDomain error code -999 in iOS

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

NSURLErrorDomain error code -999 means a request was canceled. That sounds like a networking failure, but in many iOS apps it is actually normal behavior caused by a newer request replacing an older one, a view disappearing, a cell being reused, or code explicitly calling cancel().

So the first step is not to "fix the network." The first step is to decide whether the cancellation was expected or whether your code is accidentally cancelling work too aggressively.

What -999 Usually Means

A -999 error is commonly seen when:

  • a second request supersedes the first one
  • a URLSessionTask is canceled manually
  • a search field or image loader starts a newer request for the same UI
  • a view controller disappears and the app cancels in-flight work

For example, live-search UIs often cancel stale requests on purpose:

swift
1import Foundation
2
3var currentTask: URLSessionDataTask?
4
5func search(query: String) {
6    currentTask?.cancel()
7
8    let url = URL(string: "https://example.com/search?q=\(query)")!
9    currentTask = URLSession.shared.dataTask(with: url) { data, response, error in
10        if let error = error as? URLError, error.code == .cancelled {
11            return
12        }
13
14        print("Finished latest request")
15    }
16
17    currentTask?.resume()
18}

In that pattern, the cancellation is intentional and should usually be ignored rather than treated as a bug.

Do Not Treat Every -999 As A User-Visible Error

If your networking layer logs every -999 as a failure alert, the app will look broken even when it is behaving correctly. A better approach is to filter cancellation separately from real failures such as timeouts or offline errors.

swift
1let task = URLSession.shared.dataTask(with: request) { data, response, error in
2    if let urlError = error as? URLError, urlError.code == .cancelled {
3        return
4    }
5
6    if let error = error {
7        print("Real networking error:", error)
8        return
9    }
10
11    print("Success")
12}
13task.resume()

This small distinction improves logs, telemetry, and user experience.

Common App Patterns That Produce -999

Image loading in scrolling lists is a classic source. A table or collection view cell starts loading an image, then the cell gets reused for a different item before the first request completes. Good image loaders cancel the old task, which produces -999.

Another common case is WKWebView or navigation-heavy UIs. If a new navigation starts before the previous one finishes, the older load may be canceled. That is still not necessarily a bug. It is often just how the app resolves "the user changed their mind" behavior.

The same applies to rapid form edits, debounced search, and "tap a new tab before the old tab's request completes" scenarios.

Fix The Accidental Cases

If the cancellation is not intentional, the usual causes are task ownership problems and lifecycle mistakes. Examples:

  • creating a task property and overwriting it too early
  • canceling in viewWillDisappear even though the result is still needed
  • reusing a view model that cancels shared tasks unexpectedly
  • triggering duplicate requests when one would be enough

A clean ownership model helps. The object that starts the task should also be the one that decides whether the task is still relevant.

Coordinate Request Identity

For fast-changing screens, it is often safer to track whether a response still belongs to the current UI state before applying it. Cancellation is one option, but identity checks are another.

swift
1import Foundation
2
3var latestQuery = ""
4
5func search(query: String) {
6    latestQuery = query
7    let expectedQuery = query
8
9    let url = URL(string: "https://example.com/search?q=\(query)")!
10    URLSession.shared.dataTask(with: url) { data, response, error in
11        if let urlError = error as? URLError, urlError.code == .cancelled {
12            return
13        }
14
15        guard expectedQuery == latestQuery else { return }
16        print("Apply only the newest response")
17    }.resume()
18}

This prevents old responses from overwriting new UI even if they were not explicitly canceled.

Common Pitfalls

One common mistake is treating -999 as a server or connectivity issue. It usually is not. Another is showing the user an error alert for cancellations that were triggered intentionally by normal app behavior. Developers also often log cancellation noise so heavily that real networking failures get buried. Finally, cancellation problems become much more confusing when task ownership is unclear and multiple layers of the app can cancel the same request for different reasons.

Summary

  • 'NSURLErrorDomain code -999 means the request was canceled.'
  • In many apps, that cancellation is expected and should not be treated as a real error.
  • Ignore or handle .cancelled separately from actual network failures.
  • Review task ownership and lifecycle logic when cancellations happen unexpectedly.
  • Search UIs, reusable cells, and rapid navigation commonly produce valid -999 cancellations.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.