Data corruption when External Storage enabled for Binary attributes

Originator:rodhan
Number:rdar://44884416 Date Originated:9/29/2018
Status: Resolved:10/18/2018
Product:iOS + SDK Core Data Product Version:iOS 12
Classification: Reproducible:Yes
 
On iOS 12, binary data stored in Core Data with the External Storage option gets corrupted/goes missing on every second save of the context.

Steps to Reproduce:

See attached minimal sample project, or you can follow steps 1-4 below to create a project in Xcode.  Then follow steps 5-8 to see the issue.

1. Create new project in Xcode 10 (iOS, Single View App, Core Data)
2. Add Entity to model, with a single attribute of type Binary, with Allow External Storage enabled
3. Add a PNG file called “sample_image.png" to the project.  Ours is 400x400 pixels and 200KB in size.
4. Replace the contents of ViewController.swift with the following:

```
import UIKit
import CoreData

class ViewController: UIViewController {
    var button: UIButton!
    var context:NSManagedObjectContext?
    
    private var fetchedRows: [Entity] {
        get {
            let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Entity")
            return try! context!.fetch(request) as! [Entity]
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let appDelegate = UIApplication.shared.delegate as! AppDelegate
        context = appDelegate.persistentContainer.viewContext
        
        // Ensure there is one row in the database
        if (fetchedRows.isEmpty) {
            _ = Entity(context: context!)
            try! context?.save()
        }
        
        button = UIButton(frame: view.bounds)
        button.backgroundColor = UIColor.blue
        button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
        view.addSubview(button)
        
        updateButtonBackgroundImage()
    }
    
    func updateButtonBackgroundImage() {
        var image: UIImage?
        
        if let data = fetchedRows.first!.attribute {
            image = UIImage(data: data)
        } else {
            print("Image missing from database!")
        }

        button.setBackgroundImage(image, for: .normal)
    }
    
    @objc func buttonPressed() {
        fetchedRows.first!.attribute = UIImage(named: "sample_image.png")?.pngData()

        try! context?.save()
        
        // Wait a little while before reading back value from db
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
            self.updateButtonBackgroundImage()
        }
    }
}
```

5. Run the app on the Simulator or device
6. The UI consists of a full screen button that has an image from the database as its background, it will initially be blank.
7. Press anywhere on the screen to click the button which will attempt to save the sample image to the database, and then refresh the background of the button to show the image from the database

Expected Results:

The sample image should be displayed on the button after each save.

Actual Results:

Every second time, the image is not being present in the database, and therefore the button background images goes back to being blank.

Version/Build: iOS 12

Comments

I'm having the exact same issue.

By aumann.jennifer at Oct. 8, 2018, 2:22 a.m. (reply...)

Having the same issue. We are experiencing images that are nil, after a app restart. Not all images are nil, but some do not get saved correctly. Very frustrating, and definitely a bug that should not have existing in a iOS release.

May be related

https://forums.developer.apple.com/thread/109189

Same issue here

I have a very similar issue in a similar setup in my project.

I filed a question on stackoverflow: https://stackoverflow.com/questions/52586564/ios-12-specific-problem-core-data-updates-external-storage-binary-data-even-whe


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!