Finding My Next Thing

My last day working at Solomon Solution was Friday. It's weird to think about, that it's been almost a week. My family and I went over to eastern Washington on a 4 day trip to a remote lake house over the weekend so I have been offline for the last while, and that's why I haven't written about my current job search.

What it boils down to is that I want to do iOS development full-time, and my previous job wasn't going to afford that any time soon. They needed my 100% commitment there, as well, and seeing as how I was doing marketing campaigns and HTML templates they just weren't going to get that from me. So I've moved on.

Thankfully I have lots of good leads on possible work. I'm having a second interview with a place tomorrow and there are some possible contract positions that could work if I don't find a full-time gig. I'm also not turning any opportunity away, so if you know of something please get in touch!

I've also got some really exciting things happening with Taphouse Software that I'm hoping to announce next week. I'm getting into the productized consulting game, that's all I will say for now. But stay tuned.

All in all, I'm not down about where I'm at right now. The prospects are exciting, and the chance to build my own company is very intriguing. I'm hopeful that I'll have a firmer grasp on where I'm going to land in the next couple of weeks. Although if you're so inclined, there are a couple of ways to chip in too. šŸ˜Š

Scorebook Sync Log 09 ā€“ CloudKit Xcoders Talk

Last night I gave my first Xcoders talk (and first tech talk in general), going over how Iā€™ve brought CloudKit into Scorebook. The response was overwhelmingly positive from the group and Iā€™m really happy about that. The talk had most all of the content that Iā€™ve written so far in this series, but also went pretty in depth on CloudKit in general and has my upload and download processes as well as lots of code samples.

The slides can be found here.

Some notes:

  • Iā€™m bummed that I havenā€™t shipped the Scorebook update yet, but Iā€™m planning to in the next month or at the latest by the end of July.
  • I didnā€™t talk about the user switching things from my last post. Mostly because I havenā€™t finished the implementation and I didnā€™t want to talk about it in that state. Iā€™ll keep writing about it here as I make progress though.

There was video recorded as well, and once that is posted to the Xcoders Vimeo account Iā€™ll be sure to update this post with a link to it.

Update: Here is the video on Vimeo

Lastly, I love talking about this stuff. If you have questions hit me up on Twitter or via email.

Scorebook Sync Log 08 ā€“ User Switching

Iā€™m feeling really confident in the things that Iā€™ve put into sync in Scorebook. But now Iā€™m starting to handle edge cases. Things like what happens when a user uses Scorebook without a current iCloud account, or how do I handle it when they log out and/or login again as a different user?

So I came up with a handy flowchart that I threw at Twitter and the Core Intuition Slack channel. Check it out:

Sync is hard.

I got some feedback on Slack from a guy who is doing something similar to what I am, but heā€™s accounting for the need to reset either the local store from the cloud or vice versa. The assumption is that at some point one or the other will become wrong and need to go back to square one. I may be overconfident here, but Iā€™m leaning against doing that. My desired UI for syncing is a single switch on the settings screen. I think thatā€™s how Iā€™m going to launch, and Iā€™ll have to see what the feedback is from users.

Iā€™ve started writing the code to handle all of this and so far itā€™s not as bad as I thought it would be. I need to do a lot of testing still, though (and Iā€™ve yet to start in on merging).

Do you see anything in the chart that might need to be changed? Iā€™d love to hear about it!

Scorebook Sync Log 07 ā€“ Responding to Deletions

(Thanks to David Hoang for the encouragement to post this)

I thought I was close to being done, and then I remembered that I havenā€™t implemented responding to deletions from CloudKit locally. Hereā€™s how the process works:

  1. Delete the object from Core Data & save the context
  2. Respond to the context did save notification and send CloudKit CKRecordID objects representing the deleted record to CloudKit
  3. CloudKit will send a notification to all subscribed devices letting them know of the deletion, with the corresponding CKRecordID object.
  4. Figure out what entity that object belongs to, and delete the instance from the database
  5. Save the updated database.

Pretty easy, right? Except, consider this: there isnā€™t a way to know the recordType of the CKRecordID coming down. The recordType is the database equivalent of a table, and itā€™s a property on CKRecord, not CKRecordID. When Iā€™m creating CKRecords from my managed objects, I set the recordType property to a string representing my entity. That keeps things clean (i.e. my SBGame instances would all have record types of SBGame).

