Why is FileManager so unforgiving?

This is more of a reminder for me as to how to do simple things.

Apple keeps modifying the FileManager API, but doesn’t actually make it obvious as to how to do simple things.  I have Data that I want to save somewhere.  You think it would be as easy as getting a path, then writing it.  Nope.

So here’s a recipe to show how to simply write some data somewhere, overwriting as you do it:

func testSerialization() {
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: self.dates, requiringSecureCoding: false)
            let url = writeLocation()  // currently just the caches directory / SomeFolder / SomeFilename.dta
            let fm = FileManager.default
            if fm.fileExists(atPath: url.path) {
                try fm.removeItem(at: url)
            }
            let folder = url.deletingLastPathComponent()
            try fm.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
            let success = fm.createFile(atPath: url.path, contents: data, attributes: nil)

            XCTAssertTrue(success, "Should have written the file!")
            XCTAssertTrue(fm.fileExists(atPath: url.path), "Should have written something here")

        } catch let error {
            XCTFail("Failed with error: \(error.localizedDescription)")
        }
    }

 

Advertisements

GDPR Compliance and Analytics

This post is more of a conceptual brainstorm about GDPR and still being able to acquire useful analytics data.

If I were to sum up the GDPR, I basically take it to mean that you cannot store/track data about your user without their consent, AND they have the right to be forgotten… i.e. they should be in control of their own data.  This basically means don’t track usage events using a user identifier (on iOS the venerable identifierForVendor property), as this would constitute gathering data about a person that can be inferred via this identifier.

If you can convince your user to opt-in, then great.  Business as usual just with some provisions to be able to delete any data they no longer want you to have/use.  But is there a way to still gather information about App usage without someone’s consent while still obfuscating who actually did the things you’re trying to track?  My understanding is that you can gather all the data you want so long as it’s not possible to trace that back to the user itself.

My thoughts are that if you as an analytics person are able to relax your requirements somewhat, I think it could be possible.  If you as an analytics person don’t need real-time updates on how your users have been using your app, but still want all the same insights, I think it’s still achievable…. if you have patience.

I think the solution is to keep all usage tracking on the device itself, then say once a month you upload it all in one go to some custom API endpoint that would parse all that data into usage stats, all without some user identifier.  The semantics of that are “There is a user – we don’t know who – who used the app in the following ways last month.”  Then you can see funnels.  Then you can see retention.  Then you can see all of those things over a time frame that is useful.

Currently, if we uploaded each and every event as they happened, you’d have no way to connect all those events to a user.  If you upload all of those in one go, with timestamps, you can still process all this data and get a picture of what a user does in a specific amount of time without caring about who it was, specifically.

Whoa. Bush league, Apple, Bush League. (SiriKit woes)

What a highly annoying issue I discovered today!

I was doing some exploratory work with the SiriKit / Intents Frameworks and was very frustrated to find out the following:

If you want to migrate to using a common framework, make sure you have ANY swift type defined in it.  If you only have your .intentdefinition file in that framework, it won’t compile properly, leaving you scratching your head (in my case for HOURS).  Look at target dependencies, looked at linked binaries.  Wanted to look at the generated stuff, but where is it located??

It turns out that as soon as I defined a simple struct that was added to this target, suddenly the generated intents code succeeded.  Until then, you’ll have no idea why.

But wait!  That might have not been it either.  It could also be because of the .intentdefinition file.  I modified a response template and it started working.

So basically under the hood, Xcode is doing weird, buggy stuff.  So it’s maddening when you’re trying to learn something only to find out that you did nothing wrong and the tools are ruining the experience.

Because it’s AUTOMAGIC!

 

Level Up: Test-Driven Development

