Search code examples
iosxcodeswiftpropertiesdefined

How to check in Swift if an object it's invalid?


I would like to check if an object is defined or exists using Swift.

Something like this:

if (isset(Object)){

}

I've a problem using:

if let x = myObject.property {
    //My code
} <- Here I got 'EXC_BAD_ACCESS' 

Usually the code works, but sometimes fail. When fail in the debugging myObject is defined and have other properties, but when I try:

myObject.property in debugger shows: "Invalid expression"

myObject.otherProperty <- Works!

It's not the first time that I've got that message, the last one was with an object of a UIViewController and the property 'view'. This time is happening with an object of a custom class.

Thanks in advance!

My code:

class DetallesEntidadController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        if let canal = Canal.getCanal(getManagedContext()) {
            if let imagenFondo: Imagen = canal.imagenFondo {
                if let view = self.view {
                    let ivBackgroundImage = UIImageView(image: Utils.loadImageFromPath(imagenFondo.nombreFichero!))
                    ivBackgroundImage.frame = view.frame
                    var bottomView = CGFloat()
                    if let height = self.navigationController?.navigationBar.frame.height {
                        ivBackgroundImage.frame.origin.y = -height-Utils.getStatusBarHeight()
                        bottomView = view.frame.origin.y+view.frame.size.height-Utils.getStatusBarHeight()-height
                    } else {
                        bottomView = view.frame.origin.y+view.frame.size.height-Utils.getStatusBarHeight()
                    }
                    ivBackgroundImage.frame.origin.y = bottomView - ivBackgroundImage.frame.height

                    view.addSubview(ivBackgroundImage)
                    view.sendSubviewToBack(ivBackgroundImage)
                }
            } <- Thread 1: EXC_BAD_ACCESS (code=1, address=0x........)
        }
    }
}

My classes:

import Foundation
import CoreData

class Canal: NSManagedObject {
    @NSManaged var titulo: String?
    @NSManaged var version: NSNumber?
    @NSManaged var imagenFondo: Imagen?
}

internal func persist(managedContext: NSManagedContext, xmlIndexerCanal: XMLIndexer){...}
internal static func getCanal(managedContext: NSManagedContext) -> Canal? {...}

import Foundation
import CoreData

class Imagen: NSManagedObject {

    @NSManaged var nombreFichero: String?
    @NSManaged var titulo: String?
    @NSManaged var url: String?

    internal func persist(managedContext: NSManagedObjectContext, strUrl: String){...}

}

The property that fail it's 'canal.imagenFondo' but 'canal.titulo' works, the error only happend sometimes.

Added:

func getManagedContext() -> NSManagedObjectContext {
    let delegado = UIApplication.sharedApplication().delegate as! AppDelegate
    return delegado.managedObjectContext
}

lazy var managedObjectContext: NSManagedObjectContext = {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
    let coordinator = self.persistentStoreCoordinator
    var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}()

Solution (Thanks Rob Napier):

My NSManagedObject require to be used in the same thread that the context that created it. The method getManagedContext() was replaced for:

func getManagedContextMainQueue() -> NSManagedObjectContext {
    let delegado = UIApplication.sharedApplication().delegate as! AppDelegate

    let coordinator = delegado.persistentStoreCoordinator
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}

Solution

  • This code is unsafe, and is likely the cause of your problem:

        if let canal = Canal.getCanal(getManagedContext()) {
            if let imagenFondo: Imagen = canal.imagenFondo {
    

    canal is a managed object. It can only be accessed on the queue associated with its context. Managed objects are not thread safe. I suspect that getManagedContext() returns a context that is not tied to the main (UI) queue. In that case canal.imagenFondo is illegal.

    Since this is UI code, the correct approach is to create a managed object context that is tied to the main queue (NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)) and fetch all UI-accessed objects via that context.

    For non-UI code, you will often need to wrap access to managed objects in a context.performBlock() to make sure that all access happens on the right queue.

    See the Core Data Concurrency guide for more.