MCSv3

Mobile Development


Ilya Loshkarev
loshkarev.i@gmail.com

File Access

Sandbox

All files accessed by the app are part of app's sandbox

Bundle Resources


							// Get file URL
							let resourceURL = Bundle.main.url(forResource: "MyXmlConfig",
							withExtension: "xml")!
							let parser = XMLParser(contentsOfURL: resourceURL)
						

Assets and other resources that are built directly into bundle

Parameter List


							if let infoPlist = Bundle.main.infoDictionary,
								let testNumber = infoPlist["testKey"] as? NSNumber {
									print("\(testNumber)")
							}
						

Info.plist – xml storage of main project config

File Manager


							// Get file manager
							let fileManager = FileManager.default // Singleton
							// Get directory URL
							let docsURL: URL? = fileManager.urls(
								for: .documentDirectory,
								in: .userDomainMask).last
							// Create file URL
							let fileURL = docsURL!.appendingPathComponent(fileName)
						

A file manager object is your primary mode of
interaction with the file system

File Operations


							fileManager.removeItem( at: fileURL)
							//
							fileManager.copyItem( at: fileURL, to: targetURL)
							//
							fileManager.moveItem( at: fileURL, to: targetURL)
							//
							fileManager.createDirectory(/* ... */)
						

Location can be specified by either URL or String path

Raw Data


						if fileManager.fileExists(atPath: fileURL.path) {
							let data = try! Data(contentsOf: fileURL)
							try! data.write(to: newFileURL)

							let sameData = fileManager.contents(atPath: fileURL.path)
							fileManager.createFile(atPath: newFileURL.path,
								contents: sameData,
								attributes: nil)
						}
					

Data is a simple collection of bytes

Can be converted to and from many datatypes

Archiving

Object Graph

Object-oriented applications contain complex webs of interrelated objects

Archivies & Serializations

  • The serialization only preserves the values of the objects and their position in the hierarchy
  • The archive preserves the identity of every object in the graph and all the relationships it has with all the other objects in the graph

Keyed Archives

Values that an object encodes to a keyed archive can be individually named with an arbitrary string


						

Archives are hierarchical with each object defining a separate name space for its encoded values, similar to the object’s instance variables

NSCoder


							coder.encode(42, forKey: "42")
							let i = coder.decodeInt32(forKey: "42")
						

Objects are written to and read from archives
with coder objects

All coders adopt NSCoder protocol

NSCoding


							class Photo : NSObject, NSCoding {
								var author: String!
								var photoURL: String!
								required convenience init?(coder decoder: NSCoder) {
									self.init()
									author = decoder.decodeObject(forKey: "author") as? String
									photoURL = decoder.decodeObject(forKey: "photoURL") as? String
								}
								func encode(with coder: NSCoder) {
									coder.encode(author, forKey: "author")
									coder.encode(photoURL, forKey: "photoURL")
								}
							}
						

An object being encoded or decoded is responsible for encoding and decoding its instance variables

Keyed Archiver


							// Encode object to file
							NSKeyedArchiver.archiveRootObject(photos, toFile: fileURL.path)
							// Get object from file
							NSKeyedUnarchiver.unarchiveObject(withFile: fileURL.path) as! [Photo]
						

User Defaults


							// Set value for key
							UserDefaults.standard.set(url, forKey: "myURL")
							// Get value for key
							UserDefaults.standard.url(forKey: "myURL")
						

Standart keyed archive stored in Library/Preferences

Core Data

Core Data vs Archives

Core DataNSKeyedArchiver
Entity Modeling-
Querying-
SpeedFastSlow
SerializationSQLite or NSDataNSData
MigrationsAutomaticManual
Undo ManagerAutomaticManual

Concepts Stack

Core Data Stack. Yes, I know it doesn't look like a stack here.
						But conseptually it is a stack of objects with different functions.

Model

  • Describes the data that is going to be accessed by the Core Data stack
Core Data Model XCode

Compiled into a bundle resource .momd

Persistent Storage Coordinator

Connects model to data


						// Locate Model file
						let modelURL = Bundle.main.url(forResource: "DataModel",
							withExtension: "momd")!
						// Load Model
						let model = NSManagedObjectModel(contentsOfURL: modelURL)!
						// Create Store Coordinator for the model
						let psc = NSPersistentStoreCoordinator(managedObjectModel: model)
						

Creates new instances of the entities in the model
and retrieves existing instances from a persistent store

Persistent Store


							// Construct file URL
							let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
							let location = docURL.appendingPathComponent("DataModel.sqlite")
							// Create SQLite store and add it to Store Coordinator
							try! coordinator.addPersistentStoreWithType( NSSQLiteStoreType,
								configuration: nil, URL: location, options: nil)
						

Persistent store declares the way Core Data objects are stored


							// Supported Store Types
							NSSQLiteStoreType   // SQLite - loads required data from disk
							NSBinaryStoreType   // Binary - loads full object graph to memory
							NSInMemoryStoreType // Memory - doesn't occupy disk space
						