Until very recently, I’m used to being that hired gun who parachutes in, kills all the work tickets, asks the important questions about the product, makes an app work as desired on a tight deadline to a reasonable level of quality given the constraints, then is out again, all for a reasonably good sum of money. It’s exhausting because these companies often have no established procedures.  So in addition to being Lone Wolf: Kicker of Asses, I’m training junior developers so they can assist me without slowing me down too much, I’m creating an Adhoc QA Department (what is severity, what is reproducibility, how to write bug reports, what is a end-user test suite so you know how to regression test, and why you should use a ticketing system instead of just popping by my desk to effectively pull the plug on whatever 50 things I had in my head), I’m having to interpret incomplete designs, pull the assets out of the Design tools (Zeplin, Sketch, sometimes Photoshop) because many designers don’t know what exactly you need anyway, poke holes and/or fill in the gaps with the UX, and of course manage upwards. Oh yeah, and also do “Agile Waterfall” development, which is borne out of companies who only really do Waterfall but want to be “hip” to the new trends and demand we do Agile (with no scrum master or really anyone who knows how to lead that effectively). So then your time is further taken up with meetings and pushing work tickets around that actually don’t really encapsulate the work you actually need to do, but managers need you to do that so they can generate reports with some data that is meant to impress other people who really have no idea what’s going on anyway, and actually just increased levels of trust in your hires and “we’re good” would be equally effective/ineffective. (Ah, perhaps they don’t know how to make the right hires or can’t get them if they did.) In all of that, I have to “get ‘er done” because the deadline doesn’t change and surprise! All of your dependencies (Design, Backend API) also have the same deadline.

Yikes. A day in the life.  That above would be the worst day in the life.  It’s rarely all of those things.

So I’m grateful for my current freelance contract. It’s the first contract really in years where I felt I’m working in a proper software development company. The management overhead seems low, but the organization is not impacted. They have a process here that works. (I think it’s just because I hit the jackpot and they just placed a priority on the members of the team; personable yet very competent. Ultimately it’s a team who cares about what they do, and making sure there’s a nice team vibe. It also helps that they have corporate backing and therefore have a generous timeframe and budget it would seem.)

“Take the time you need to do a good job.” This is very much the culture here. For one of the first times in my career, I’ve been really exposed  to an office environment where you’re given time to think, and time to write a lot of tests while you develop. You can ask for specifications and those exist and are fixed. There are 2 other iOS devs here to bounce ideas off of, and of course to do code reviews with. It is so extremely satisfying when you get to refactor your original approach into ever more concise code that is more robust and less error prone. Time where you can write the API docs and the Unit Tests to basically freeze the specification. Normally there just isn’t enough time, given all the other tasks that Lone Wolf has, AND the product design always seems to be a moving target.

In short, it feels like I finally have the time and space to level up. Unit Tests are especially awesome when you work in teams and I’m glad for the opportunity to work in this environment for a while so I can establish some good habits and really reach a new plateau in my journey as a software developer.

No, I didn’t die

Looking at this blog, it’s been over a year since I wrote anything. I’ll just list my excuses:

  • I was overworked on some agency projects and there simply wasn’t enough time to write posts
  • It’s generally a grey area to write about problems solved for clients who make me sign an NDA
  • I could have made the solution to the problem look generic, thereby obfuscating my client’s identity and thus my attachment to the project, but didn’t.
  • And now THE main reason….

I spent 8 months of 2018 taking a sabbatical. I like to call it a sabbatical because this is how you don’t lose respect from people when compared to saying “I’m unemployed and am enjoying watching my beard grow”. But really it was more like a “I’m fed up with insane projects that are under-staffed, with poor management and deadlines that don’t change, so ultimately the bottom-feeding developer who is doing the job of a small team on his own is starting to wonder if it’s time to pack it in and open a bakery, or do some other kind of artisanal work that involves repetitive, meditative movements and a certain degree of craftsmanship. <blows sawdust off his handmade hope chest>”

Sabbatical has a nicer ring to it and doesn’t require a backstory.  “Ah, he must be a well-to-do kind of man.”