I havenā€™t talked about uniquing objects in CloudKit yet, so letā€™s go back to the beginning there. When you create a CKRecord you can specify itā€™s unique ID (made up of a recordName property which is a string, and a zone if itā€™s going to a custom zone). Those 2 factors combine with the CKRecordā€™s recordType property to create a primary key. Good database practice so far. You can also choose not to specify a recordID at the CKRecordā€™s creation, and it will be created when saved to the server.

For ease, I have decided to create a unique ID as a UUID string that is set on -awakeFromInsert on all of my entities. That way I always know what their unique ID will be, and I donā€™t have to worry about saving them back to the database when they are saved to the cloud.

The first step to determining what record type the CKRecordID Iā€™m responsible for deleting, I made a change to each entityā€™s -recordName property, so that it includes the entity name as the first part of the record name. Then itā€™s followed by a delimiter (Iā€™m using ā€œ|~|ā€) and then the UUID. Hereā€™s how that now looks:

- (void)awakeFromInsert
{
    [super awakeFromInsert];
    self.ckRecordName = [NSString stringWithFormat:@"SBGame|~|%@", [[NSUUID UUID] UUIDString]];
}

A sample recordName for an SBGame instance would be ā€œSBGame||386c1919-5f25-4be2-975f-5b34506c51dbā€, with || being the delimiter between the entity name and the UUID. A bit hacky, but it works.

On the downloader I explode the string into an array using he same delimiter and grab the first object, which will be ā€œSBGameā€. Once I have the entity and the recordName to search for, I can put this in a fetch request to grab the object that needs deletion. Hereā€™s what that looks like:

NSString *recordName = recordID.recordName;
NSString *recordType = [[recordName componentsSeparatedByString:@"|~|"] firstObject];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ckRecordName = %@", recordName];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:recordType];
fetchRequest.predicate = predicate;

NSArray *foundObjects = [backgroundContext executeFetchRequest:fetchRequest error:nil];
id foundObject = [foundObjects firstObject];
if (foundObject != nil) {
    [backgroundContext deleteObject:foundObject];
}

In my limited initial testing this is working well so far, and itā€™s a simple solution to the problem.

Scorebook Sync Log 06 ā€“ Fun with Protocols

In my usage of CloudKit I had to determine early on how I was going to upload batches of records and deal with batches coming down. Iā€™m dealing entirely with CKRecord instances, so all of my entities will need to know how to handle those; both in how to create one from themselves, and how to turn turn one into itself.

I didnā€™t have any clever idea at first, so I punted. For saving records to CloudKit, Iā€™m monitoring the NSManagedObjectContextDidSaveNotification (which isnā€™t verbose enough) and I get NSSets of managed objects. Hereā€™s what my initial implementation looked like:

- (CKRecord *)ckRecordForManagedObject:(NSManagedObject *)managedObject
{
    CKRecordZoneID *userZone = [[SBSyncController sharedSyncController] userRecordZone];
    CKRecord *recordToReturn;

    if ([managedObject isKindOfClass:[SBPerson class]]) {
        recordToReturn = [(SBPerson *)managedObject cloudKitRecordInRecordZone:userZone];
    } else if ([managedObject isKindOfClass:[SBGame class]]) {
        recordToReturn = [(SBGame *)managedObject cloudKitRecordInRecordZone:userZone];
    } else if ([managedObject isKindOfClass:[SBMatch class]]) {
        recordToReturn = [(SBMatch *)managedObject cloudKitRecordInRecordZone:userZone];
    } else if ([managedObject isKindOfClass:[SBPlayer class]]) {
        recordToReturn = [(SBPlayer *)managedObject cloudKitRecordInRecordZone:userZone];
    } else if ([managedObject isKindOfClass:[SBScore class]]) {
        recordToReturn = [(SBScore *)managedObject cloudKitRecordInRecordZone:userZone];
    } else if ([managedObject isKindOfClass:[SBMatchImage class]]) {
        recordToReturn = [(SBMatchImage *)managedObject cloudKitRecordInRecordZone:userZone];
    } else if ([managedObject isKindOfClass:[SBMatchLocation class]]) {
        recordToReturn = [(SBMatchLocation *)managedObject cloudKitRecordInRecordZone:userZone];
    }

    return recordToReturn;
}

