Crash while casting object in swift - swift

While trying to cast object the crash connected with releasing object is generated.
The problem can be narrowed to following code:
enum A: Error {
case a
}
enum B: Error {
case b
}
func errorHanlding(result: Result<Void, NSError>) {
if case .failure(let error) = result {
if case .a = error as? A {
print("Success")
}
}
return
}
errorHanlding(result: .failure(B.b as NSError))
This is the snipped of code that allows to check the root source.
The problem is connected with this line:
if case .a = error as? A
Without this casting everything works correctly.
Doesn't anyone have idea why it is crashing?
Xcode version: 11.3.1
Swift: 5.1.3

Related

Swift Combine framework setFailureType error operator

For scientific reasons I've created a Publisher and a Subscriber so I can dive into Combine.
The Publisher has been converted from a never failing to the failing one.
enum IntegerError: String, Error {
case miltupleOf2 = "We are sorry but the number is a multiple of 2, therefore cannot be used in the process"
}
let integerPublisher = [1,3,3,3,3,3,5,6,7,7].publisher
.setFailureType(to: IntegerError.self)
let subscribtion = integerPublisher
.tryMap { intValue in
if intValue.isMultiple(of: 2) {
throw IntegerError.miltupleOf2
} else {
return intValue
}
}
.sink { completion in
switch completion {
case .finished:
print("success")
case .failure(let error):
if let error = error as? IntegerError {
print(error.rawValue)
} else {
print(error)
}
}
} receiveValue: { value in
print(value)
}
My question is: when using sink, the error type is Error. Why is it not the custom IntegerError that I've used within the .setFailureType modifier?
The need of casting my error to the type that I specified earlier seems a little redundant.
Thank you.
The reason for this is quite straightforward. tryMap returns a Publishers.TryMap<Upstream, Output>, which is a specific kind of publisher with Failure == Error:
typealias Failure = Error
So as soon as you use tryMap, that undoes what setFailureType did.
The reason why Publishers.TryMap has Error as its Failure type is because in Swift, you can't specify what specific type of error a closure can throw (unlike Java for example). Once you mark a closure as throws, like tryMap has done with its transform parameter:
func tryMap<T>(_ transform: #escaping (Self.Output) throws -> T) -> Publishers.TryMap<Self, T>
any Error can be thrown inside the transform closure. You can try changing throw IntegerError.miltupleOf2 to throwing another type of error. Your code will still compile.
Hypothetically, if Swift allowed you to specify what type of error you are allowed to throw in the closure, then tryMap could have been declared as (fake syntax):
func tryMap<T, E: Error>(_ transform: #escaping (Self.Output) throws E -> T) -> Publishers.TryMap<Self, T, E>
and you wouldn't even need setFailureType.
As a workaround, you can use mapError to cast the error to your desired type:
let subscribtion = integerPublisher
.tryMap { intValue -> Int in
if intValue.isMultiple(of: 2) {
throw IntegerError.miltupleOf2
} else {
return intValue
}
}.mapError { $0 as! IntegerError }
.sink { completion in
switch completion {
case .finished:
print("success")
case .failure(let error):
print(error.rawValue)
}
} receiveValue: { value in
print(value)
}
You need to reassure the compiler that you haven't thrown any other type of errors in tryMap.

How to avoid re-parsing Error when using enum that conforms to Error protocol in Swift

There's an error enum:
#objc public enum MyError: Int, Error {
case good
case bad
case ugly
}
Note that it's available to objective C - that's part of the challenge. For instance I cannot change enum to have associated values (like case good(Error))
A decent solution is to have a constructor that can parse from some arbitrary error:
init(from error: Error?) {
// parse it and set self to corresponding code, e.g.
guard let error = error else {
self = .good
return
}
if let uglyError = error as NSError? {
self = .ugly
return
}
self = .bad
}
But one of the cases this constructor has to handle is that provided error is already one of MyError values, i.e. Error(MyError.ugly) - this is simplification of course. More likely it's a result of lower-level error bubbling up.
In that case I want to avoid re-parsing it. How is that possible?
In pseudo-code I want to implement condition like this:
if error is [one of MyError values] {
self = error
return
}
Tried to do that with CaseIterable and allCases, but seems cannot convert Error to something that can be compared
for value in MyError.allCases {
if error == value { // <-- Error: Binary operator '==' cannot be applied to operands of type 'Error' and 'MyError'
}
}
Any way to accomplish something like that? (I am not stuck on CaseIterable, anything else works too).
If you are aiming to keep the same initializer as init(from error: Error?), what you could do is to add
if let castedError = error as? MyError {
self = castedError
return
}
at the beginning (before doing anything).
As a full implementation, it should be similar to:
#objc public enum MyError: Int, Error, CaseIterable {
case good
case bad
case ugly
init(from error: Error?) {
if let castedError = error as? MyError {
print("Already MyError!")
self = castedError
return
}
guard let error = error else {
self = .good
return
}
if let _ = error as NSError? {
self = .ugly
return
}
self = .bad
}
}
Therefore, the output would be:
let givenError: MyError = .good
let resultError = MyError(from: givenError)
print(resultError.rawValue) // 0 (which is .good raw value)
Note that it should also log "Already MyError!" because of the print statement in the first check to confirm that it has been reached.
It should also behave as expected with NSErrors:
let nsError = NSError(domain: "", code: 101, userInfo: nil)
let myError = MyError(from: nsError)
print(myError.rawValue) // 2 (which is .ugly raw value)

