Core Data: Beyond the Basics

Jul 26 2022 · Swift 5.5, iOS 15, Xcode 13.3.1

Part 2: Advanced Core Data

19. Storing Large Files

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 18. Deleting Launch Lists Next episode: 20. Conclusion

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 19. Storing Large Files

So far, everything you’ve modeled in your entities have been primitive data types - Ints, Strings, Dates and Booleans. What if you wanted to model data that didn’t fit into one of these categories? What about media such as audio, video or an image? Can Core Data handle this? It most certainly can.

Navigate to the model file and select the RocketLaunch entity. Add a new attribute named attachment. Using this attribute you’re going to store an image that the user attaches when creating a launch. So what type should this attribute be? If you click on the drop down, you’ll notice two types that are not very obvious as to what they store - Binary Data and Transformable. Set it to Transformable.

A Transformable attribute allows you to store data of types that are not primitive values and it does so by transforming these values into simple data representations. Let’s say you wanted to store the launchpad of a RocketLaunch not as a string value as you have done, but as a class with some additional fields.

You would mark the landing pad field in your data model as transformable and use a value transformer object to convert your class into an instance of Data. Core Data then stores this raw data instead of your custom type.

When you want to retrieve a launch and access the priority value, Core Data again uses the value transformer to convert it back to an instance of your struct

By the way, if you’ve been looked at the entity description for SpaceXLaunch, you’d see that many of the properties are Transformable - the data coming from the API is a bit more complex. Transformable allows you to simplify that data for storage in Core Data.

We’re not going to touch on value transformers because if you use an object that inherits from NSObject and implements the NSCoding protocol, then Core Data can transform it automatically. So for example if you wanted to store a background color, which is represented by UIColor, Core Data can automatically do that for you.

Back to demo

The same applies for instances of UIImage. With the attribute selected in the model editor, switch over to the Data Model Inspector. You’ll see the usual options here - whether this property is optional and so on. But below that you can specify the name of your value transformer. You can leave this empty since UIImage is handled for you automatically. Next is a custom class attribute. By default when using transformable attributes the generated code will be of type NSObject since subclasses of NSObject are the only ones that can be transformed. If you wanted the generated code to be a more specific type instead this is where you would list it.

Navigate to RocketLaunch+CoreDataProperties and add code to represent your new attribute. Start by importing UIKit

import UIKit

Then add the property.

@NSManaged public var attachment: UIImage?

Build the app to make sure everything works. Now navigate to LaunchCreateView.swift. At the bottom of the file there is code to load an image picker and select an image. Let’s save this in core data. First declare a property to hold on to the image

@State var attachment: UIImage?

Next, inside the .sheet call, have the ImagePicker load with the attachment property as the input:

.sheet(isPresented: $showImagePicker) {
	ImagePicker(selectedImage: $attachment)
}

When the image picker sets the image, it will use this binding.

Now, in the RocketLaunch.createWith method right above, add an argument to set the attachment:

launchpad: self.launchpad,
attachment: self.attachment,
tags: tags,

Finally for this file, update the section that contains the picker to either show the selected image or the picker, depending on the state of attachment:

if let attachment = attachment {
	Image(uiImage: attachment)
	  .resizable()
	  .aspectRatio(contentMode: .fit)
} else {
	Button("Pick Image") {
	  showImagePicker.toggle()
	}
}

Next, open RocketLaunch+CoreDataProperties and update the createWith method to match:

launchpad: String,
attachment: UIImage?,
tags: Set<Tag> = [],
    
//.....

launch.attachment = attachment

Build and run the app. Navigate all the way to the add launch modal. If you tap on the attachments row, the image picker should come up and you can select an image. Give your launch a title, launchpad and notes, and if you hit save it should work as expected. So that’s one way you can save data types that aren’t your basic, run of the mill types. Many types have built in value transformers so you get a lot of behavior for free and in the off chance you want to transform a custom type you can always create your own transformer.

The second option at our disposal is the Binary Data type. Navigate back to the data model and change the type of the attachment attribute to Binary Data. In RocketLaunch+CoreDataProperties change the type of the managed property to optional Data instead.

@NSManaged public var attachment: Data?

In the createLaunch method in RocketLaunch+CoreDataProperties, instead of assigning the image to the property, assign a data representation of the image by calling the jpegData() method.

launch.attachment = attachment?.jpegData(compressionQuality: 1) ?? Data()

Before you build and run the app, switch to the simulator and remove the existing app. Remember that you have an instance of RocketLaunch saved with a transformable value and now you’ve changed the object model. That will cause it to crash. Build and run the app, and again you should be able to navigate all the way to the New RocketLaunch modal, create one and save with no issues.

You’re probably very tempted to go with transformable, right? Core Data automatically handled converting the image to data and back which is super convenient. There is a significant advantage with Binary Data though. If you navigate back to the data model editor and select the attachment attribute you should see in the model inspector on the right a checkbox that says “Allows External Storage”. Media like images and videos, especially those you take on modern iOS devices, can have large file sizes and if you chose to save these files as data directly in Core Data then as the number of records in your store grows, you will see an impact to performance as Core Data tries to fetch all these large objects for you. Go ahead and select it.

By checking this box Core Data stores your image files in external storage, that is on iOS’s file system, instead of the persistent store.

If the image is smaller than 10MB then it is stored directly in the store, otherwise it is stored externally and Core Data maintains a reference to the storage location instead.

Typically when it comes to media files you’ll want to go with Binary Data since you don’t have to worry about file size impacting you. There are two more ways you can store large files, and we’ll touch on them briefly because these are more intermediate topics.

Earlier we talked about faulting. If you don’t remember, here’s a quick recap - Faults are placeholder objects used in the object graph in memory that are realized when you access the property.

Back to demo. Have the simulator running to show this

Why does this matter? Think about how the UI in your app is set up. Each row represents a launch but you’re not showing the attachments inline inside the row. You could allow the user to tap a disclosure indicator on each row to bring up a detail view of the launch and in there you can show the attachment. But because attachment is defined on the RocketLaunch, when you fetch all launches for a list Core Data is going to do the work to fetch all large image files for you as well. This isn’t ideal.

The way you can solve that is by relying on faulting behavior. Instead of defining attachment as an attribute, you could define a new Entity named Attachment that stores the image as binary data ideally in external storage, and then define an optional relationship on RocketLaunch to reference an attachment. Since it is a relationship, Core Data will use a placeholder object until you actually tap on a RocketLaunch and go to the detail view. At that point Core Data will load the data from its external store for use. This way you get the benefit of external storage and fetching only when it is needed.

Back to demo. Start in data model file -> RocketLaunch entity.

Now that last way is the most manual of all. You’ll notice in the attribute type selector you can specify a URI or uniform resource identifier as a type. If you pick this then you can explicitly choose where you save large files and then store the reference to the file as a URL. Everything else, is totally up to you.