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/

Ba6b43b7b6198e2c20cbd348431ca6f4?s=128

Jesse Squires

April 15, 2015
Tweet

Transcript

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

    • GITHUB/JESSESQUIRES
  2. WHAT IS CORE DATA?

  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
  4. Core Data is backed by a SQLite database. However, it

    is not a relational database or RDBMS.
  5. BACKED BY SQLITE. NOT A DATABASE?

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

  7. CORE DATA STACK: Managed objects NSMangedObject Managed object context NSManagedObjectContext

    Persistent Store Coordinator NSPersistentStoreCoordinator Persistent Store NSPersistentStore SQLite
  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
  9. WHY USE SWIFT? ▸ Clarity ▸ Type-safety ▸ Swift-only features

    ▸ Functional paradigms
  10. SWIFT + CORE DATA

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

  12. STANDING UP THE CORE DATA STACK Same boilerplate code as

    Objective-C Better in Swift
  13. CORE DATA STACK struct CoreDataModel { let name: String let

    bundle: NSBundle init(name: String, bundle: NSBundle) // other properties & methods }
  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 }
  15. CORE DATA STACK let model = CoreDataModel(name: "MyModel", bundle: myBundle)

    let stack = CoreDataStack(model: model, storeType: NSSQLiteStoreType, concurrencyType: .MainQueueConcurrencyType) // Use context stack.managedObjectContext
  16. CORE DATA STACK AppDelegate.m DO NOT

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

  18. CREATING MANAGED OBJECTS ▸ Xcode generated classes are terrible ▸

    mogenerator Swift support still experimental (Last release Sept 2014)
  19. CREATING MANAGED OBJECTS VISUAL MODEL EDITOR

  20. CREATING MANAGED OBJECTS ATTRIBUTE VALIDATION

  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
  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 }
  23. OPTIONALS Xcode will generate String instead of String? @property (nonatomic,

    retain) NSString * address; @NSManaged var address: String?
  24. CREATING MANAGED OBJECTS PREFIXED SUBCLASSES NSManagedObject <ModuleName>.<ClassName> Swift namespaces

  25. Xcode does not prefix classes automatically Must add prefix manually

    after generating classes No prefix means runtime crash, obscure errors
  26. INSTANTIATING MANAGED OBJECTS Reduce boilerplate, generalize NSEntityDescription

  27. THE OBJECTIVE-C WAY // "Person" NSString *name = [Person entityName];

    @implementation NSManagedObject (Helpers) + (NSString *)entityName { return NSStringFromClass([self class]); } @end
  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
  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()
  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) } }
  31. THE SWIFT WAY? class Employee: NSManagedObject { init(context: NSManagedObjectContext) {

    let entity = NSEntityDescription.entityForName("Employee", inManagedObjectContext: context)! super.init(entity: entity, insertIntoManagedObjectContext: context) } }
  32. NOT VERY SWIFT "OBJECTIVE-C WITH A NEW SYNTAX"

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

  34. EMBRACE THE SWIFTNESS

  35. SWIFT DESIGNATED INITIALIZERS

  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
  37. DESIGNATED INITIALIZERS? // designated init init(entity:insertIntoManagedObjectContext:) // our convenience init

    convenience init(context:)
  38. CORE DATA BYPASSES INITIALIZATION RULES @NSManaged

  39. class Employee: NSManagedObject { init(context: NSManagedObjectContext, name: String, dateOfBirth: NSDate,

    salary: NSDecimalNumber, employeeId: String = NSUUID().UUIDString, email: String? = nil, address: String? = nil) { // init } }
  40. typealias

  41. TYPEALIAS typealias EmployeeId = String class Employee: NSManagedObject { @NSManaged

    var employeeId: EmployeeId } // Example let id: EmployeeId = "12345"
  42. RELATIONSHIPS NSSet Set<T>

  43. Set<T> Nope

  44. enum

  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" }
  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 } } }
  47. ENUM // old band.genre = "Black Metal" // new band.genre

    = .BlackMetal
  48. ENUM Unfortunately, must use private property for fetch requests let

    fetch = NSFetchRequest(entityName: "Band") fetch.predicate = NSPredicate(format: "genreValue == %@", genre)
  49. FUNCTIONAL PARADIGMS WITH MICRO-LIBRARIES

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

    handle success or error
  51. SAVING func saveContext(context:) -> (success: Bool, error: NSError?) // Example

    let result = saveContext(context) if !result.success { println("Error: \(result.error)") }
  52. FETCH REQUESTS var error: NSError? var results = context.executeFetchRequest(request, error:

    &error) // [AnyObject]? if results == nil { println("Error = \(error)") }
  53. FETCH REQUESTS // T is a phantom type class FetchRequest

    <T: NSManagedObject>: NSFetchRequest { init(entity: NSEntityDescription) { super.init() self.entity = entity } }
  54. FETCH REQUESTS typealias FetchResult = (success: Bool, objects: [T], error:

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

    NSError?) func fetch <T>(request: FetchRequest<T>, context: NSManagedObjectContext) -> FetchResult { var error: NSError? if let results = context.executeFetchRequest(request, error: &error) { return (true, results as! [T], error) } return (false, [], error) }
  56. FETCH REQUESTS // Example let request = FetchRequest<Band>(entity: entityDescription) let

    results = fetch(request: request, inContext: context) if !results.success { println("Error = \(results.error)") } results.objects // [Band]
  57. SLIGHTLY LESS TERRIBLE?

  58. CLARITY OPTIONALS, ENUMS, TYPEALIAS, DESIGNATED INIT

  59. SAFETY TYPES, GENERICS, DESIGNATED INIT

  60. SWIFTNESS ENUMS, TYPEALIAS, OPTIONALS

  61. FUNCTIONAL SAVING, FETCHING, MORE

  62. github.com/jessesquires/JSQCoreDataKit

  63. Thank you!

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