Search code examples
ioscore-dataarkit

Is CoreData a good way to store 3D Models for an AR App


I wanted to know if storing 3D Models in Core Data is efficient and makes sense? If not, what would be the optimal solution? Something like Firebase or?

I am currently using CoreData but I am concerned about the storage and performance if the user imports a larger model.


Solution

  • I don't think core data will have any noticeable performance impact on the whole process. The process being that large files or blobs of data are imported/downloaded, then stored and later read, parsed and used in your application.

    In core data model you can have a field of type Binary Data with option Allows External Storage. This is designed to store your data (when required) as external file. The file is not loaded until you actually access the property of your item. And even after you access it it should return data as mapped file so that memory consumption is minimal.

    But on the other hand I see no benefits of using database in the first place. A database will only come in handy once you can expect large number of items, introduce relation and more. It will come in handy if you need to use fetched result controllers or use queries to get items based on filters like searching text. And none of those can be expected on binary data.

    If your items do include some metadata, for instance your models have tags and authors and comments and so forth then it would be nice to place this data into a database and benefit from everything that database can offer. After that you can still choose to either keep the binary data in your database or you can use normal files to save the binary data while saving file names in the database.

    To generally work with files there is a little bit of effort but not too much. Probably way less than implementing any database. A quick implementation may look something like the following

    class ModelsFileManager {
        
        struct ModelMetadata: Codable {
            fileprivate let localID: UUID
            let name: String
            let author: String
            let tags: [String]
        }
        
        let modelPathExtension: String = "3DModel"
        let metadataPathExtension: String = "3DModelMetadata"
        let folderName: String
        
        private let directoryPath: URL
        
        init(folderName: String = "3D models") {
            self.folderName = folderName
            directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appending(component: folderName)
        }
        
        private func modelPathWithID(_ id: UUID) -> URL {
            directoryPath.appendingPathComponent(id.uuidString).appendingPathExtension(modelPathExtension)
        }
        
        private func metadataPathWithID(_ id: UUID) -> URL {
            directoryPath.appendingPathComponent(id.uuidString).appendingPathExtension(metadataPathExtension)
        }
        
        func insertNewModel(file: URL, modelInfo: (name: String, author: String, tags: [String])) throws {
            let metadata = ModelMetadata(localID: .init(),
                                         name: modelInfo.name,
                                         author: modelInfo.author,
                                         tags: modelInfo.tags)
            
            if !FileManager.default.fileExists(atPath: directoryPath.path) {
                try FileManager.default.createDirectory(at: directoryPath, withIntermediateDirectories: true)
            }
            
            try FileManager.default.moveItem(at: file, to: modelPathWithID(metadata.localID))
            let metadataFile = try JSONEncoder().encode(metadata)
            
            try metadataFile.write(to: metadataPathWithID(metadata.localID))
        }
        
        func loadAllMetadata() throws -> [ModelMetadata] {
            try FileManager.default.contentsOfDirectory(at: directoryPath, includingPropertiesForKeys: nil)
                .filter { $0.pathExtension == metadataPathExtension }
                .compactMap {
                    let data = try Data(contentsOf: $0)
                    return try? JSONDecoder().decode(ModelMetadata.self, from: data)
                }
        }
        
        func modelPathForMetadata(_ metadata: ModelMetadata) -> URL {
            modelPathWithID(metadata.localID)
        }
        
    }
    

    The idea would be to download your model to a temporary directory and then move it using insetNewModel with all the info attached. After that loadAllMetadata should give you all the power to access and manage your models and additional info. You can get an URL to your model which can then be converted to Data if needed.