This sabbatical was not about going to some coding retreat so to level up my skills as a developer. It was more like a sanity check. The reason I like coding and software engineering is that I find it to be a highly creative medium. For me, being creative is what gives life some meaning and makes it fun. The process of having an idea, developing it in your mind, putting it down on paper, refining and iterating on that, solving the unknowns, then finally doing the implementation / building. Then it’s done, you pat yourself on the back, revel in your accomplishments, then say “next!!” I love it. So the sanity check was a) to get a little distance from a profession that was starting to become not fun for me anymore, and b) do something completely different but also creative.

So I did that. In 2018, I decided I wanted to build a camper out of some kind of vehicle, then drive around North America until I experienced some sort of epiphany. I gave myself a year. I used 8 months. I built a camper. I had an adventure. I drove 24,000km in 4.5 months and saw so many beautiful places and met tons of very lovely people. I had some profound realizations about my life that were restorative and empowering for me going forward.

In short it was the most useful year I’ve had in over a decade. If you have the opportunity to take a long period of time off and leave your normal surroundings behind, I highly recommend it.  You’ll return knowing what changes you need to make in life.

True, I have fallen a bit behind with the latest and greatest in iOS / Swift dev, but since I’ve been back working (since November 2018), I’ve been fortunate to work with great developers and I’ve actually leveled up in this short time. I can say I’ve become way more of a test-driven developer, and I’ve also got a few new tricks up my sleeve that you generally get to learn when you work with other developers that work on your platform; not something I’m all that used to as I tend to be either the only iOS dev on a project, or they quickly say “oh but you know the most”.

At any rate, I’m back and I’m still happy to be a developer, and now that my approaches have changed somewhat, I have new areas to grow. So I’m not stagnating like I felt I was, and I’ve been fortunate to get a few freelance contracts that have not turned about to be a complete and total clusterf*ck, which has often been the case in Berlin, Germany. I oftentimes have the feeling that when it comes to Software Development practices, Berlin is the wild west; there are no rules, OR Berlin is teenage love; just fumble around in the dark for a while and hope something good happens.

Manual Core Data Migrations – Lessons Learned

So, I’ll try to keep this all brief.  In short, as everyone, including Core Data Ninja Master Marcus Zarra, has said:  Avoid heavy Core Data Migrations!

The tools are buggy, the crash bugs difficult to interpret.  I spent 4-5 hours already just sort of shooting at a black box.   So, I hope to share with you the things I learned and what caught me up.  If this is all too brief or out of context in places, it’s because I’m not a tutorial writer, and keep my blog posts quite often so that I can refer back to them.  So please feel free to write me if you need clarification.

I’ll also give the disclaimer that I’m not the most ‘canonical’ of programmers.  So, these are my findings.  I don’t know if they follow best practices, but I don’t think there are any best practices when it comes to this stuff…. otherwise I probably wouldn’t have made this lengthy blog post!

Scenario

Migration of old data store using an old Core Data model to a new data store with a brand new Core Data model

This documents my personal experience trying to migrate a Data Model I want to retire (i.e. completely separate Core Data Model file) to a brand new data model.

I wanted to move away from my old Objective-C pipeline which involves NSManagedObject subclasses using mogenerator that all have an Objective-C style Namespace and codegen, to a pure Swift version which uses the codegen feature of the Latest Xcode Core Data editor.

I kept these models separate because in a few releases when I know everyone has upgraded, I can eventually just remove the legacy model from the bundle and all the associated classes.

What I learned / experienced:

  • How to set up incremental migrations using a handy little helper from http://www.objc.io
  • How to migrate old data to new using Mapping Models
  • How to write and use custom NSEntityMappingPolicy objects
  • How to use the Mapping Model editor to call functions on these custom policies
  • All the various Pitfalls, Gotchas, and things that Apple really needs to improve on.

Useful Pre-requisite Reading:

How to set up incremental migrations

This is really what you want to do.  It makes your life easy in that you only need to make mapping models for each change to your data model.  There is a lot of code floating around the internet that helps you make an incremental migration manager.  I took one from objc.io and then modified it so it could support my use case where I am migrating a completely different store to a new one, and that new one should have its own URL on disk that may not follow from the original store.