Yuck.

And similarly, I captured the -recordType property of an incoming CKRecord, then ran -isEqualToString: against it to determine which entity it represented. And then something interesting happened.

I got to the place where I was working through how to handle conflicts and needed a -modificationTimestamp property to get that done, and realized that I could use a protocol to declare uniform conformance across all of my entities. I could make sure that the class conforms to the protocol, and then set the property without having to do the ugliness of something like the above snippet.

And thus, SBCloudKitCompatible was born. This protocol defines 4 things in the interface:

@protocol SBCloudKitCompatible <NSObject>
@property (nonatomic, strong) NSString *ckRecordName;
@property (nonatomic, strong) NSDate *modificationDate;

- (CKRecord *)cloudKitRecordInRecordZone:(CKRecordZoneID *)zone;
+ (NSManagedObject *)managedObjectFromRecord:(CKRecord *)record context:(NSManagedObjectContext *)context;
@end

By conforming to this protocol, Iā€™ve been able to cut out a bunch of code. Hereā€™s what the snippet above now looks like:

- (CKRecord *)ckRecordForManagedObject:(NSManagedObject *)managedObject
{
    CKRecord *recordToReturn = nil;

    if ([managedObject conformsToProtocol:@protocol(SBCloudKitCompatible)]) {
        id<SBCloudKitCompatible> object = (id<SBCloudKitCompatible>)managedObject;
        recordToReturn = [object cloudKitRecordInRecordZone:self.zoneID];
    }

    return recordToReturn;
}

I think we can agree that it looks way, way better.

CocoaConf PDX 2015

I was blessed to go to Portland over the weekend for CocoaConf with my family in tow. We packed up the Hyundai Elantra and made the trek down last Thursday, completing our journey to the Embassy Suites PDX by mid-afternoon. The following 2 days were chock full of great talks, great conversation, and lots of motivation to go make great apps.

I went to great talks from Daniel Steinberg, Danny Greg, Laura Savino, and more. A few high points:

  • I had my mind expanded by Danny Greg's talk on MVVM. I want to start building my apps using this in mind in the future. I was unprepared for how awesome that architecture looks to wire things together on iOS. (This talk was actually voted the favorite of all the conference-goers)
  • I loved Daniel Steinbergā€™s talks. I come away from them wanting to play, learn, and teach. I canā€™t wait to expose my son to lots of things. Not just programming (donā€™t get me wrong, I will show him) but I want to also show him Christianity, math, English, science, sports. Itā€™s going to be amazing to watch him grow and become his own person.
  • The Apple Watch talks were really good. David and Curt have spent lots and lots of time with the SDK and mockups thinking about how apps should work on that platform and they have been great to share with the broader community. I havenā€™t built anything for the watch but I now feel equipped to go try it out.

The conference was all-inclusive, even with the meals. Instead of going offsite to eat somewhere they provided lunch and dinner for us on Friday and lunch on Saturday. We got to sit together and mingle or work on something that has inspired us (I got started outlining my June Xcoders talk). The community amongst Cocoa developers is something that Iā€™m truly growing to treasure more and more each day. I got to hang out with some folks that Iā€™ve met up in Seattle but didnā€™t know that well, and I got to meet new people too.

I canā€™t say enough good things about CocoaConf. This is the second one Iā€™ve been able to attend (after they visited Seattle last October) and both have been great experiences. The next time theyā€™re up in the Northwest Iā€™ll definitely be on the attendee list.

3 Days with Apple Watch

I placed my order for a Space Gray 42mm Apple Watch Sport at 12:03 PST, and was one of the fortunate ones to have it delivered on launch day last Friday. It came in the later afternoon and Iā€™ve spent the last few days getting to know it. Iā€™ve got a few thoughts that I hope will be helpful to those waiting for theirs to arrive or wondering what the experience is like.

