Crash in SwiftyJSON during string comparison: swift_unknownRetain - swift

I'm switching on a flag from the server in order to determine what type of object to instantiate. Each type is a subclass of the return type (Snack, in the example.) My guess is that the whole subclass thing is irrelevant to the main issue, but I include it for completeness.
The trouble is that I'm pretty consistently getting a crash reported from Crashlytics on the case "chips": line. In order to simplify parsing in my initializers, I'm wrapping the server response in a SwiftyJSON JSON. This all worked fine in testing.
class func fromJSON(json: JSON) -> Snack {
switch json["SnackName"] {
case "chips": // CRASH OCCURS HERE
return BagOChips(json: json)
case "apple":
return Apple(json: json)
default:
return Spam(json: json)
}
}
Specifically, the crash is occurring at "SwiftyJSON.swift:1013" (marked below). Crashlytics describes it as "EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x0000000093a4bec8" and "swift_unknownRetain + 32".
public func ==(lhs: JSON, rhs: JSON) -> Bool {
switch (lhs.type, rhs.type) {
case (.Number, .Number):
return (lhs.object as NSNumber) == (rhs.object as NSNumber)
case (.String, .String):
return (lhs.object as String) == (rhs.object as String) // CRASH REALLY OCCURS HERE
case (.Bool, .Bool):
return (lhs.object as Bool) == (rhs.object as Bool)
case (.Array, .Array):
return (lhs.object as NSArray) == (rhs.object as NSArray)
case (.Dictionary, .Dictionary):
return (lhs.object as NSDictionary) == (rhs.object as NSDictionary)
case (.Null, .Null):
return true
default:
return false
}
}
Any idea why this is failing and what I might be able to do to correct it in our next release?

Found the problem, and boy was it obscure!
TL;DR - Avoid SwiftyJSON's == function entirely by replacing
switch json["SnackName"]
with
switch json["SnackName"].stringValue
That's probably a good idea in general, but the reason it's necessary appears to be a bug deep in the bowels of how Swift + Foundation handle strings. I've filed an open radar here.
All it takes to reproduce this problem is Xcode 6.1, SwiftyJSON, and the following sample code that I submitted to Apple:
let d = NSDictionary(dictionary: ["foo": "bar"])
let j = JSON(d)
switch (j["foo"]) {
case "bar":
println("> No crash!")
default:
println("> default")
}
Then throw in these logging statements in your copy of SwiftyJSON.
public func ==(lhs: JSON, rhs: JSON) -> Bool {
// Next 2 lines added just for SwiftyCrasher test project.
println( "> Left: \(_stdlib_getTypeName(lhs.object))" )
println( "> Right: \(_stdlib_getTypeName(rhs.object))" )
switch (lhs.type, rhs.type) {
case (.Number, .Number):
return (lhs.object as NSNumber) == (rhs.object as NSNumber)
case (.String, .String):
...
}
This shows the following console output, just before the crash:
> Left: _TtCSs19_NSContiguousString
> Right: _TtCSs19_NSContiguousString
Again, in Debug mode, this doesn't crash. Boxing "foo" and "bar" in NSString, or changing j["foo"] to j["foo"].stringValue also prevent a crash.

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.

Specifying custom error type within the Result type in Swift 5

I am trying to create a Result variable with a custom error type with the builtin Result type in Foundation for Swift 5, but I can't quite get the type system to understand the kind of error I want to throw.
The code below does not compile.
import Foundation
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw CustomError.somethingBadHappened
} else {
return Model(value: number)
}
}
// compiler complains here about: Cannot convert value of type 'Result<Model, Error>' to expected argument type 'Result<Model, CustomError>'
completion(result)
}
}
let request = Request()
request.execute(number: 19) { result in
switch result {
case .success(let value): print("Succeded with \(value)")
case .failure(let error): print("Failed with \(error)")
}
}
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
How can I make the type system understand I would like to use the custom error type?
Apologies for giving a second answer, but there needs to be a corrective for fphilipe's answer.
You can use init(catching:) to form the Result and yet return it as a Result<Model, CustomError>. That is what mapError is for! Like this:
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw NSError()
} else {
return Model(value: number)
}
}.mapError { err in
return CustomError.somethingBadHappened
}
completion(result)
}
}
I have to throw something in order to form the initial Result<Model, Error>, so I just throw an NSError as a kind of placeholder. But then mapError comes along and transforms this into a Result<Model, CustomError>. The power of mapError is that it changes only what happens in case of failure.
Thus we are able to keep the original form of the code.
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
Yes, you are! Change the signature in exactly that way, so that you compile, and then run your code. When we get to this line:
case .failure(let error): print("Failed with \(error)")
... we print "Failed with somethingBadHappened". That proves that your CustomError.somethingBadHappened instance came through just fine.
If the problem is that you want to separate out your CustomError explicitly, then separate it out explicitly as you catch it:
case .failure(let error as CustomError): print(error)
default : fatalError("oops, got some other error")
Or if you want to winnow it down still further and catch only the .somethingBadHappened case, specify that:
case .failure(CustomError.somethingBadHappened): print("Something bad happened")
default : fatalError("oops, got some other error")
Those examples are artificial, but they demonstrate what they are intended to demonstrate — that your CustomError instance is coming through with full integrity.
Just create Result manually:
let result: Result<Model, CustomError>
if (number < 20) {
result = .failure(.somethingBadHappened)
} else {
result = .success(Model(value: number))
}
completion(result)