Code for that migration manager can be found in a gist here.  Don’t just copy-paste:  You have to copy-paste into the correct files!  Also, it’s in objective-c so you’ll want to make a bridging header… It’s really not hard.

What is different in my version of MHWMigrationManager than from on objc.io:

  • It has a hack fix for a dubious migration error that has minimal debug help: ‘Mismatch between mapping and source/destination models’
  • It allows you to specify via the delegate a “Final Destination URL” once the Migration has successfully completed.  This can be useful as in my case where I have 2 different Stores and Models and want them to stay that way.

Set up your Core Data Stack

Basically, you start with Apple’s boilerplate code for Core Data and a persistent container, then you modify in a way that I’ve done here.  These 2 gists I’ve provided are all just starting points.  We haven’t even got to the annoying stuff yet.

If you create a brand new data model like I have done, be sure that it uses a different name than your old one.  You can’t rename the old one because you want the new one to have the same nice name.  Leave everything about the old one in place.

Pro Tip:  If you didn’t do this but already did the work of creating this new model version, if you create a new model either via duplication or copy-paste of entities, keep in mind that delete rules are not copied over, NOR are the inverse relationships.  So you’ll have to go through and set these by hand again.

Now Create Mapping Models

You can now proceed to create a mapping model in the ways you’ve probably read about on the internet already.   Pro Tips:

  • Only write your mapping model, once your Core Data Object Model is complete.  If you make a modification to your Core Object Model (.xcdatamodeld), any mappings that reference it will no longer be found by the Migration Manager.
  • Due to the nature of the migration process, you won’t have access to any subclass of NSManagedObject, and so this can make setting up NEW relationships difficult.  It is therefore a good idea to keep ‘foreign keys’ on your models with relationships.  (i.e. you extend your Post model to have an Attachment.  Attachment should have an attribute postId, or something identifiable)
  • If you use a subclass of NSEntityMigrationPolicy and you specify it in the Mapping Model editor, make sure that you also provide the Module name.  So, instead of LegacyToModernPolicy, I would put SongbookSimple.LegacyToModernPolicy, because my app’s module name is SongbookSimple.

Mapping Models are useful in that they allow a lot of customization, and you can sort of elect what you want to be done “automagically” and what you’d like to have more control over.  You get more control over these by using custom subclasses of NSEntityMigrationPolicy.

In general, for property or relationship mappings, the auto-generated functionality can get you pretty far.  Otherwise, when you have specified a custom Policy, you can make method calls on that policy.

When to use custom policy methods