Before I dive in, I want to couch my thoughts as very much initial impressions. This is a device that needs to be lived with. Itā€™s definitely Appleā€™s most personal device yet (as theyā€™ve been calling it) and I have only a few days with it. Itā€™s also a 1.0. There are some things that work really well and some things that donā€™t; this is to be expected, and will only get better over time.

  • The build quality of the watch is outstanding. Itā€™s slightly smaller even than the analog watch that I have been wearing for a few months now, and definitely lighter. The fluoroelastomer band really is comfortable. Itā€™s soft and almost disappears when you are wearing it. I like the look of it as well. When I did my try on at the Apple Store, my wife commented that she liked the one I got the best. Vindication!

  • I was shocked when I looked this up the other day, but at 30 grams, the 42mm Sport watches are the same weight as a Jawbone Up. The Jawbone has no screen, yet Apple Watch has a wonderful retina display that enables worlds of other functions besides just fitness tracking.

  • The setup process is really great. Thereā€™s an information cloud that serves as a QR code for your phone to capture and then as the pairing continues, it narrows down into these cool shapes and finally an Apple logo with a circle around it. The circle fills up as your settings get moved over to the watch. Overall it took about 10 minutes or so to be up and running.

  • The first thing I tried was a speaker phone call to my wife (with our son also in the room so he could hear his first speakerphone calls). That was the first glimpse into the future for me. Itā€™s so cool to finally have a Dick Tracy (or Michael Knight) watch finally on my wrist. I can see it being a big help in the car, but out in public might be a bit weird.

  • The fitness tracking works quite well, but thereā€™s room for improvement. I really like having the goals on a daily basis. When you achieve them you get ā€œawardsā€ which are oddly fulfilling. Iā€™ve set modest daily goals and have been able to hit my move and stand goals without problems the past couple of days. They can be adjusted, as well. The thing that I will probably have to look to an app for is weight training and tracking. The exercise app only supports cardio things (bike, treadmill, elliptical, etc). Thatā€™s not a huge deal for me.

  • The pedometer actually leaves something to be desired for me. It functions like a normal wrist-worn pedometer would, meaning that you need to swing your arms for steps to be counted (which wouldnā€™t work for Raquel Welch). I like to walk with my hands in my hoodie pockets, so the watch wonā€™t tally those steps either. I have to be cognizant of how Iā€™m walking in order to know my steps are being counted. What bums me out about that is my iPhone is most likely in my pocket, happily counting my steps. I wish the two would talk to each other. This feels like a low-hanging fruit feature for a future update.

  • Iā€™ve installed a few apps, and am trying to keep that number as low as possible. So far my favorites are Overcast (which provides a great remote control feature to control playback) and MLB At-Bat. The At-Bat app offers a glance, so I can see the Mariners score easily from the watch face. Iā€™m excited to see what developers will do with the Watch now that itā€™s available and we can use them.

  • The front Glance is always going to be the settings one. This is the equivalent of Control Center on iPhone, and it works really well. You can enable airplane mode, do not disturb, mute, and even ping your iPhone if youā€™ve misplaced it. The killer thing is you can mirror the settings from the watch to the phone. So if your phone enters do not disturb, so will your watch (and vice versa). Itā€™s really nice.

Here are 2 tips that I hope you will find helpful:

  1. If youā€™re a heavy app user, then changing the ā€œActivate on Wrist Raiseā€ setting of ā€œResume Toā€ so that the watch resumes where you were, rather than the watch face, could be very useful. I did this just after starting to use Overcast and itā€™s been great. I would like Apple to have a timeout on this, though. If I havenā€™t raised my wrist in 15 or 30 minutes, then go back to the watch face. That would be perfect I think.

  2. Go slow with notifications, glances, and apps. You can customize this all on your phone and itā€™s really easy to do. Donā€™t let yourself get overwhelmed with buzzing and tapping on your wrist. My default on the phone is to deny notifications to start so this might need to start on the phone for you, but on the watch you can allow the notifications to go through for any app you have them enabled on the phone. I like that touch a lot. Being picky about what buzzes your wrist will make for a more pleasant experience.

So those are a few thoughts and a couple tips. If youā€™ve got any questions youā€™d like me to answer, hit me up on Twitter or by email and Iā€™ll try to post some follow-ups if there are any.

Portfolio

Hello

I love software development, especially for Apple's platforms. If I got to design my own position, it would be writing delightful software alongside a great team where we could elevate each other to do fantastic work together. If this sounds interesting to you, please get in touch!. You can also view my rƩsumƩ here.

App Store

