iOS interview questions (7)
iOS 2018 Series: Cracking iOS interview or Become iOS expert (7)
Chapter 7: Core Data & Concurrency
Let’s suppose , you’re developing a small or simple application, then you probably don’t see the benefit of running Core Data operations in the background. However, what would happen if you imported hundreds or thousands of records on the main thread during the first launch of your application? The consequences could be dramatic. We have face all these kind of issue in real programming world when worked with core data so lets discuss the solution..
Basics:
Core Data is a framework that you use to manage the model layer objects in your application
Concurrency is the ability to work with the data on more than one queue at the same time.
Steps:
* Initializing the Core Data Stack
- Create Manage object model instance using model file
- Create persistent store coordinator using Manage object model instance
- Add persistent store in PSC instance using SQLite or other type and file path for SQLite dB
-
Create Managed Object Context with concurrency type —
NSMainQueueConcurrencyType
andNSPrivateQueueConcurrencyType
. - Set persistent store coordinator in Manage object model instance.
* Creating and Saving Managed Objects
- Create entity using NSEntityDescription entity
- Create instance of row which need to be inserted which will be Managed Object instance using entity and MOC.
- set Value in MO instance
- Save MO entry in core data — context. Save()
* Fetching Objects
- Prepare the request of type NSFetchRequest for the entity
let request = NSFetchRequest<NSFetchRequestResult>(entityName: “Users”)
2. Fetch the result from context in the form of array of [NSManagedObject]
let result = try context.fetch(request)
3. Iterate through an array to get value for the specific key
for data in result as! [NSManagedObject]
Core Data Stack Setup — The Easy Way (NSPersistentContainer)
Setting up a Core Data stack takes some work. A typical setup needs a number of steps:
- Load the managed object model from the application bundle
- Create a persistent store coordinator with the model
- Ask the coordinator to load a persistent store
- Create one or more managed object contexts
Starting with iOS 10 the NSPersistentContainer
removes most of this boilerplate code. A container that encapsulates the Core Data stack in your
application.
If you adopt some simple naming conventions and the default store location the setup of a core data stack can be as simple as this:
container = NSPersistentContainer(name: "storeName")
container.loadPersistentStores { (storeDescription, error) in
if let error = error {
fatalError("Failed to load store: \(error)")
}
}
you need to set the store description before you load the store.
Persistent Store Descriptor
A persistent container collects the settings for a persistent store into a persistent store descriptor. To override the defaults create a new descriptor before loading the store. You create a persistent store descriptor with the URL for the store.The most commonly used configuration settings:
-
type:
String
constant specifying the store type (default isNSSQLLiteStoreType
). -
isReadOnly:
Bool
set totrue
for a read only store (default isfalse
). -
shouldAddStoreAsynchronously:
Bool
set totrue
to add a store to the coordinator asynchronously on a background thread. The default isfalse
which adds the store synchronously on the calling thread. -
shouldInferMappingAutomatically:
Bool
if the flagshouldMigrateStoreAutomatically
istrue
try to infer a mapping model when migrating. Default istrue
. -
shouldMigrateStoreAutomatically:
Bool
migrate the store automatically. Default istrue
.
So to create a read-only persistent store that loads asynchronously on a background thread (and by default that migrates automatically):
let url = NSPersistentContainer.defaultDirectoryURL()
let description = NSPersistentStoreDescription(url: url)
description.shouldAddStoreAsynchronously = true
description.isReadOnly = true
container.persistentStoreDescriptions = [description]
Getting the Main View Context
The persistent container has a convenient read-only property
named
viewContext
to
get the
managed object context
for the
main queue. As the name suggests this is the context you should be using
when working with your user interface.
container.viewContext.perform({
// ...
})
Performing a Background Task
To avoid blocking the user interface you should not use the main view context for time consuming tasks. Create a private managed object context and execute the task in the background. The persistent container has a convenience method that takes care of creating a temporary private context for you and takes a block to execute:
container.performBackgroundTask({ (context) in
// ... do some task on the context
// save the context
do {
try context.save()
} catch {
// handle error
}
})
Getting a Private Context
You can also just get a new private context to use anyway you see fit:
let context = persistentContainer.newBackgroundContext()
context.perform({
// ...
})
Core Data, Multithreading, and the Main Thread
Working with Core Data on multiple threads is actually very
simple from a theoretical point of view.
NSManagedObject
,
NSManagedObjectContext
, and
NSPersistentStoreCoordinator
aren't thread safe.
Thread confinement:
Create a managed object context for every thread that interacts
with Core Data.
NSManagedObjectContext
class isn't thread safe.
So far, we’ve learned that you need multiple managed object contexts if you perform Core Data operations on multiple threads. The caveat, however, is that managed object contexts are unaware of each others existence. Changes made to a managed object in one managed object context are not automatically propagated to other managed object contexts. How do we solve this problem?
There are two popular strategies that Apple recommends, notifications and parent-child managed object contexts.
- Notifications
Managed object context posts three types of notifications:
-
NSManagedObjectContextObjectsDidChangeNotification
: This notification is posted when one of the managed objects of the managed object context has changed. -
NSManagedObjectContextWillSaveNotification
: This notification is posted before the managed object context performs a save operation. -
NSManagedObjectContextDidSaveNotification
: This notification is posted after the managed object context performs a save operation.
When a managed object context saves its changes to a persistent store, via the persistent store coordinator, other managed object contexts may want to know about those changes.
2. Parent/Child Managed Object Contexts
Creating a child managed object context is only slightly
different from what we’ve seen so far. We initialize a child
managed object context by invoking
init(concurrencyType:)
. The concurrency type the initializer accepts defines the
managed object context's threading model. Let's look at
each concurrency type.
-
MainQueueConcurrencyType
: The managed object context is only accessible from the main thread. An exception is thrown if you try to access it from any other thread. -
PrivateQueueConcurrencyType
: When creating a managed object context with a concurrency type ofPrivateQueueConcurrencyType
, the managed object context is associated with a private queue and it can only be accessed from that private queue. -
ConfinementConcurrencyType
: This is the concurrency type that corresponds with the thread confinement concept we explored earlier. If you create a managed object context usinginit()
, the concurrency type of that managed object context isConfinementConcurrencyType
. Apple has deprecated this concurrency type as of iOS 9. This also means thatinit()
is deprecated as of iOS 9.
There are two key methods that were added to the Core Data
framework when Apple introduced parent/child managed object
contexts,
performBlock(_:)
and
performBlockAndWait(_:)
. Both methods will make your life much easier. When you call
performBlock(_:)
on a managed object context and pass in a block of code to
execute, Core Data makes sure that the block is executed on the
correct thread. In the case of the
PrivateQueueConcurrencyType
concurrency type, this means that the block is executed on the
private queue of that managed object context.
For more details — https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/Concurrency.html#//apple_ref/doc/uid/TP40001075-CH24-SW1
Bonus time: @IBDesignable and @IBInspectable
IBDesignable
IBDesignable attribute will identify the UIView or the elements inherited from UIView — Eg: UIButton, UIImageView, UILabel etc..
For example , I created a Custom UIButton class,
@IBDesignable
open class MYHighLightedButton: UIButton {
public override init(frame: CGRect) {
super.init(frame: frame)
setTitle("MyTitle", for: .normal)
setTitleColor(UIColor.blue, for: .normal)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
You can add custom view in your xib , In the Interface Builder (`StoryBoard`) drag and drop a UIButton. Go to the `Show the Identity Inspector` and set the Class and Module as MYHighLightedButton.
IBInspectable
Lets add some custom properties our button. 🙌 🙌
For this we have to use IBInspectable attribute. Let’s see how we can add them.
@IBInspectable
public var cornerRadius: CGFloat = 2.0 {
didSet {
self.layer.cornerRadius = self.cornerRadius
}
}
This will add the corner_radius property to your button. You will be able to see them in the Attributes Inspector .
How to read only a few attributes of an entity?
We can use NSFetchRequest class’s property “setPropertiesToFetch”. We can pass a array of properties in string format in setPropertiesToFetch method.
Core Data vs Sqlite?
Core Data Works on object graph management. Operates on objects stored in memory. Can create millions of new objects in-memory very quickly.
Sqlite Works on table structure with sql queries. Operates on data stored on disk. Can be slow to create millions of new rows, as it is a disk I/O operation & Have keys like primary, composite keys.
Knowledge is power!