$30 off During Our Annual Pro Sale. View Details »

Using Core Data in Swift

Using Core Data in Swift

Core Data is probably loved as much as it is shunned by iOS developers. No matter its pros and cons, it remains a popular choice for many iOS developers as Apple continues to invest in it. Core Data is a framework of great power that often comes with great frustration. When using Swift, we can make Core Data more powerful, and sometimes more frustrating. This talk presents practical strategies for moving away from an Objective-C model, the bugs you'll run into and how to work around them, how Swift can bring clarity to your model objects, and how to harness Swift features in your NSManagedObject subclasses.

Video:
https://realm.io/news/jesse-squires-core-data-swift/

GitHub project:
https://github.com/jessesquires/JSQCoreDataKit

Event:
https://www.meetup.com/swift-language/events/220612410/

Jesse Squires

April 15, 2015
Tweet

More Decks by Jesse Squires

Other Decks in Programming

Transcript

  1. USING CORE DATA IN SWIFT
    JESSE SQUIRES
    JESSESQUIRES.COM • @JESSE_SQUIRES • GITHUB/JESSESQUIRES

    View Slide

  2. WHAT IS CORE DATA?

    View Slide

  3. The Core Data framework provides generalized and automated solutions
    to common tasks associated with object life-cycle and object graph
    management, including persistence.
    — Core Data Programming Guide

    View Slide

  4. Core Data is backed by a SQLite database.
    However, it is not a relational database or RDBMS.

    View Slide

  5. BACKED BY SQLITE.
    NOT A DATABASE?

    View Slide

  6. CORE DATA MANAGES:
    LIFE-CYCLE
    GRAPH
    PERSISTENCE
    SEARCHING
    OF OBJECTS

    View Slide

  7. CORE DATA STACK:
    Managed objects
    NSMangedObject
    Managed object context
    NSManagedObjectContext
    Persistent Store Coordinator
    NSPersistentStoreCoordinator
    Persistent Store
    NSPersistentStore
    SQLite

    View Slide

  8. WHY USE CORE DATA?
    ▸ Provides featues you need
    ▸ "Mature, unit tested, optimized"
    ▸ Part of iOS and OS X toolchain
    ▸ Apple continues to invest heavily in it
    ▸ Popular, tons of resources online

    View Slide

  9. WHY USE SWIFT?
    ▸ Clarity
    ▸ Type-safety
    ▸ Swift-only features
    ▸ Functional paradigms

    View Slide

  10. SWIFT + CORE DATA

    View Slide

  11. WARNING: TOOLS ARE IMMATURE
    SourceKitService
    Terminated
    Editor functionality
    temporarily limited.

    View Slide

  12. STANDING UP THE
    CORE DATA STACK
    Same boilerplate code as Objective-C
    Better in Swift

    View Slide

  13. CORE DATA STACK
    struct CoreDataModel {
    let name: String
    let bundle: NSBundle
    init(name: String, bundle: NSBundle)
    // other properties & methods
    }

    View Slide

  14. CORE DATA STACK
    class CoreDataStack {
    let model: CoreDataModel
    let managedObjectContext: NSManagedObjectContext
    let persistentStoreCoordinator: NSPersistentStoreCoordinator
    init(model: CoreDataModel,
    storeType: String,
    concurrencyType: NSManagedObjectContextConcurrencyType)
    // other properties and methods
    }

    View Slide

  15. CORE DATA STACK
    let model = CoreDataModel(name: "MyModel", bundle: myBundle)
    let stack = CoreDataStack(model: model,
    storeType: NSSQLiteStoreType,
    concurrencyType: .MainQueueConcurrencyType)
    // Use context
    stack.managedObjectContext

    View Slide

  16. CORE DATA STACK
    AppDelegate.m
    DO NOT

    View Slide

  17. USE FRAMEWORKS
    Clear model namespace, Modular, Reusable, Unit Testing

    View Slide

  18. CREATING MANAGED OBJECTS
    ▸ Xcode generated classes are terrible
    ▸ mogenerator Swift support still experimental
    (Last release Sept 2014)

    View Slide

  19. CREATING MANAGED OBJECTS
    VISUAL MODEL EDITOR

    View Slide

  20. CREATING MANAGED OBJECTS
    ATTRIBUTE VALIDATION

    View Slide

  21. OBJECTIVE-C
    @interface Employee : NSManagedObject
    @property (nonatomic, retain) NSString * address;
    @property (nonatomic, retain) NSDate * dateOfBirth;
    @property (nonatomic, retain) NSString * email;
    @property (nonatomic, retain) NSString * name;
    @property (nonatomic, retain) NSDecimalNumber * salary;
    @property (nonatomic, retain) NSNumber * status;
    @end

    View Slide

  22. SWIFT
    class Employee: NSManagedObject {
    @NSManaged var address: String?
    @NSManaged var dateOfBirth: NSDate
    @NSManaged var email: String?
    @NSManaged var name: String
    @NSManaged var salary: NSDecimalNumber
    @NSManaged var status: Int32
    }

    View Slide

  23. OPTIONALS
    Xcode will generate String instead of String?
    @property (nonatomic, retain) NSString * address;
    @NSManaged var address: String?

    View Slide

  24. CREATING MANAGED OBJECTS
    PREFIXED SUBCLASSES
    NSManagedObject
    .
    Swift namespaces

    View Slide

  25. Xcode does not prefix classes automatically
    Must add prefix manually after generating classes
    No prefix means runtime crash, obscure errors

    View Slide

  26. INSTANTIATING
    MANAGED OBJECTS
    Reduce boilerplate, generalize
    NSEntityDescription

    View Slide

  27. THE OBJECTIVE-C WAY
    // "Person"
    NSString *name = [Person entityName];
    @implementation NSManagedObject (Helpers)
    + (NSString *)entityName
    {
    return NSStringFromClass([self class]);
    }
    @end

    View Slide

  28. THE OBJECTIVE-C WAY
    // Create new person
    Person *person = [Person insertNewObjectInContext:context];
    @implementation NSManagedObject (Helpers)
    + (instancetype)insertNewObjectInContext:(NSManagedObjectContext *)context
    {
    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName]
    inManagedObjectContext:context];
    }
    @end

    View Slide

  29. THE SWIFT WAY?
    // "MyApp.Person"
    let fullName = NSStringFromClass(object_getClass(self))
    extension NSManagedObject {
    class func entityName() -> String {
    let fullClassName = NSStringFromClass(object_getClass(self))
    let nameComponents = split(fullClassName) { $0 == "." }
    return last(nameComponents)!
    }
    }
    // "Person"
    let entityName = Person.entityName()

    View Slide

  30. THE SWIFT WAY?
    // Create new person
    let person = Person(context: context)
    extension NSManagedObject {
    convenience init(context: NSManagedObjectContext) {
    let name = self.dynamicType.entityName()
    let entity = NSEntityDescription.entityForName(name,
    inManagedObjectContext: context)!
    self.init(entity: entity,
    insertIntoManagedObjectContext: context)
    }
    }

    View Slide

  31. THE SWIFT WAY?
    class Employee: NSManagedObject {
    init(context: NSManagedObjectContext) {
    let entity = NSEntityDescription.entityForName("Employee",
    inManagedObjectContext: context)!
    super.init(entity: entity,
    insertIntoManagedObjectContext: context)
    }
    }

    View Slide

  32. NOT VERY SWIFT
    "OBJECTIVE-C WITH A NEW SYNTAX"

    View Slide

  33. THE OBJECTIVE-C WAY
    IS NOT ALWAYS
    THE SWIFT WAY

    View Slide

  34. EMBRACE
    THE SWIFTNESS

    View Slide

  35. SWIFT DESIGNATED
    INITIALIZERS

    View Slide

  36. 1. Stored properties must be assigned initial value
    2. Designated init fully initializes all properties
    3. Convenience init are secondary
    4. Convenience init must call designated init
    5. Superclass initializers not inherited in subclasses by default

    View Slide

  37. DESIGNATED INITIALIZERS?
    // designated init
    init(entity:insertIntoManagedObjectContext:)
    // our convenience init
    convenience init(context:)

    View Slide

  38. CORE DATA BYPASSES
    INITIALIZATION RULES
    @NSManaged

    View Slide

  39. class Employee: NSManagedObject {
    init(context: NSManagedObjectContext,
    name: String,
    dateOfBirth: NSDate,
    salary: NSDecimalNumber,
    employeeId: String = NSUUID().UUIDString,
    email: String? = nil,
    address: String? = nil) {
    // init
    }
    }

    View Slide

  40. typealias

    View Slide

  41. TYPEALIAS
    typealias EmployeeId = String
    class Employee: NSManagedObject {
    @NSManaged var employeeId: EmployeeId
    }
    // Example
    let id: EmployeeId = "12345"

    View Slide

  42. RELATIONSHIPS
    NSSet
    Set

    View Slide

  43. Set
    Nope

    View Slide

  44. enum

    View Slide

  45. ENUM
    enum Genre: String {
    case BlackMetal = "Black Metal"
    case DeathMetal = "Death Metal"
    case DoomMetal = "Doom Metal"
    case FolkMetal = "Folk Metal"
    case Grindcore = "Grindcore"
    case Hardcore = "Hardcore"
    case CrustPunk = "Crust Punk"
    case StreetPunk = "Street Punk"
    case Thrash = "Thrash"
    }

    View Slide

  46. ENUM
    public class Band: NSManagedObject {
    @NSManaged private var genreValue: String
    public var genre: Genre {
    get {
    return Genre(rawValue: self.genreValue)!
    }
    set {
    self.genreValue = newValue.rawValue
    }
    }
    }

    View Slide

  47. ENUM
    // old
    band.genre = "Black Metal"
    // new
    band.genre = .BlackMetal

    View Slide

  48. ENUM
    Unfortunately, must use private property for fetch requests
    let fetch = NSFetchRequest(entityName: "Band")
    fetch.predicate = NSPredicate(format: "genreValue == %@", genre)

    View Slide

  49. FUNCTIONAL
    PARADIGMS
    WITH MICRO-LIBRARIES

    View Slide

  50. SAVING
    var error: NSError?
    let success: Bool = managedObjectContext.save(&error)
    // handle success or error

    View Slide

  51. SAVING
    func saveContext(context:) -> (success: Bool, error: NSError?)
    // Example
    let result = saveContext(context)
    if !result.success {
    println("Error: \(result.error)")
    }

    View Slide

  52. FETCH REQUESTS
    var error: NSError?
    var results = context.executeFetchRequest(request, error: &error)
    // [AnyObject]?
    if results == nil {
    println("Error = \(error)")
    }

    View Slide

  53. FETCH REQUESTS
    // T is a phantom type
    class FetchRequest : NSFetchRequest {
    init(entity: NSEntityDescription) {
    super.init()
    self.entity = entity
    }
    }

    View Slide

  54. FETCH REQUESTS
    typealias FetchResult = (success: Bool, objects: [T], error: NSError?)
    func fetch (request: FetchRequest,
    context: NSManagedObjectContext) -> FetchResult

    View Slide

  55. FETCH REQUESTS
    typealias FetchResult = (success: Bool, objects: [T], error: NSError?)
    func fetch (request: FetchRequest,
    context: NSManagedObjectContext) -> FetchResult {
    var error: NSError?
    if let results = context.executeFetchRequest(request, error: &error) {
    return (true, results as! [T], error)
    }
    return (false, [], error)
    }

    View Slide

  56. FETCH REQUESTS
    // Example
    let request = FetchRequest(entity: entityDescription)
    let results = fetch(request: request, inContext: context)
    if !results.success {
    println("Error = \(results.error)")
    }
    results.objects // [Band]

    View Slide

  57. SLIGHTLY LESS TERRIBLE?

    View Slide

  58. CLARITY
    OPTIONALS, ENUMS, TYPEALIAS, DESIGNATED INIT

    View Slide

  59. SAFETY
    TYPES, GENERICS, DESIGNATED INIT

    View Slide

  60. SWIFTNESS
    ENUMS, TYPEALIAS, OPTIONALS

    View Slide

  61. FUNCTIONAL
    SAVING, FETCHING, MORE

    View Slide

  62. github.com/jessesquires/JSQCoreDataKit

    View Slide

  63. Thank you!

    View Slide

  64. QUESTIONS?
    JESSESQUIRES.COM • @JESSE_SQUIRES • GITHUB/JESSESQUIRES

    View Slide