If you have a somewhat strange migration for a specific entity, you can just remove all Attribute mappings in for an Entity Mapping, then in your subclass, override the method below.  The method signature is misleading because you are actually asked to create the equivalent version of the old object in the new object.  So you’re manually specifying how one object “becomes” the new object, and in my case where a Song started to store its data in a separate SongData object, you have to create this SongData object and find a way to link the two (i.e. songFilename) because you create the actual relationships later.  EDIT:  What I’ve learned however is that ideally this createDestinationInstances(…) method should really be about 1:1 mapping.  If your migration ends up taking one object and splitting it into two, you should write to Policy Subclasses and have 2 Entity Mappings:  i.e OldSong -> Song and OldSong -> SongData, then in the 2nd pass of the migration where the relationships are re-established, that’s where you can associate them again.

    override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
        
        let dInstance = NSEntityDescription.insertNewObject(forEntityName: mapping.destinationEntityName!, into: manager.destinationContext)
        
        let text = sInstance.value(forKey: "text") as! String?
        
        dInstance.setValue(sInstance.value(forKey: "lastModified"), forKey: #keyPath(Song.createdAt))
        dInstance.setValue(sInstance.value(forKey: "lastModified"), forKey: #keyPath(Song.updatedAt))
        dInstance.setValue("txt",                                   forKey: #keyPath(Song.fileExtension))
        dInstance.setValue(sInstance.value(forKey: "filename"), forKey: #keyPath(Song.filename))
        dInstance.setValue(sInstance.value(forKey: "firstLetterUppercase"), forKey: #keyPath(Song.firstLetterUppercase))
        dInstance.setValue(sInstance.value(forKey: "name"), forKey: #keyPath(Song.title))
        dInstance.setValue(sInstance.value(forKey: "oldFilename"), forKey: #keyPath(Song.oldFilename))
        dInstance.setValue(self.songDataLengthOfText(text), forKey: #keyPath(Song.songDataLength))
        dInstance.setValue(text, forKey: #keyPath(Song.text))
        dInstance.setValue(SongDataType.plainText.rawValue, forKey: #keyPath(Song.typeRaw))
        dInstance.setValue(nil, forKey: #keyPath(Song.userInfoData))
        
        // NOTE: DON'T DO THIS.  Set up a OldSong -> SongData entity mapping!
        // Then fetch this instance later via its songFilename
        //let songData = NSEntityDescription.insertNewObject(forEntityName: SongData.entity().name!, into: manager.destinationContext)
        //songData.setValue(sInstance.value(forKey: "filename"), forKey: #keyPath(SongData.songFilename))
        //let structure = SongDataStructure()
        //structure.set(text: text)
        //songData.setValue(structure.serialize(), forKey: #keyPath(SongData.nsdata))
        
        // NOTE: DON'T DO THIS.  Set up a OldSong -> SongViewPreferences entity mapping!
        //let viewPreferences = NSEntityDescription.insertNewObject(forEntityName: SongViewPreferences.entity().name!, into: manager.destinationContext)
        //viewPreferences.setValue(sInstance.value(forKey: "filename"), forKey: #keyPath(SongViewPreferences.songFilename))
        
        manager.associate(sourceInstance: sInstance, withDestinationInstance: dInstance, for: mapping)
        return
    }

I will say this:  this method above is used to create instances.  You don’t associate them yet!  That’s why you can see I set songFilename on these 2 new data types (in my new model), so that I can create their relationship later…  this is for the second pass in the migration, where relationship mappings are created (more on that).

Quick Reference for Mapping Editor FUNCTION arguments:

NSMigrationManagerKey: $manager
NSMigrationSourceObjectKey: $source
NSMigrationDestinationObjectKey: $destination
NSMigrationEntityMappingKey: $entityMapping
NSMigrationPropertyMappingKey: $propertyMapping
NSMigrationEntityPolicyKey: $entityPolicy

If I don’t need such a detailed mapping of an object, I can also use custom methods to set a property in the editor for any attribute.  Pro Tip: Make sure you change the the “Source Fetch” on a Relationship mapping to “Use Custom Value Expression” if you do this.

FUNCTION($entityPolicy, "filenameForLibraryWith:" , $manager)

This is syntax in the editor.  It’s saying “to get the value for this destination attribute, call a method on the (custom) policy called

func filename(forLibraryWith manager: NSMigrationManager) -> String

Remember you can use the arguments listed about in the Quick Reference section for knowing what kind of arguments you can pass into these methods!

You can also use these custom methods when performing relationship mappings.  Now, I personally haven’t had to override the method:

func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws

I’ve got by with the Relationship mappings in the Mapping Model editor and custom methods.

Do not reference subclasses of NSManagedObject during migration!

It would seem that this is how Core Data ensures that the migration can work reliably.  It sucks because you need to remember:

  • awakeToInsert is not called, because no instances of your subclass are used in migration, so whatever you would set in awakeToInsert, you should do here too.
  • The relationship accessors aren’t present either.  So be safe and specify the relationship and its inverse.

I created a handy helper method to find NSManagedObject instances:

class LegacyToModernPolicy: NSEntityMigrationPolicy {
    
    static func find(entityName: String,
                     in context: NSManagedObjectContext,
                     sortDescriptors: [NSSortDescriptor],
                     with predicate: NSPredicate? = nil,
                     limit: Int? = nil) throws -> [NSManagedObject] {
        
        let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: entityName)
        fetchRequest.predicate = predicate
        fetchRequest.sortDescriptors = sortDescriptors
        if let limit = limit {
            fetchRequest.fetchLimit = limit
        }
        
        do {
            let results = try context.fetch(fetchRequest)
            return results
        } catch {
            log.error("Error fetching: \(error.localizedDescription)")
            throw error
        }
    }
  }

Pitfalls, Gotchas, Other Tips, Things I’ve already said

Here’s a list of things I might have mentioned already, but in this modern age where attention spans are low, and we need headings to actually read something, here is a list of things that I discovered in this long journey over 2 days:

    1. If you created your new Data Model and it started with the same name as your legacy one (i.e. SongbookSimple), it’s better to just create a new Core Data Model file with a new name (e.g. SongbookSimpleDataModel), then rebuild your data model by hand. This in practice means copy-pasting all the entities from the one to the other. Note! The Delete rules are not copied over, NOR are the inverse relationships. So you’ll have to go in and for each Entity, connect these up again.
    2. If you get an error during Migration such as:
      Couldn’t create mapping policy for class named (LegacyToModernArtistPolicy)
      It’s probably because you have Swift module-related errors. See Sandeep’s comment in this Question:
      So LegacyToModernArtistPolicy becomes SongbookSimple.LegacyToModernArtistPolicy and it works
    3. If you change your current model to accommodate the migration process, AND you’ve already run the app, you’ve altered the data model in a bad way. You don’t know if this is a typical state to be in, so better play it safe. You’ve got to remove the app from the simulator, re-install an old version so to populate an old data store, then run your new version in development.
    4. If you are doing custom fetch requests so to satisfy creating relationships (in your NSEntityMigrationPolicy), it would seem that fetch requests are returning NSManagedObject objects and not the subclasses. Nor can they be casted. For example, you might have seen a crash error:Could not cast value of type 'NSManagedObject_Library_' (0x6100000504d0) to 'SongbookSimple.Library' (0x101679180).This is why the helper above can be useful.  I fixed the bug I mention here with:
          func libraryForManager(_ manager: NSMigrationManager) -> NSManagedObject {
              
              do {
                  var library: NSManagedObject? = try LegacyToModernPolicy.find(entityName: Library.entity().name!,
                                                          in: manager.destinationContext,
                                                          sortDescriptors: [NSSortDescriptor(key: "filename", ascending: true)],
                                                          with: nil,
                                                          limit: 1).first
                  if library == nil {
                      let dInstance = NSEntityDescription.insertNewObject(forEntityName: Library.entity().name!, into: manager.destinationContext)
                      
                      // awakeFromInsert is not called, so I have to do the things I did there, here:
                      dInstance.setValue(Library.libraryFilename, forKey: #keyPath(Library.filename))
                      dInstance.setValue(NSDate(timeIntervalSince1970: 0), forKey: #keyPath(Library.updatedAt))
                      library = dInstance
                  }
                  
                  return library!
                  
              } catch {
                  fatalError("Not sure why this is failing!")
              }
          }
      
    5. You think you are far along because all of your NSEntityMigrationPolicy objects are having their custom methods called, nothing is failing, but at the end of the migration process you’re still getting errors. SpecificallyCocoa error 1570. This implies that data validations are failing.It could easily be that it’s because you made a false assumption that awakeFromInsert will get called on your new instances during the migration process. For example you added a non-optional attribute to your model who gets his default value set in awakeFromInsert. You should specifically make sure these are getting set either via the mapping editor and your custom policy (with a custom method), or try to handle the errors in performCustomValidationForEntityMapping:manager:error:I prefer graphic tools. So I just write some boring methods that will set default values and it’s all good.
    6. IF YOU CHANGE YOUR MODEL, THEN THE MAPPING FILE WILL BREAK AND NOT BE FOUND DURING MIGRATION.IF YOU CHANGE YOUR MODEL, THEN THE MAPPING FILE WILL BREAK AND NOT BE FOUND DURING MIGRATION.This means you have to rebuild the Mapping File.
    7. In all of the NSEntityMigrationPolicy subclass, make sure you are always dealing with NSManagedObject and not their subclasses!! If you don’t, your automatic mappings (that use custom methods on the policy subclass) will fail.
    8. You don’t HAVE to create mappings in the Mapping Model editor. Basically, the part that has attribute mappings is a way of NOT overriding the method
      func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws

      and the Relationship Mappings editor is a way of not having to subclass

      func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws

      ONE THING you DO need to understand is that during the migration process, you are dealing with NSManagedObject instances, and not instances of your (likely) subclasses. So you don’t get all the benefits of the accessor methods that would ensure object graph integrity. What does this mean? You should make sure that you connect both sides of a relationship via relationship mappings. This means, in your data model it makes sense to keep a field to a ‘primary key’, so that you can use one object to find another.

    9. MAKE SURE you keep an eye on ‘optionality’ of your properties when moving from source -> destination.  If in your source you had a property that was optional, but in the destination it is not, the entire migration can fail if it tries to migrate a nil value to a property that can no longer be nil.  In this case, you may even need to create a custom policy method to generate a non-nil value if the source value for that property is nil.  Remember, if it CAN happen, it WILL happen to someone.  This is hard to believe sometimes.
    10. YAY! The Migration manager says it migrated… except… I got an exception:
      'Mismatch between mapping and source/destination models'

      I have to say, Core Data documentation sucks. There’s just not enough of it, and no explanation of why it fails during migration. Totally crap. I’ve spent a whole day just trying to shoot into the dark, hoping I’ll figure out what’s going on with this giant black box.

      The fix for this occurs in my modified MHWMigrationManager gist, and it’s what sorted me out!

Still Missing from everything that I learned:

  • I don’t really know how one is supposed to work with the createRelationships(…) method.  I only do that aspect of migration via the Mapping Model Editor and custom methods on the Policy
  • I haven’t done anything with validation yet.  So I don’t know how that comes into play.

Wrapping Up

This was a super long post.  But hopefully it can be seen as a good reference guide or at least things to think about when trying to do heavyweight migrations.  Now that I understand how they’re done, they’re not AS scary anymore.  What was annoying was trying to figure this all out.

If you benefit from this, I would be happy if you let me know!

Core Data Migrations – Woes…

So thankfully I won’t go into a misogynistic blog post a la frustrated Mark Zuckerberg in the movie “The Social Network”, but sometimes you just need to blog to let your technical frustrations out!

I’m doing some complicated Core Data migrations where the lightweight ones won’t work.  I’m migrating from one Core Data model to a completely different one, and I’m trying to map those properties over.

I’ve already discovered a wealth of information either through google, trial and error, and a bit of both, which I will share once I’ve got the entire migration code running.

I just have to say here however… I think it’s probably best to avoid these Xcode tools like a mapping model.  Because they’re buggy as hell.  For example, if you define a mapping model and provide source and destination models, but then change either of those source or destination models, the Mapping Model will no longer work, and even if you re-select the source and destination, it doesn’t matter:  You’ve just killed your Mapping Model.  Is there any info about this?  No.  Just “could not find a suitable mapping model”.

Ugh.

I’ve been trying to debug something for 3 HOURS because all you get is an EXC_BAD_ACCESS crash, and ZERO information as to what it’s all about, and NSZombies do nothing.

So I’m sitting here, taking shots into the dark and hope I discover something.

I question whether I should just give up on TOOLS, which are supposed to make your life easier, and just write the whole heavyweight migration in code.  If I had started on that 3 hours ago, I’d probably be done.  My data model is like 8 entity types.

Is it just me or is the quality of Apple’s tools getting worse?  It’s like they produce stuff that’s 80% good.  But it’s a tool chain, so 0.8 * 0.8 = 0.64.  So you see where this is going…