Crash using failable initializer - ios

I'm getting a strange crash using a failable initializer in Swift. I pretty sure it's a bug in Swift, but figured I do a quick post to see if I'm missing something obvious.
I'm writing an app using CloudKit and added a new convenience initializer to CKRecord:
extension CKRecord {
convenience init?(cloudInfo: NSData?) {
guard let info = cloudInfo else { return nil }
self.init(coder: NSKeyedUnarchiver(forReadingWithData: info))
}
This just uses archived data I created using CKRecord's encodeSystemFieldsWithCode: method to create a CKRecord instance. I used to simply have a method in my class to do this same thing and it worked fine.
When I call this initializer like this:
let record: CKRecord
if let rec = CKRecord(cloudInfo: self.cloudInfo) {
record = rec
} else {
record = CKRecord(recordType: "Item", recordID: CKRecordID(recordName: self.id))
}
When I make that call, I get a crash. It doesn't matter if I pass in nil for cloudInfo or a value. When stepping through the code, the crash seems to happen between returning from the init call and getting back to the caller. For example, if I pass nil and step into the guard statement in the init that seems to work, but as soon as I step off of it, I crash.
I've tried getting rid of guard and going with a simple if let construct, but same outcome. And, I've also just returned nil from the init without any other code, and that also crashes.
I've done the usuals: cleaned the build folder, rebooted, etc.
BTW, by "crash" I mean I'm getting: EXC_BAD_ACCESS (code=1, address=0xfffffffc) at the call site.
Does anyone have any ideas? Maybe it's something obvious that I'm missing.
Thanks.

According to The Swift Programming Language (Swift 2.1) book,
For classes, however, a failable initializer can trigger an initialization failure only after all stored properties introduced by that class have been set to an initial value and any initializer delegation has taken place.
Taken from here.
You can't extend an existing class with stored properties, so the first requirement is met. But to meet the second requirement, you have to delegate your initialization process like in code below:
extension CKRecord {
convenience init?(cloudInfo: NSData?) {
guard let info = cloudInfo else {
self.init(coder: NSKeyedUnarchiver(forReadingWithData: NSData()))
return nil
}
self.init(coder: NSKeyedUnarchiver(forReadingWithData: info))
}
}

Actually in Swift 3, there seems to be a bug and a regression from Swift 2: returning nil from a failable intializer crash the runtime.

Related

Sometimes getting crash when getting computed variable

I'm getting a crash in the following code:
var gmsBounds: GMSMutablePath {
get {
let path = GMSMutablePath(with: self.boundaries)
return path!
}
}
Currently it crashes when initializing the GMSMutablePath according to Crashlytics. However this code isn't ever called before the class is initialized, and self.boundaries is stored during initialization, so I'm not sure how self.boundaries could be nil. I'm also personally unable to recreate the crash, so it is not a bug that affects every user. Any help is appreciated, computed variables are a new concept for me.
When I make code reviews always i avoid forced unwrapping, at first sight you can believe that it never will turn to nil but I prefer avoid that
return path!
Try to unwrapping (safely) it first or change the variable definition for optional:
var gmsBounds: GMSMutablePath? {
get {
let path = GMSMutablePath(with: self.boundaries)
return path!
}
}

Swift memory leak when iterating array of Errors

I'm relatively new to Swift, so I hope I'm not asking a stupid question.
I have some code that instantiates an array of type Error, which will later be iterated and printed to the console. When running this code through Instruments using the "Leaks" instrument, it shows a leak of _SwiftNativeNSError. If I change the array type from [Error] to [Any], the leak disappears, even though it is still actually holding an object conforming to Error. The leak is not reproducible with any other data types or protocols that I've tried.
Here's some sample code:
class myLeak {
lazy var errors = [Error]()
enum err: Error {
case myFirstError
}
func doSomething() {
errors.append(err.myFirstError)
for error in errors {
print(String(describing: error))
}
}
}
// call with let myleak = myLeak(); myleak.doSomething()
Calling the doSomething() function immediately creates a leak. Switching [Error]() to [Any]() resolves the leak, but I'm not happy with this as a solution without understanding the underlying problem. The problem is also solved by changing [Error]() to my enum implementing the Error protocol: [err](). I've also tried creating my own custom protocol just to prove if this is being caused specifically by Error, and I'm only able to reproduce the problem when using Error; my own custom protocol did not exhibit this behaviour.
Originally, my code used a forEach loop to iterate the array, but I then tried re-writing it to use a standard for loop in case the closure in forEach was causing the issue, but this didn't work.
I'm suspicious that this may be a Swift bug (in which case, I will open an issue for it), but there's also a chance that I'm missing a key piece of understanding. If what I'm doing is bad practice, I'd like to understand why.
Update:
After speaking with Joe Groff, an Apple engineer, this is the bug you could have encountered: https://bugs.swift.org/browse/SR-6536
Original Answer
I've played a bit with your code and I think the problem is due to Error type.
In fact, taking the code by Josh, you can find a different behaviour if you use Error or MyError as the type of your array.
I guess the problem arises since the deinit call is not forwarded to CustomObject since Error is just a protocol and it's not aware of the underlying class. While, MyError is. We can wait for other people to have clarifications on this behaviour.
Just for simplicity, I'm using a Playground here. See that I'm not even trying to print the error value.
import UIKit
class ViewController: UIViewController {
var errors: [Error] = [] // change to MyError to see it working
enum MyError: Error {
case test (CustomObject)
}
class CustomObject {
deinit {
print("deiniting")
}
}
override func viewDidLoad() {
super.viewDidLoad()
let testerror = MyError.test(CustomObject())
errors.append(testerror)
errors.removeAll()
}
}
do {
let viewController = ViewController()
// just for test purposes ;)
viewController.viewDidLoad()
}
I tested your code and it looks like the String(describing) statement is causing the string to retain the error, which is just weird. Here is how I can tell: I created an associated object that prints out when its being deinitialized.
import UIKit
class ViewController: UIViewController {
var errors = [Error]()
override func viewDidLoad() {
super.viewDidLoad()
class CustomObject {
deinit {
print("deiniting")
}
}
enum MyError: Error {
case test (CustomObject)
}
let testerror = MyError.test(CustomObject())
errors.append(testerror)
for error in errors {
//print(String(describing: error))
}
errors.removeAll()
}
}
When the print doesn't run, sure enought he associated object is deinitialized at when the error is removed from the array and the output is:
deiniting
Uncomment the print and the output becomes:
test(CustomObject #1 in stack.ViewController.viewDidLoad() -> ())
At first I thought it was the print that's the problem but If I refactor to:
errors.forEach{print($0)}
I get the output:
test(CustomObject #1 in stack.ViewController.viewDidLoad() -> ())
deiniting
But if I change it to:
errors.map {String(describing:$0)}.forEach{print($0)}
Then deinit is no longer called:
test(CustomObject #1 in stack.ViewController.viewDidLoad() -> ())
Weirdness. Maybe file a radar?
This bug was Fixed in Xcode 9.3.

Core Data Swift cast failure in generic function in optimized builds

We have an app with a fairly broad Core Data model, with lots of custom subclasses implemented in Objective C, but which are also used by a growing fraction of the app that's written in Swift. (For what it's worth: we're building with Xcode 7.3.1 against iOS 9.3, and the Swift code is thus 2.2.)
There's a helper function written in Swift that looks like this:
extension NSManagedObject {
func inContext<T: NSManagedObject>(moc: NSManagedObjectContext) -> T? {
guard self.managedObjectContext != moc else {
return (self as! T)
}
do {
let obj = try moc.existingObjectWithID(self.objectID)
return (obj as! T) // <--- fails here
}
catch let error {
return nil
}
}
}
This is called in a fair number of places where objects jump contexts. Calling code generally looks like this:
let result: ECFoo? = foo.inContext(managedObjectContext)
This works flawlessly in debug builds of the app. But with optimizations turned on, I'm running into a case where this call fails at the line I've marked, where the fetched object is being cast from NSManagedObject to the correct subclass. The stack trace starts with swift_dynamicCastObjCClassUnconditional, and the message that gets logged to the console is:
Could not cast value of type 'ECFoo_Foo_' (0x7fb857d2c250) to 'ECFoo_Foo_' (0x7fb857d2c250).
If I put a breakpoint on that line, what I'm attempting to do seems fine in the debugger console:
(lldb) po moc.existingObjectWithID(self.objectID) is ECFoo
true
This is deeply confusing, because it's clearly the same type on both sides here, and they both appear to be the dynamically generated subclass, rather than the formal class that it should be trying to cast to (based on inference of the calling code). I can only assume that there's some piece of information being optimized away that is necessary to make this work, but I'm not entirely sure how to fix it.

Swift - casting a nil core data string as an optional value

I have a field stored on a core data object called "metadata" which is of type String (no optional, because Apple docs say not to mess with optionals in CD). Sometimes, the metadata field is nil. In checking whether this value is nil, I do the following check:
if object.metadata as String? != nil {
...
}
However, my code continuously crashes on this line as an EXC_BAD_ACCESS. I have also tried:
if let metadata = object.metadata as String? {
...
}
Which doesn't work either. I cast objects successfully to optionals in other parts of my code, so I don't understand why this particular case isn't working. How do you check whether a core data property is a nil string?
It looks like what you really want is this:
if object.metadata != nil {
...
}
or this:
if let metadata = object.metadata as? String {
// You can now freely access metadata as a non-optional
...
}
--EDIT--
My mistake, I didn't read the first part of your question thoroughly enough. It looks like the duplicate answer has a solution for this. Essentially, the generated managed object subclass is a bug and you should modify the properties to be either optional or implicitly unwrapped. You can check both of those using the first method for implicitly unwrapped and second for optionals.
There are several questions which discuss the issue of the generated subclasses not producing optional properties. I wouldn't be too concerned about editing the subclasses; there's nothing special about them except that Apple is making it easier to create them.
Check if property is set in Core Data?
Swift + CoreData: Cannot Automatically Set Optional Attribute On Generated NSManagedObject Subclass
--Edit2--
If you really don't want to touch the subclass you can access the property using valueForKey() and could add that as an extension if you wanted something a bit cleaner.
if let metadata = object.valueForKey("metadata") as String? {
...
}
In an extension:
extension ObjectClass {
var realMetadata: String? {
set {
self.setValue(newValue, forKey: "metadata")
}
get {
return self.valueForKey("metadata") as String?
}
}
}

updateValue not working for Dictionary

I'm creating a test app using Swift in Xcode, and I've run into an annoying issue. I'm writing a simple class that will act as a cache using a Dictionary object. My implementation is below:
import Foundation
import UIKit
class ImageCache {
var dict:Dictionary<String,NSData>?;
init() {
dict = Dictionary<String,NSData>();
}
func exists(id:String) -> Bool {
return dict!.indexForKey(id)!==nil;
}
func getImage(id:String) -> UIImage? {
if(!exists(id)) {
return nil;
}
return UIImage(data: (dict!)[id]);
}
func setData(id:String, data:NSData) {
dict!.updateValue(data, forKey: id);
}
}
The issue is in the last method, with Xcode stating "Could not find member 'UpdateValue'". This is weird, because the code hint seems to show it just fine:
But when I try to compile:
Could this potentially be a bug in Xcode? Or am I missing something super-obvious?
this is not a bug or a quirk in the compiler.
it is how Optional implemented (which may be flawed or not)
what happened is that the Optional store the Dictionary as immutable object (with let perhaps). So even Optional it is mutable, you can't modify the underlying Dictionaryobject directly (without reassign the Optional object).
updateValue(forKey:) is mutating method, you can't call it on immutable object and hence the error.
you can workaround it by doing
var d = dict!
d.updateValue(data, forKey: id)
because you copy the dictionary to another mutable variable, which then is mutable and able to call mutating method on it
but without dict = d, your change won't be applied on dict because Dictionary is value type, it makes copy on every assignment
related answer
Smells like a bug or a quirk in the compiler.
I just tried something like
var d = dict!
d.updateValue(data, forKey: id)
and it works as expected.

Resources