Friday, March 29, 2013

Open Source Code: BTIConcepts

I've published some code on GitHub that I'm calling BTIConcepts. There is nothing earth-shattering here; I'm certainly not breaking any new ground in computer science. This is just a variety of small things that are individually useful, and become even more so when combined. If you have ever thought "Boy, I sure wish all of my [instances of class] would do [something]", then you might want to peek through the code. Even if you don't use any of these specific classes, surely there is a weak spot somewhere in your repertoire where you can apply the concepts.


But first let's talk about coding style.


I have written previously about how I use logging. I've even written a little service to make adding these logs easier. This is caveman logging at its finest, and I am not even a little bit ashamed of it. The number of problems I have solved over the years simply by doing this more than outweighs any loss of "l33t" status I may have as a professional developer. I've added to this recently with BTITrackingLog. I'm not the first to make an NSLog macro, and probably won't be the last. But until I did this, I was only using NSLogs. So it was either ALL messages, or no messages. Now I can conditionally enable the tracking logs, but still see "important" NSLogs. I will add a configuration and scheme called Debug + Tracking that allows me to see everything. I've thoughtfully commented them out from the repository in anticipation of people viewing them as annoying, but what can I say, I like them. I sadly have not yet updated my service to use BTITrackingLogs.


For categories, method names should be decorated somehow to avoid clashes with primary methods. Most people use a prefix, so btiDoSomething, for example. I saw a recommendation a while back (sorry person, I'd credit you if I remembered who you are) to use postfixes instead. doSomethingBTI. They key argument offered was that with a prefix, you have to know that you mean to use a category method when you start typing in order to get code completion. So it's not merely "This is an array method", it is also "This is an array method that I or someone else have manually created". With a postfix, just start typing like you normally would, and code completion will take care of the rest. For methods that accept multiple parameters, I place the decoration at the end of the first group of text, like so: doSomethingBTI:andSomethingElse:andThisToo: Another benefit (or curse, depending on point of view) of using a postfix is that if the category method is similar to a primary method, then you can easily change your mind while typing by hitting either method in the pop-up. Can't do that with a prefix.


The ways that I have organized my view controllers have become less and less important as Objective-C gains new features. I saw a recommendation a long time ago to put dealloc at the top of the file, so that it is right there next to the properties. This way when you add a property, you're less likely to forget to travel all the way to the bottom to clear out the property in dealloc. And since viewDidUnload was often very similar to dealloc, I wanted it placed right after dealloc for the same reasons. Well, with ARC you don't need dealloc (in most cases) anymore. And you don't need to synthesize properties anymore. And viewDidUnload won't be called anymore. So, yeah. But there are still a few of my organization quirks that remain. I like notification responders grouped, and UI response methods (IBActions, etc) grouped, and so on. Delegate methods always go to the bottom, though if multiple delegate protocols are in use, the specific order doesn't matter. The #pragma mark usage in the .m files here are quite typical of how I do things. I don't normally do it in the .h file quite so much, but wanted things to look nice for public consumption. The specific order of anything in these files doesn't really matter, but for sake of consistency, try to follow some kind of pattern.


As I said at the beginning, I often want all of [instances of class] to do something. Naturally this points to categories, and there are categories here, but categories don't cover everything. For example, if I always want view controllers to do something in viewWillAppear:, a category isn't going to accomplish that easily, if at all. For far too long, my solution to this has been to use templates. Every time I wanted a table view (I don't like UITableViewController), I'd fire up my template that contains a table view property, and be on my merry way. I even sorta made this work with XIBs in Xcode 4. I always got what I wanted, my code was consistent, and I was doing less work. Yay! The thing about templates is that you are effectively copy-pasting code. If you change your mind about how something should work, then all of the other hundreds of classes that were created by your template have to be changed. Or, most likely, don't get changed. Another issue with templates is that your non-standard templates don't keep up when Apple changes their templates. This is not to suggest that all of Apple's templates are winners, but if they start adding something or removing something, one should at least inquire if that is a noteworthy change. I would still be trucking along adding now-useless dealloc and viewDidUnload methods if I was continuing to use my templates. I've decided that it is useful to start with Apple's templates, quickly observe any changes, and then copy-paste in my style stuff (pragma marks, etc).


If templates are out, and categories don't cover everything, that pretty much leaves subclassing. Instead of always subclassing UIViewController and copy-pasting in what I want over and over again, make a new common view controller. I've made BTIViewController. And then all of my view controllers can inherit from it. Don't need viewDidUnload anymore? One class, delete one method, done. Need to update didReceiveMemoryWarning since viewDidUnload isn't called anymore? One class, modify one method, done (Ignoring customization in subclasses, of course). All subclasses then inherit that change. And although this is less relevant with iOS 6, want EVERY view controller in your project to have the same autorotation behavior? One class, one method, done. (Really wish I'd thought of this years ago...). And then differences are handled like any other subclass. Override any behaviors you want to change.


So the "meat" of BTIConcepts is really found in 3 classes: BTIObject, BTIViewController, and BTITableViewCell. They don't have to do much at all- BTIObject in particular does not - but by simply using them, downstream activities become much easier if you need to apply changes across the board. Instead of NSObject, inherit from BTIObject. Instead of UIViewController, inherit from BTIViewController. If you don't like what my classes do, then modify them or make your own. But the key concept here is to insert something as high in the class hierarchy as you can, so that you can easily apply mass changes if the need should arise. I would probably even go so far as to argue that you should do this even if your inserted class is totally blank. Prevents having to go change all of your inheritances later.


BTIObject - At the moment all it does is provide a custom description method. It automatically logs all properties. Before I came up with this, I had lengthy and quite manual description methods. This is easier.


BTITableViewCell - I hate worrying about the cell identifier, particularly with XIB-based cells. Now, (hat tip to Jeff LaMarche) I no longer have to.


BTIViewController - Now we're getting into more functionality. First of all, I hate when other classes call initWithNibName: (or initWithStyle: for UITVC). That's bad OOP, IMO. The 'parent' VC has to know too much about this VC when doing this. Is there a nib? What name does it have? And if this VC is used in multiple places across an app, these decisions have to be made every single time. Sloppy, unnecessary work. So right off the bat I override init to pass through to initWithNibName:. Parents simply alloc/init, done.

I always want notification listeners to unsubscribe in dealloc. Done. The number of reasons to have dealloc in a view controller decreases even further.


The bulk of the remaining additions deal with BTINotificationHandlers, which I'll describe in a moment. This view controller will automatically register and unregister notifications at appropriate times. No more forgetting to unregister, or unregistering incorrectly.


Pre-iOS 6, if I wanted each view controller to have the same autorotation behavior, I'd define that behavior one time in this class. No more forgetting that VC's default to portait if you don't override the proper method. I didn't include it here since the code is aimed at iOS 6, but it would be easy enough to add.


BTITableViewController - This can be viewed as an alternative to UITableViewController, but I'm not really trying to recreate that class since I don't like it. I define basic table view stuff like a property and conform to protocols. Additionally, there are some features that get you started for handling things like selection and searching. Not very much code is implemented, it's more of a "here are some tools that should be useful" kind of thing.


At the time of writing, I've really only focused on table views, since that's most of what I do. But this concept could easily be expanded. If you do a lot of web views, you could make a BTIWebViewController that doesn't have to do much more than add a web view property and become the delegate. But you could also implement the delegate methods so that the activity spinner triggers at appropriate times, and then never worry about it again. You could make an image view class, perhaps embedded in a scroll view, so that each time you want to show an image you get zooming and panning. The point of all of this is to identify routine activities, and then put them in one place rather than doing them over and over again. Like I've done here:


BTIArrayTableViewController.h BTIManagerTableViewController.h BTICoreDataTableViewController.h - These are common flavors of table view controllers that I make. The first is for really basic stuff, nothing fancy. A list of strings, perhaps. The second utilizes another of the models that I'll mention here soon, BTITableContentsManager. The goal here is to define your table information in one place - contents and behaviors - rather than having a ton of if/else statements in each of your delegate methods. And lastly, one that is powered by a NSFetchedResultsController that gets defined in subclasses. Each of these has sufficiently different needs that I felt it made more sense to split them up rather than make a uber-table view controller.


That covers the view controllers. On to categories...


BTIErrorCategories - Described here.
NSArray - Isn't it annoying that we get a lastObject method that doesn't crash on an empty array, but we don't have a firstObject method?
NSMutableArray - Isn't it annoying that we get a removeLastObject method but we don't have a removeFirstObject method? And isn't it annoying that moving an item to a different index requires removing and reinserting?
NSNotificationCenter - Post notifications on the main thread.
NSManagedObject - Described here.
UITableView - Came from a book. Books are good.
UIDevice - Wrappers around the userInterfaceIdiom for nicer-looking code.


Little stuff. They don't have to be complicated. Sure, making complicated stuff simple is nice, but making repetitive stuff less repetitive is nice too.


Last but not least are a few models.


BTINotificationHelper - I've written about them here. The short version is that these let you define notification handlers in one spot, as opposed to registering in one place, and then unregistering with similar parameters somewhere else. Define all of the usual stuff, along with an extra parameter describing when this notification should be active. Then register/unregister as needed. This class doesn't do much by itself, but BTIViewController has been rigged to automatically handle a couple of common scenarios.

The next few are for table views.


BTITableSectionInfo - I came up with this a long time ago, mostly for table headers. The app I was working on had a crazy if/else scheme to determine when to show headers. Finally decided it made more sense to come up with a model to encapsulate what was necessary, defining things at the time I was building the data list. When I later learned of NSFetchedResultsSectionInfo, I rearranged this class a bit to mimic it. So you can use this class to build up your table data, a section at a time. Define headers/footers, add contents, and off you go. Throw a bunch of these in an array, and multi-section table view life gets easier.


BTITableRowInfo - This one isn't quite as useful for model-driven tables (ex: list of people, list of addresses, etc). But for table views with a lot of individual behaviors, such as a settings screen that might drill deeper in any number of directions, this can be quite handy. Before coming up with this, I would build arrays of strings to define the overall structure. Then cellForRow and didSelectRow would each have a massive if/else chain that determined what to show in each row, and then where to go in the event of a row tap. I'm not proud; I did it this way for a long time. Once I got my head wrapped around blocks, I realized there was a better way. Using this class, you can pack up this information into a single object, then extract what you need in the various delegate methods. It may not be practical in 100% of cases, but it sure covers 90+% of what I've needed. Any remaining if/else cases are truly for exceptions, rather than for everything.


BTITableContentsManager - This class took me longer to create than it should have. Ever since learning Core Data, I've really enjoyed the objectAtIndexPath: method that NSFRC provides. I've wanted to be able to do that in non-Core Data situations, too. After creating the previous two classes, it finally seemed like the time was right to pursue it. I wish I had done it sooner. So although it isn't capable-enough at the moment, you can think of this as a non-Core Data version of NSFetchedResultsController. Right now, you have to build up the table structure manually. I'd like to get to a point where I just dump a pool of objects in, and then hand it a sort descriptor and maybe a predicate. I'm not there yet. In the meantime, enjoy simplified table delegate methods while using this class.


So there you have it: BTIConcepts. I've tried to put nice overviews into each of the .h files. I also try to make code that is fairly self-explanatory. Hopefully this code helps you out, or at least inspires you to simplify your own coding life somehow. Enjoy.

No comments:

Post a Comment