Accessing code in Swift 3 Error

New in Xcode 8 beta 4, NSError is bridged to the Swift Error protocol type. This affects StoreKit when dealing with failed SKPaymentTransactions. You ought to check to be sure the error didn't occur because the transaction was cancelled to know whether or not to show an error message to the user. You do this by examining the error's code. But with Error instead of NSError, there is no code defined. I haven't been able to figure out how to properly get the error code from Error.
This worked in the previous version of Swift 3:
func failedTransaction(_ transaction: SKPaymentTransaction) {
if let transactionError = transaction.error {
if transactionError.code != SKErrorCode.paymentCancelled.rawValue {
//show error to user
}
}
...
}
Now that error is an Error not NSError, code is not a member.
Another option to access code and domain properties in Swift 3 Error types is extending it as follow:
extension Error {
var code: Int { return (self as NSError).code }
var domain: String { return (self as NSError).domain }
}
Now in Xcode 8 and swift 3 the conditional cast is always succeeds, so you need do following:
let code = (error as NSError).code
and check the code for your needs. Hope this helps
Casting to SKError seems to be working for me in xCode 8 and Swift 3...
guard let error = transaction.error as? SKError else {return}
switch error.code { // https://developer.apple.com/reference/storekit/skerror.code
case .unknown: break
case .paymentCancelled: break
case .clientInvalid: break
case .paymentInvalid: break
case .paymentNotAllowed: break
case .cloudServiceNetworkConnectionFailed: break
case .cloudServicePermissionDenied: break
case .storeProductNotAvailable: break
}
No need for rawValue.
This is correct (Apple's own tests use this approach):
if error._code == SKError.code.paymentCancelled.rawValue { ... }
On the other hand, casting to NSError will probably be deprecated soon:
let code = (error as NSError).code // CODE SMELL!!
if code == SKError.code.paymentCancelled.rawValue { ... }
Use
error._code == NSURLErrorCancelled
to match the error code.
A lot is changing. Here's update for Xcode 11.
if let skError = transaction.error as? SKError, skError.code == .paymentCancelled {
print("Cancelled")
}

Transform Reactive Cocoa SignalProducer into enum

I'm trying to transform my API SignalProducers into an enum so I get Loading, Failed and Loaded states. Enum is:
enum DataLoadState<DataType>{
case Loading
case Failed
case Loaded(DataType)
}
To do it I transform my SignalProducer to a NoError producer and start with loading state:
extension SignalProducerType{
func materializeToLoadState() -> SignalProducer<DataLoadState<Value>,NoError>{
let producer = self
.map(DataLoadState.Loaded)
.startWithValue(DataLoadState.Loading)
return producer.ignoreErrors(replacementValue:DataLoadState<Value>.Failed)
}
}
extension SignalProducerType {
func startWithValue(value:Value)->SignalProducer<Value,Error>{
return SignalProducer(value:value).concat(self.producer)
}
public func ignoreErrors(replacementValue replacementValue: Self.Value? = nil) -> SignalProducer<Self.Value, NoError> {
return self.flatMapError { error in
return replacementValue.map(SignalProducer.init) ?? .empty
}
}
}
It works but I don't want to ignore Errors, instead I want to include it in the failed case:
enum DataLoadState<DataType>{
case Loading
case Failed(APIError?)
case Loaded(DataType)
}
Any idea on how can I use flatMapError (like in ignoreError) so errors are fired as DataLoadState.Failed(error) instead of being ignored? When error isn't display error is could be just nil. Is it even possible?
Ok, fixed it just with:
producer.flatMapError { error in SignalProducer(value:.Failed(error as? APIError)) }

Get string message from catch error in do/try/catch with Swift

I have 2 classes, 1 is where view controller of form and the other is custom class to validate all forms in my app.
In the enum of the list of possible errors I have these:
enum ValidateErrors: ErrorType{
case Empty(desc: String)
case WrongFormat(desc: String)
}
and inside the validate method from the same class I have these:
guard email.characters.count > 0 else { throw ValidateErrors.Empty(desc:"Empty email.") }
When I do the do/try/catch in the view controller, I need to show an error message with "Empty email." but it show: Empty("Empty email.")
These is the code from the view controller where I make the do/try/catch
do{
try ValidateData.validateEmail(emailTextView.text!)
print("Campo Valido")
}catch let error{
print(error)
}
I found a solution here:
https://developer.apple.com/documentation/swift/error
And in Swift 4 try this for your example:
enum ValidateErrors: Error
{
case Empty(desc: String)
case WrongFormat(desc: String)
}
do
{
try ValidateData.validateEmail(emailTextView.text!)
print("Campo Valido")
}
catch ValidateErrors.Empty(desc: let error)
{
print(error)
}
You might try print(error.desc)