Objective-C provides complete try-catch-finally semantics for exception handling, with full stack unwinding. While Cocoa and CocoaTouch conventions discourage their use for runtime errors (originally for performance reasons), they are of common use also in Apple's frameworks for programmer errors (i.e. logic errors, such as accessing an out-of-bounds element of an NSArray).
Swift, on the other hand, provides similar do-try-catch semantics, but its errors (do not call them exceptions) are actually passed back as an out parameter and does not provide a stack unwinding mechanism. Moreover, as of Swift 3.x, Objective-C exceptions are left completely out of Swift error handling: they are uncatchable in this language, and cause a direct crash of the application or the service. For the rationale behind this choice, and its corresponding discussion, see this dissertation on the Swift GitHub repository.
Our SDKs for iOS, macOS and tvOS originate from our Java SDK, where the language itself makes quite a liberal use of exceptions, even for runtime errors. So did our initial Objective-C ports. With time, during their development roadmap, we progressively adapted our APIs to this Swift idiosyncrasy. In particular:
- Until version 1.2.x (2014) use of exceptions was still liberal.
- Starting with version 1.4.0 (January 2015) double method signatures were introduced, to support both Objective-C exceptions and Swift error handling.
- Starting with version 2.0.0 (May 2016) the new Unified API model removed all uses of exceptions for runtime errors, while still keeping their use for programmer errors.
With the recent release of version 2.1.2, our SDKs for iOS, macOS and tvOS make one more step towards Swift developers by introducing a new flag called limitExceptionsUse. You can find it on the LSLightstreamerClient class.
This flag avoids the use of exceptions for a subclass of programmer errors that can be considered equivalent to trying to access a non-existent member of a dictionary. For the LSItemUpdate class it is applied to the following methods:
For the LSSubscription class it is applied to the following methods:
- LSSubscription.value(withItemPos:, fieldPos:)
- LSSubscription.value(withItemPos:, fieldName:)
- LSSubscription.value(withItemName:, fieldPos:)
- LSSubscription.value(withItemName:, fieldName:)
- LSSubscription.commandValue(withItemPos:, key:, fieldPos:)
- LSSubscription.commandValue(withItemPos:, key:, fieldName:)
- LSSubscription.commandValue(withItemName:, key:, fieldPos:)
- LSSubscription.commandValue(withItemName:, key:, fieldName:)
In all these cases, once the limitExceptionsUse flag has been set, a call for a non-existing field or item name, or field or item position, results in a null return value, in place of throwing an Objective-C exception. The error is also always logged on the console, for your convenience during debugging. Check the API docs for more information.
While our SDKs must remain coded in Objective-C for a number of engineering choices (at least for the near future), our commitment to supporting the Swift language remains strong, and we will continue to improve our APIs as new challenges are posed by our customers.
If you have further suggestions on how to improve our APIs for the Swift language, any feedback in the comments will be appreciated.