[This blog post is part of a series, Defining Vertical Learning.]
Last year, I decided to learn how to program iOS apps. I had tried to learn this before, but had always given up at the first hurdle. Most books on programming iOS apps start off using table views. I guess, on iOS, creating a scrollable list in a table view is a bit like printing “Hello, World” on the screen. Given the screen size of an iPhone, it makes sense that most apps will be built around scrollable lists. But the gulf between the apps that I wanted to create and the apps that these programming books wanted me to create was so huge that I felt defeated right away.
Other than an introductory computer science course in college, I had no formal training in programming and was entirely self-taught. Learning to program iOS apps involved learning the Cocoa Touch framework, Objective-C, and Xcode. I had zero experience with C, and I had never worked with a set of APIs or an IDE that was nearly as rich and complex as Cocoa Touch and Xcode. The number of things that I’d have to learn was daunting, and I did not want to feel as though it would take months before I could even begin to program the kind of apps I wanted to create.
Fortunately, Apple introduced Sprite Kit in iOS 7. Sprite Kit is an API for creating 2D games. It makes it very simple to draw to the screen and animate things. Since most of my current projects run within a <canvas> element on a web page using JavaScript, this was right up my alley.
I have learned a number of programming languages in the past, so I basically know what works for me. I found a book of Sprite Kit tutorials for beginners and worked through a third of them. At this point, I was copying lines of code verbatim and then running them to see what would happen. For each tutorial, the book included a challenge at the end where you had to tweak the program to get a different outcome. I completed those as well, but I was a long way from understanding the code that I was writing.
After completing about a third of the tutorials in the book, I felt like I had drilled down far enough to where I understood some of the basic concepts and was familiar enough with Cocoa Touch and Xcode to attempt to write one of my own programs. Because I still did not fully understand the code that I was writing, when I wanted to do something that I didn’t know how to do, I would scour through the tutorials to find a piece of code that did the same thing or something similar. Once I had found the code that I wanted, I would copy it verbatim into my own program. This worked well as long as I stuck to fairly standard tasks.
When learning a new programming language, one of the benchmark standards I have for myself is being able to read through a program and understand what it is doing. This doesn’t mean that I understand every word or could write the code myself, but I know enough to follow the program logic fluently. I decided that it was time to create my first full-fledged app, something that could be released on the App Store. I decided to port a game that I had created a long time ago in BASIC, but to add native iOS features, including Game Center support, data syncing, and a full user interface built in Interface Builder. Most of my previous projects had been a single view in a single window running a Sprite Kit scene. I also wanted this app to be a universal app that would run on 3.5" iPhones, 4" iPhones, and iPads.
To create this app, I couldn’t rely on code snippets from tutorials any more. I started using Apple’s own API documentation as a reference, and searching for tips on Stack Overflow (a website where programmers ask and answer questions). Now that I could fluently read and get the gist of a chunk of code, I pushed myself to reach my next benchmark standard: fully understanding every line of code that I was writing. There had always been certain terms or concepts that I had used but didn’t understand; now I wanted to understand them.
After releasing my first native iOS app to the App Store, it was almost time for me to graduate from exercises to creating real apps for work. For my final exam, I decided to port a far more complex app and to build it using Core Data. To make it even more challenging, I decided to include functionality that was discouraged both by Apple and by the resident Core Data experts on Stack Overflow. In Core Data, when an iCloudstore is initialized on a device, a fallback store is created first and then the actual iCloudstore is created and synced with iCloud. If you search Stack Overflow, there are a few hacks that attempt to detect when the fallback store is created and then switched over with the actual iCloudstore, but the expert advice on Stack Overflow is: DON’T TRY IT. I wanted to try it.
I came up with an approach for sniffing out the iCloudstore initialization process that is more robust than anything I’ve seen, but it is also very complex. I made things even more complicated by allowing the user to navigate through and use the app while the iCloudstore is initializing, which means that the app has to able to react to the initialization process no matter where the user is or what the user is doing. To accomplish this, dozens of objects have to communicate and interact intricately. Timing and sequence is critical.
In Objective-C, there are a few ways for objects to communicate with one another. For example, Object A can send a message directly to Object B, but in order for that to work, Object A has to know how Object B works, and it has to hold a reference to Object B. If you change Object B, you may have to update Object A as well or your program may not work as expected. Alternatively, Object A can broadcast a notification, and any object that is listening for that notification, such as Object B, can react to it. This way, you can change Object A or Object B without having to update any other object. One of the design principles of object-oriented programming is to encapsulate your program into modular objects that can be updated independently. This makes it easier to maintain and extend your app in the future.
When I was first learning how to program iOS apps, I was copying code snippets, so I wasn’t giving any thought to how my objects were communicating. But at some point, it began to bug me that I wasn’t applying any criteria and being internally consistent, so I started to read up on direct messaging, notifications, and key-value observing (a third technique) until I felt like I could go back through my code and revisit all of my previous design decisions (or, more accurately, non-decisions). But once I started working with Core Data and the intricate iCloudstore initialization process, I realized that I didn’t understand object messaging as well as I thought. When Object A posted a notification, when did Object B react to it? And if Object B and Object C were both listening for the same notification, which reacted first?
I had now drilled below the basic API documentation provided by Apple, so I started doing my own testing, and I discovered that notifications behave like direct messages. When Object A posts a notification in the middle of a method, the method stops executing until Object B finishes reacting to it and returns control. I have read that direct messaging, notifications, and key-value observing all run on top of the same messaging infrastructure in the Objective-C runtime, so, in hindsight, it doesn’t surprise me. It wouldn’t surprise me either if all notifications get compiled as direct messages. When Apple provides different ways for objects to communicate in Objective-C, that’s to give programmers a few mental models to chose from, but the compiler strips all of that out. This made me realize that the object-orientedness of a program only exists at the surface for the programmer.
I never did figure out how the compiler decides which object runs first if two objects are both listening for the same notification. I know how events pass from object to object through the DOM in JavaScript, but I realized that, if the order in which objects react to a notification matters, than a notification is almost certainly the wrong mental model to be using. I also realized that I had a very naive understanding of object-oriented programming. I had resisted the idea of creating a single object to coordinate the iCloudstore initialization process because it seemed to run counter to object-oriented principles. But when taking an object-oriented approach, it is very important to think about the level at which we are encapsulating a program. If I have a bunch of objects that are communicating in an intricate way and I’m having trouble tracing through those communications to see what happens, then it might be logical to encapsulate those objects at a higher level.
When I set out to learn how to program iOS apps, I needed resources and an approach that felt relevant to me. I wanted to create the apps that I wanted as soon as possible. In order to get better at programming iOS apps, I pushed myself to drill down and build up. Actually, that is not quite accurate. When learning anything, I naturally drill down and build up. That’s how I learn. I have to stop myself from drilling down and building up if I am learning something to use once and I don’t need a deeper understanding of it. My drive to drill down and build up has developed over the years as I’ve gotten good at it and I’ve discovered how effective it can be. It is something that I value. I’m sure that my friend Daniel would have drilled down immediately to understand how object messaging works in the Objective-C runtime (something that I still haven’t done yet but would like to in the future) because he thinks that way. Others would have stopped drilling down or building up long before they reached the point that I did. Some of that may be based on intrinsic interest level, but I remain convinced, based on my own personal experience and my experience as an educator, that learning vertically generates a virtuous cycle that leads to more vertical learning, and eventually puts us on a path to becoming our truest self.
No comments:
Post a Comment