Skills

  • iOS
  • OS X
  • Objective-C
  • Swift
  • Cocoa
  • Cocoa Touch
  • Xcode
  • .NET
  • ASP.NET MVC
  • C#
  • Visual Studio
  • JavaScript
    • jQuery
    • node.js
  • PHP
  • SQL

Scorebook Sync Log 05 - Plan Accordingly

When I decided to begin sync implementation with CloudKit, I just started going. I watched the WWDC videos (mostly) and read through the getting started, then started coding.

That wasn't the best tactic. Turns out.

There are a couple of questions that I should have asked myself. Do I want to have record changes pushed to me automatically? Do I want atomic operations so that all of the items have to succeed?

(As a slight aside, take note that neither of those things can be done on the public database, this is only for private data)

Well, I do want at least the record changes. So I need to implement a custom record zone. This is done with a CKRecordZone object, and is kind of like the equivalent of a database schema. Instead of using the default zone (which can't have push notifications or atomic actions), I create my own like this:

CKRecordZoneID *zoneID = [[CKRecordZoneID alloc] initWithZoneName:@"ScorebookData" ownerName:CKOwnerDefaultName];
CKRecordZone *zone = [[CKRecordZone alloc] initWithZoneID:zoneID];

The zone name can be "ASCII characters and must not exceed 255 characters". The zone name needs to be unique within the user's private database (which makes using a single string as the zone name possible). The owner name can just have the string constant CKOwnerDefaultName, which I'm pretty sure gets changed server-side to be some sort of user identifier. The docs aren't super clear about that part, but the method notes say:

To specify the current user, use the CKOwnerDefaultName constant.

Poking inside that constant at runtime expands it to __defaultOwner__. CloudKit seems to know what to do, though.

The next part I had to figure out was how do I get records into this zone? You don't save anything to the zone object, but instead use a different initializer on the CKRecordID objects. So I added a paramteter to my -cloudKitRecord methods in my model categories to take a zone. Now the methods are - (CKRecord *)cloudKitRecordInRecordZone:(CKRecordZoneID *)zone, and the implememtation uses the -initWithRecordName:zoneID initializer to create the record ID.

The thing I'm going to talk about next time is setting this whole stack up. I'm coming to realize that CloudKit, like Core Data, is a stack. I'm building a sync controller (which is what injects the CKRecordZoneID into the model objects' zone parameter) and that is working well so far. Stay tuned.

Scorebook Sync Log 04 - Relationships

I want to finish out my schema bit with a little bit on relationships. CloudKit fully supports relationships so you can associate your data with one record type to another. However itā€™s not exactly like you would expect in a relational database. CloudKit supports 1:1 and 1:many relationships, but not many:many. Thatā€™s not a problem for me but it may be for some.

In designing these relationships, the big thing to know going in is that you create the reference from the foreign-key side, not the primary side. So if you have a product that is becoming a line item, where the left side might be the product with many line items, you create the relationship on the right side with the line item. See the hand-crafted image below for clarity.

Hand-crafted visual aid

Creating a relationship is straightforward, and you use a CKReference object to do so. The designated initializer is -initWithRecordID:action: and you can send in the record ID object of the record to reference. If you donā€™t have the record ID, you can send in the whole record to the -initWithRecord:action: method. I donā€™t know which is better, and if you donā€™t have a record ID then thatā€™s the one to use. I was going to flip back and forth, but since I decided to make the record ID at creation time I stick with the initWithRecordID:action: method instead.

The action part of the initializer refers to delete rules. Do you want the child to be deleted when the parent goes away? If so you can pass in CKReferenceActionDeleteSelf, and if not then youā€™ll want to use CKReferenceActionNone. Itā€™s pretty straightforward.

From there in my CloudKit record creation method, the reference becomes just another key on the CKRecord object. So it gets a key, with the reference as a value, and thatā€™s it. It looks something like this:

CKRecord *record = [[CKRecord alloc] initWithRecordType:@ā€œPlayerā€];
CKRecordID *personID = [[CKRecordID alloc] initWithRecordName:self.person.ckRecordName;
CKReference *personReference = [[CKReference alloc] initWithRecordID:personID action:CKReferenceActionDeleteSelf];
record[@ā€œpersonā€] = personReference;

Save the record to a database and thatā€™s all there is to it.