swift 3 from working swift 2.3 : #escaping with optionals and calls needing error chain management

I have optionals (NSError?) that are flagged by Xcode/Swift as non-escaping. These are called within a function (that has #escaping on its closure) by a second function (also with #escaping on its closure). The problem seems to be that the errors are not captured in the closure of either function and so Xcode/Swift is seeing them as potentially escaping.
Previous stack overflow posts had noted 'withoutActuallyEscaping' as a workaround. That no longer seems to be supported (Xcode version 8.2.1).
Refactoring to try to keep everything local to the function has not worked. I've also tried moving all of the NSError? to Error (via local enum), but that also hasn't worked.
The core problem is how to enable an error returned (via success/failure) from one function to the calling statement to be either captured locally and retained or tagged as escaping so that Swift/XCode can compile.
Again, this was working in Swift 2.3, so I'm looking for suggestions on how to refactor or what to look at for correctly handling these calls with NSError or Error.
This is part of a large code stack (about 25K lines). I'm posting part of the stack below (given Hamish's comment) to try and make the question clearer. Currently there are about 17-different variations of this error that are present in the code-stack.
public func fetchMostRecentSamples(ofTypes types: [HKSampleType], completion: #escaping HMTypedSampleBlock)
{
let group = DispatchGroup()
var samples = [HKSampleType: [MCSample]]()
let updateSamples : ((MCSampleArray, CacheExpiry) -> Void, (MCError) -> Void, HKSampleType, [MCSample], MCError) -> Void = {
(success, failure, type, statistics, error) in
guard error == nil else {
failure(error)
return
}
guard statistics.isEmpty == false else {
failure(error)
return
}
samples[type] = statistics
success(MCSampleArray(samples: statistics), .never)
}
let onStatistic : ((MCSampleArray, CacheExpiry) -> Void, (MCError) -> Void, HKSampleType) -> Void = { (success, failure, type) in
self.fetchMostRecentSample(type) { (samples, error) in
guard error == nil else {
failure(error)
return
}
Then fetchMostRecentSample has this header:
public func fetchMostRecentSample(_ sampleType: HKSampleType, completion: #escaping HMSampleBlock)
and the error message on the failure is "Closure use of non-escaping parameter 'failure' may allow it to escape" : "Parameter 'failure' is implicitly non-escaping"
Note that the let updateSamples is fine (not calling another function), but that the onStatistic with the failure (having error codes) is where the problem with escaping/non-escaping is coming from. MCError is an enum with the error codes (refactored from NSError? in Swift 2.3 version).
Major credits to Hamish for helping me to see into this. In case it helps others coming across this on their Swift 3 conversions, the other place that I stumbled was in assuming that all of our third-party pods were complete if they stated ready for Swift 3. In this case I had to update the AwesomeCache code to handle the errors correctly:
open func setObject(forKey key: String, cacheBlock: #escaping (#escaping(CacheBlockClosure), #escaping(ErrorClosure)) -> Void, completion: #escaping (T?, Bool, NSError?) -> Void) {
if let object = object(forKey: key) {
completion(object, true, nil)
} else {
let successBlock: CacheBlockClosure = { (obj, expires) in
self.setObject(obj, forKey: key, expires: expires)
completion(obj, false, nil)
}
let failureBlock: ErrorClosure = { (error) in
completion(nil, false, error)
}
cacheBlock(successBlock, failureBlock)
}
}
This is simply adding two more #escaping beyond what was in the github master. Otherwise, as Hamish pointed out, it comes down to paying attention to where #escaping needs to be added on the function calls. The update for AwesomeCache came from code like this:
aggregateCache.setObjectForKey(key, cacheBlock: { success, failure in
let doCache : ([MCAggregateSample], NSError?) -> Void = { (aggregates, error) in
guard error == nil else {
failure(error)
return
}
success(MCAggregateArray(aggregates: aggregates), .Date(self.getCacheExpiry(period)))
}
where the success, failure will be flagged as potentially escaping without the code changes on the AwesomeCache.

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)) }

how to save and load a number (integer) using CloudKit (swift)

I am creating an app that needs to save a counter variable (which is an integer) into the cloud when the app exits, then loads the counter when the app becomes active. Ive never used CloudKit before could someone simplify how i could do this using swift? many of the examples I've tried to replicate are too complex for what I am trying to achieve.
Note: Before anyone mentions it , I know there are other ways to achieve this but I want to do it using CloudKit.
Also, I already understand how appDelegate transitions work so i don't need help with that :)
CloudKit: Ok lets do some planning and get out assumptions agreed.
You need to check the network is up and reachable
You need check said user is logged into the cloud
Unclear as to the nature of what your really want to do here beyond writing a noddy method; lets assuming you want something a bit more.
You save your integer using cloud kit, ensuring any errors that come thru are handled. What sort of errors. Here a list for you.
enum CKErrorCode : Int {
case InternalError
case PartialFailure
case NetworkUnavailable
case NetworkFailure
case BadContainer
case ServiceUnavailable
case RequestRateLimited
case MissingEntitlement
case NotAuthenticated
case PermissionFailure
case UnknownItem
case InvalidArguments
case ResultsTruncated
case ServerRecordChanged
case ServerRejectedRequest
case AssetFileNotFound
case AssetFileModified
case IncompatibleVersion
case ConstraintViolation
case OperationCancelled
case ChangeTokenExpired
case BatchRequestFailed
case ZoneBusy
case BadDatabase
case QuotaExceeded
case ZoneNotFound
case LimitExceeded
case UserDeletedZone
}
You might want to read the thing back to check it even if you don't get any errors, it is a very important integer. You need to handle these errors if you do that too.
OK, you saved it; what about next time. Ok its the same palaver, network, cloud kit, read, deal with errors etc etc.
If your still here, well done. Here the code just to save a record.
func save2Cloud(yourInt:Int) {
let container = CKContainer(identifier: "iCloud.blah")
let publicDB = container.publicCloudDatabase
let newRecord = CKRecord(recordType: "BlahBlah")
newRecord.setObject(yourInt, forKey: "theInt")
var localChanges:[CKRecord] = []
var recordIDsToDelete:[CKRecord] = []
localChanges.append(newRecord)
let saveRecordsOperation = CKModifyRecordsOperation(recordsToSave: localChanges, recordIDsToDelete: nil)
saveRecordsOperation.perRecordCompletionBlock = { record, error in
if error != nil {
self.showAlert(message: error!.localizedDescription)
print(error!.localizedDescription)
}
dispatch_async(dispatch_get_main_queue()) {
// give the UI a all good sign for that record
}
}
saveRecordsOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
if error != nil {
self.showAlert(message: error!.localizedDescription)
print(error!.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
// give the UI a all good sign for all records
}
}
}
saveRecordsOperation.qualityOfService = .Background
publicDB.addOperation(saveRecordsOperation)
}
And here the code to read it back.
var readerOperation: CKQueryOperation!
func read4Cloud(theLink: String, theCount: Int) {
var starCount:Int = 0
let container = CKContainer(identifier: "iCloud.blah")
let publicDB = container.publicCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "BlahBlah", predicate: predicate)
readerOperation = CKQueryOperation(query: query)
readerOperation.recordFetchedBlock = { (record) in
let YourInt = record["theInt"] as! Int
}
readerOperation.queryCompletionBlock = {(cursor, error) in
if error != nil {
// oh dingbats, you need to check for one of those errors
} else {
// got it
}
}
readerOperation.qualityOfService = .Background
publicDB.addOperation(readerOperation)
}
But wait Matt, this is going to save a new record everytime, and read back multiple Ints when you re-open. No this solution needs some more work; and I haven't done the network or the cloud check or any of the errors... :\
Disclaimer; I edited this code in SO, it may not compile cleanly :)
Matt, the reason you should do this another way is cause it is far simpler and will almost certainly be more reliable than cloud kit.
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(yourInt, forKey: "blah")
Your got these options in swift ...
func setBool(value: Bool, forKey defaultName: String)
func setInteger(value: Int, forKey defaultName: String)
func setFloat(value: Float, forKey defaultName: String)
func setDouble(value: Double, forKey defaultName: String)
func setObject(value: AnyObject?, forKey defaultName: String)
func setURL(url: NSURL, forKey defaultName: String)
To fetch the value the next time
let defaults = NSUserDefaults.standardUserDefaults()
if let yourInt = defaults.integerForKey("blah")
{
print(yourInt)
}
You got a few more methods to get them back
func boolForKey(defaultName: String) -> Bool
func integerForKey(defaultName: String) -> Int
func floatForKey(defaultName: String) -> Float
func doubleForKey(defaultName: String) -> Double
func objectForKey(defaultName: String) -> AnyObject?
func URLForKey(defaultName: String) -> NSURL?
func dataForKey(defaultName: String) -> NSData?
func stringForKey(defaultName: String) -> String?
func stringArrayForKey(defaultName: String) -> [AnyObject]?
func arrayForKey(defaultName: String) -> [AnyObject]?
func dictionaryForKey(defaultName: String) -> [NSObject : AnyObject]?
And that is it; but wait, you want to use cloud kit... let me give you a second answer after I post this.