Managed Object Context

Manager for a collection of entity instances


						let context = NSManagedObjectContext(
							concurrencyType: .mainQueueConcurrencyType)
						// Should be connected to a persistent store
						managedObjectContext.persistentStoreCoordinator = coordinator
						

Context bound to UI should run in the main queue

Managed Objects

Every managed object should exist in context


							// Add new object into context
							let e = NSEntityDescription.insertNewObject(
								forEntityName:"Entity",
								into: context) as! Entity

							// Get some objects from context
							let request = NSFetchRequest<Entity>(entityName: "Entity")
							let results = try! context.fetch(request)
							for entity in results {
								entity.name = "I'm an entity!"
							}
							// Send everything to store
							try! context.save()
						

Persistent Container


						let container = NSPersistentContainer(name: "DataModel")
						container.loadPersistentStores(completionHandler: {
							(storeDescription, error) in
							if let error = error as NSError? {
								fatalError("Unresolved error \(error), \(error.userInfo)")
							}
						})
						container.context.save()
						

A container that encapsulates the Core Data stack

Availiable since iOS 10.0

Managed Objects

Model to Objects

Core Data Model to Context relationship

Entity

Entity describes a class of objects with their attributes and relationships
Entity Example

Entity Relationships

Relationship & Inverse Type

To-OneTo-Many
To-OneOne to OneOne to Many
To-ManyOne to ManyMany to Many

Assigment of inverse relationship helps
to preserve object graph integrity

Managed Object

Describes single instance of the entity


							class Bird: NSManagedObject {
								// NSManaged properties are mapped to model entities
								@NSManaged var name: String
								@NSManaged var latinName: String
								@NSManaged var data: String?
								@NSManaged var photo: String?
								@NSManaged var favourite: Bool
							}
						

Every managed object needs a context
to define its entity mappings

Entity Description

Specifies an entity’s name, its properties
and the class by which it is represented


							static let entityName = "Bird"
							static var entityDescription: NSEntityDescription {
								if #available(iOS 10.0, *) {
									// in iOS 10 default class method has been added
									return self.entity()
								} else {
									return NSEntityDescription.entity(
										forEntityName: entityName,
										in: Ornitary.context)!
								}
							}
						

Intialization


							// NSManagedObject subclass should override this initializer
							override init(entity: NSEntityDescription,
								insertInto context: NSManagedObjectContext?) {
								super.init(entity: entity, insertInto: context)
							}
							convenience init() {
								self.init(entity: Bird.entityDescription,
									insertInto: Ornitary.context)
								/* properties initialization here */
							}
						

Every object of type Bird will be inserted
into Ornitary context

Saving Context


							static func save () {
								if context.hasChanges {
									do {
										try context.save()
									} catch {
										fatalError("Failure to save context: \(error)")
									}
								}
							}
						

The creation of NSManagedObject instances
does not guarantee their persistence

Fetching Objects

NSFetchRequest retrieves data from persistent store


							// Create request for every object of specific entity
							let request = NSFetchRequest<Bird>(entityName: Bird.entityName)
							// Fetch data from the store
							let results = try! context.fetch(request)

							// Count number of results for request without fetching data
							let count = try! context.count(for: request)

							// In iOS 10 default entity fetchRequest method has been added
							let count2 = try! context.count(for: Bird.fetchRequest())
						

Filtering Results


							let birdsName = "Puffin"
							let latinNameSubstr = "pus"
							// Set number of results
							request.fetchLimit = 10
							// Set number of skipped objects
							request.fetchOffset = 10
							request.predicate = NSPredicate(
								format: "name == %@ OR latinName CONTAINS %@",
								birdsName, latinNameSubstr)
						

Predicates can access relationship properties
with ALL, ANY or NONE aggregate operations

Deleting Objects


							let birdsToDelete = try! context.fetch(request)
							for bird in birdsToDelete {
								context.delete(bird)
							}
							// Or you can just:
							context.delete(thisBirdInParticular)
						

Core Data Migration

Model Version

model version diagram

Migraion Type

Migraton allows to automatically convert entities
of an old model into entities of a new model

SettingsChanges
LightweightAutomaticAdd/Remove Entities
Mapping ModelCustomAny Model Changes
CustomMigration codeAnything

Lightweight Migration

Lightweight migrations are perfomed automatically
if the new version changes are limited to:

  • Add/Remove Entities
  • Add/Remove Entity Attributes
  • Add/Remove Relationships
  • Change Relationship Types

Store Options


							let options = [
								NSMigratePersistentStoresAutomaticallyOption : true,
								NSInferMappingModelAutomaticallyOption : true
							]
							try coordinator.addPersistentStore(
								ofType: NSSQLiteStoreType,
								configurationName: nil,
								at: location,
								options: options
							)
						

Related Resources