Structure and Interpretation of Computer Programmers

I make it easier and faster for you to write high-quality software.

Tuesday, October 13, 2020

Reflections on an iBook G4

I had an item in OmniFocus to “write on why I wish I was still using my 2006 iBook”, and then Tim Sneath’s tweet on unboxing a G4 iMac sealed the deal. I wish I was still using my 2006 iBook. I had been using NeXTSTEP for a while, and Mac OS X for a short amount of time, by this point, but on borrowed hardware, mostly spares from the University computing lab.

My “up-to-date” setup was my then-girlfriend’s PowerBook G3 “Wall Street” model, which upon being handed down to me usually ran OpenDarwin, Rhapsody, or Mac OS X 10.2 Jaguar, which was the last release to boot properly on it. When I went to WWDC for the first time in 2005 I set up X Post Facto, a tool that would let me (precariously) install and run 10.3 Panther on it, so that I could ask about Cocoa Bindings in the labs. I didn’t get to run the Tiger developer seed we were given.

When the dizzying salary of my entry-level sysadmin job in the Uni finally made a dent in my graduate-level debts, I scraped together enough money for the entry-level 12” iBook G4 (which did run Tiger, and Leopard). I think it lasted four years until I finally switched to Intel, with an equivalent white acrylic 13” MacBook model. Not because I needed an upgrade, but because Apple forced my hand by making Snow Leopard (OS X 10.6) Intel-only. By this time I was working as a Mac developer so had bought in to the platform lock-in, to some extent.

The treadmill turns: the white MacBook was replaced by a mid-decade MacBook Air (for 64-bit support), which developed a case of “fruit juice on the GPU” so finally got replaced by the 2018 15” MacBook Pro I use to this day. Along the way, a couple of iMacs (both Intel, both aluminium, the second being an opportunistic upgrade: another hand-me-down) came and went, though the second is still used by a friend.

Had it not been for the CPU changes and my need to keep up, could I still use that iBook in 2020? Yes, absolutely. Its replaceable battery could be improved, its browser could be the modern TenFourFox, the hard drive could be replaced with an SSD, and then I’d have a fast, quiet computer that can compile my code and browse the modern Web.

Would that be a great 2020 computer? Not really. As Steven Baker pointed out when we discussed this, computers have got better in incremental ways that eventually add up: hardware AES support for transparent disk encryption. Better memory controllers and more RAM. HiDPI displays. If I replaced the 2018 MBP with the 2006 iBook today, I’d notice those things get worse way before I noticed that the software lacked features I needed.

On the other hand, the hardware lacks a certain emotional playfulness: the backlight shining through the Apple logo. The sighing LED indicating that the laptop is asleep. The reassuring clack of the keys.

Are those the reasons this 2006 computer speaks to me through the decades? They’re charming, but they aren’t the whole reason. Most of it comes down to an impression that that computer was mine and I understood it, whereas the MBP is Apple’s and I get to use it.

A significant input into that is my own mental health. Around 2014 I got into a big burnout, and stopped paying attention to the updates. As a developer, that was a bad time because it was when Apple introduced, and started rapidly iterating on, the Swift programming language. As an Objective-C and Python expert (I’ve published books on both), with limited emotional capacity, I didn’t feel the need to become an expert on yet another language. To this day, I feel like a foreign tourist in Swift and SwiftUI, able to communicate intent but not to fully immerse in the culture and understand its nuances.

A significant part of that is the change in Apple’s stance from “this is how these things work” to “this is how you use these things”. I don’t begrudge them that at all (I did in the Dark Times), because they are selling useful things that people want to use. But there is decidedly a change in tone, from the “Come in it’s open” logo on the front page of the developer website of yore to the limited, late open source drops of today. From the knowledge oriented programming guides of the “blue and white” documentation archive to the task oriented articles of today.

Again, I don’t begrudge this. Developers have work to do, and so want to complete their tasks. Task-oriented support is entirely expected and desirable. I might formulate an argument that it hinders “solutions architects” who need to understand the system in depth to design a sympathetic system for their clients’ needs, but modern software teams don’t have solutions architects. They have their choice of UI framework and a race to an MVP.

Of course, Apple’s adoption of machine learning and cloud systems also means that in many cases, the thing isn’t available to learn. What used to be an open source software component is now an XPC service that calls into a black box that makes a network request. If I wanted to understand why the spell checker on modern macOS or iOS is so weird, Apple would wave their figurative hands and say “neural engine”.

And a massive contribution is the increase in scale of Apple’s products in the intervening time. Bear in mind that at the time of the 2006 iBook, I had one of Apple’s four Mac models, access to an XServe and Airport base station, and a friend who had an iPod, and felt like I knew the whole widget. Now, I have the MBP (one of six models), an iPhone (not the latest model), an iPad (not latest, not Pro), the TV doohickey, no watch, no speaker, no home doohickey, no auto-unlock car, and I’m barely treading water.

Understanding a G4-vintage Mac meant understanding PPC, Mach, BSD Unix, launchd, a couple of directory services, Objective-C, Cocoa, I/O Kit, Carbon, AppleScript, the GNU tool chain and Jam, sqlite3, WebKit, and a few ancillary things like the Keychain and HFS+. You could throw in Perl, Python, and the server stuff like XSAN and XGrid, because why not?

Understanding a modern Mac means understanding that, minus PPC, plus x86_64, the LLVM tool chain, sandbox/seatbelt, Scheme, Swift, SwiftUI, UIKit, “modern” AppKit (with its combination of layer-backed, layer-hosting, cell-based and view-based views), APFS, JavaScript and its hellscape of ancillary tools, geocoding, machine learning, the T2, BridgeOS…

I’m trying to trust a computer I can’t mentally lift.

posted by Graham at 08:54  

Thursday, April 15, 2010

The difference between NSTableView and UITableView

A number of times, I’ve chased myself down rat holes in iPhone projects because I’ve created a design or implementation that assumes UITableView and NSTableView are similar objects. They aren’t.

The main problem I come across is related to how the cells are treated in Cocoa and in Cocoa touch. An AppKit table comprises columns, each of which uses a cell to display its content. A cell contains the drawing and event-handling stuff of a view, but nothing to do with its place in the view hierarchy or responder chain. It’s essentially a light-weight view. For each row in the table, NSTableColumn takes its cell, configures it for the content in that row and then draws the cell at its location in the column. No matter how many rows there are, a single cell is used.

UIKit works differently. Of course a UITableView only has one column, but it also displays views rather than cells. This is good, but leads to the key distinction that always trips me up: you can’t use the same view more than once in a table view. Of course, sections in a UITableView will often have more than one row, but each row that is visible on-screen will needs its own instance of UITableViewCell (which is a subclass of UIView, and therefore a view in the traditional sense rather than a cell). If you try to re-use the same instance multiple times, the table view will configure each row but only the last one it prepared will be drawn.

So what’s this -reuseIdentifier? stuff? That’s related to caching views for scrolling. Imagine a table view with 10 rows, of which 4 can be seen on screen at once. Each uses the same type of cell in this example. When the table view first becomes visible there will be 4 UITableViewCell instances in use, displaying rows 0-3. Now you start to scroll the view. UITableView finds it needs an extra cell to display row 4, which is now partially on-screen and row 0 is starting to slide off. When row 0 disappears completely, the table view could just delete its cell – but rather than do that, it adds it to a queue of reusable cells. When row 5 starts to appear, the table view can re-use the object it’s already created for row 0, because it’s the same type of cell as the one for row 5 and is currently unused.

So, that’s that really. Note to self: don’t treat UIKit like it’s just AppKit, you’ll end up wasting a day of code.

posted by Graham Lee at 13:32  

Friday, April 2, 2010

On writing a book

Well, I’ve performed my final author’s review, and Professional Cocoa Application Security is all with the printers. This post is about my experiences writing the book, not the book material itself.

My original motivation for writing PCAS was that it was a topic someone needed to talk about, and nobody had hitherto done the talking. I was actually initially approached by Wiley to see if I’d write anything at all – their commissioning editor had seen my Objective-C FAQ and this blog, and liked my style. I said that I didn’t want to write a book, I wanted to write this book. It was the best way I could pay back the community that has helped me so much in the years I’ve been a Mac developer.

It took about 6 months to write the draft – my original estimate was much shorter but once we realised I couldn’t meet that we revised it, after which I stayed on track with the updated schedule. That’s six months of nearly full time work. Some other books probably don’t take so long, but in this case I had set myself a very ambitious scope and needed to research quite a lot of the topics before I could write on them. If you’ve already got a series of blog posts, training material or something that you just want to turn into a book, I can imagine the drafting process being much quicker.

I’ve found that book authorship is not the best vehicle for self-study. You get a biased view of the material, looking for things that would be interesting to readers rather than things you will need to use yourself. Because the goal of the book is to provide utility to the readers, you end up with a gotcha-oriented approach to research, looking for the subtle benefits or issues that are not obvious on a casual inspection. That said, it was still a good motivation to learn about the technologies I wrote about, so I’m glad I did it. Parts of the writing process were a lot of fun: I got to find out about some cool frameworks and APIs, absorb loads of information and re-emit it in a form that is, I hope, engaging and interesting. I’ve looked at my bookshelves and have about 6ft of books that I used as source material – and that doesn’t include websites, ebooks and journal papers. On the other hand, I’m not going to deny that I had occasional days that just felt like a long slog to get the day’s section written. I didn’t mind solving hard problems, but there are some subjects that just seem impossible to say anything interesting about. It’s when writing those sections that you find yourself staring at a half-written sentence for an hour, wondering just what it was you were thinking when you wrote the ToC.

You’re not going to get rich off the advance :). I was in a good position where I could live off practically no income while writing, meaning that devoting a few months to producing the drafts was not a problem. What I have become rich in is exposure and recognition, even before the book was published. Because both the proposal and the book content must be peer-reviewed by a technical reviewer, “I am writing a book” says “there are people out there who trust that I know my subject”. Of course, “I have written a book and you can read it” carries more weight, so I expect this exposure to increase after publication.

I’ve worked on reviewing proposals too, and the things you really need to make sure if you are trying to punt a proposal to publishers are:

  • You need to tell the publishers that the market for your book exists, who is in that market and how big it is. They’re not going to go and look for the buyers on your behalf (but they will get a reviewer to make sure you’re not talking bullshit).
  • Having identified your reader, the goals and content of the book must be appropriate to the reader. Don’t put an introduction to Xcode in your Advanced iPad Apps book, just to make up an example.

This theme carries on into the review process for the actual content. The technical reviewer (a role I’ve also taken before) is not just there to check that the code compiles. Responsibilities include verifying the accuracy of the content and appropriateness for the target reader, and indeed review comments I’ve made on book drafts have been split roughly evenly between “this isn’t quite right” and “your reader won’t understand this” (though I’m more verbose in the actual review).

So, in short, you will not make money writing a book. You will gain kudos and satisfaction. If you’ve got something that you think the world desperately needs to know, and you know that you can explain it in a way the world will want to pay attention to, then by all means write! If you want to make a few thousand dollars, or want an easy project between apps, then I’d suggest finding something else. Writing’s fun, and it’s worthwhile, but it’s certainly not an easy life.

posted by Graham Lee at 12:20  

Tuesday, March 30, 2010

Rehearsals in beta!

I have a new application, Rehearsals, an online practice diary for musicians. If that sounds like the kind of thing you’re interested in, and you have Mac OS X 10.6 or newer, then please download the beta release and test it out. There’s absolutely no charge, and if you submit feedback to support <at> rehearsalsapp <dot> com you’ll be eligible for a free licence for version 1.0 once that’s released. There are no limitations on the beta version, so please do download and start using it!

You can follow @rehearsals_app for updates to the beta programme (new releases are automatically downloaded using Sparkle, if you enable it in the app).

posted by Graham Lee at 12:22  

Tuesday, March 2, 2010

How to hire Graham Lee

There are few people who can say that when it comes to Cocoa application security, they wrote the book. In fact, I can think of only one: me. I’ve just put the final draft together for Professional Cocoa Application Security and it will hit the shops in June: click the link to purchase through my Amazon affiliate programme.

Now that the book’s more-or-less complete, I can turn my attention to other interesting projects: by which I mean yours! If your application could benefit from a developer with plenty of security experience and knowledge to share in a pragmatic fashion, or a software engineer who led development of a complex Cocoa application from its legacy PowerPlant origins through Snow Leopard readiness, or a programmer who has worked on performance enhancement in networking systems and low-level daemon code on Darwin and other UNIX platforms, then your project will benefit from an infusion of the Graham Lee magic. Even if you have some NeXTSTEP or OPENSTEP code that needs maintaining, I can help you out: I’ve been using Cocoa for about as long as Apple has.

Send an email to iamleeg <at> securemacprogramming <dot> com and let’s talk about your project. The good news is that for the moment I am available, you probably can afford me[], and I really want to help make your product better. Want to find out more about my expertise? Check out my section on the MDN show, and the MDN security column.

[] It came up at NSConference that a number of devs thought I carry a premium due to the conference appearances, podcasts and other material I produce. Because I believe that honesty is the best policy, I want to come out and say that I don’t charge any such premium. My rates are consistent with other contractors with my level of experience, and I even provide a discounted rate for NGOs and academic institutions.

posted by Graham Lee at 13:22  

Sunday, January 10, 2010

Unit testing Core Data-driven apps, fit the second

It took longer than I expected to follow up my previous article on unit testing and Core Data, but here it is.

Note that the pattern presented last time, Remove the Core Data Dependence, is by far my preferred option. If a part of your code doesn’t really depend on managed objects and suchlike, it shouldn’t need them to be present just because it works with (or in) classes that do. The following pattern is recommended only when you aren’t able to abstract away the Core Data-ness of the code under test.

Pattern 2: construct an in-memory Core Data stack. The unit test classes you develop ought to have these, seemingly contradictory properties:

  • no dependence on external state: the tests must run the same way every time they run. That means that the environment for each test must be controlled exactly; dependence on “live” application support files, document files or the user defaults are all no-nos.
  • close approximation to the application environment: you’re interested in how your app runs, not how nice a unit test suite you can create.

To satisfy both of these properties simultaneously, construct a Core Data stack in the test suite which behaves in the same way but which does not use the persistent store (i.e. document files) used by the real app. My preference is to use the in-memory store type, so that every time it is created it is guaranteed to have no reference to any prior state (unlike a file-backed store type, where you have to rely on unlinking the document files and hoping there are no timing issues in the test framework which might cause two tests simultaneously to use the same file).

My test case class interface looks like this (note that this is for a dependent test case bundle that gets embedded into the app; there’s an important reason for that which I’ll come to later). The managed object context will be needed in the test methods to insert new objects, I don’t (yet) need any of the other objects to be visible inside the tests but the same objects must be used in -setUp and -tearDown.

#import <SenTestingKit/SenTestingKit.h>

@interface SomeCoreDataTests : SenTestCase {
NSPersistentStoreCoordinator *coord;
NSManagedObjectContext *ctx;
NSManagedObjectModel *model;
NSPersistentStore *store;
}

@end

The environment for the tests is configured thus. I would have all of the error reporting done in tests, rather than that one lone assertion in -tearDown, because the SenTest framework doesn’t report properly on assertion failures in that method or in -setUp. So the -testThatEnvironmentWorks test method is a bellwether for the test environment being properly set up, but obviously can’t test the results of tear-down because the environment hasn’t been torn down when it runs.


#import "TuneNeedsHighlightingTests.h"

@implementation TuneNeedsHighlightingTests

- (void)setUp
{
model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain];
NSLog(@"model: %@", model);
coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model];
store = [coord addPersistentStoreWithType: NSInMemoryStoreType
configuration: nil
URL: nil
options: nil
error: NULL];
ctx = [[NSManagedObjectContext alloc] init];
[ctx setPersistentStoreCoordinator: coord];
}

- (void)tearDown
{
[ctx release];
ctx = nil;
NSError *error = nil;
STAssertTrue([coord removePersistentStore: store error: &error],
@"couldn't remove persistent store: %@", error);
store = nil;
[coord release];
coord = nil;
[model release];
model = nil;
}

- (void)testThatEnvironmentWorks
{
STAssertNotNil(store, @"no persistent store");
}
@end

The important part is in setting up the managed object model. In using [NSManagedObjectModel mergedModelFromBundles: nil], we get the managed object model derived from loading all MOMs in the main bundle—remembering that this is an injected test framework, that’s the application bundle. In other words the MOM is the same as that created by the app delegate. We get to use the in-memory store as a clean slate every time through, but otherwise the entity definitions and behaviours ought to be identical to those provided by the real app.

posted by Graham Lee at 18:12  

Friday, January 1, 2010

CocoaHeads Swindon, January and February

The next CocoaHeads Swindon will take place on 4th January, at the Glue Pot in Swindon. Get here at 8 for some NSChitChat with your (well, my) local Mac developer community.

There is no February meeting of Swindon CocoaHeads, on account of NSConference Europe taking place in Reading on that weekend. So buy your NSConference ticket and come along to say hi!

posted by Graham Lee at 14:15  

Friday, September 18, 2009

Next Swindon CocoaHeads meeting

At one time a quiet market town with no greater claim than to break up the journey between Oxford and Bristol, Swindon is now a bustling hub of Mac and iPhone development activity. The coming meeting of CocoaHeads, at the Glue Pot pub near the train station on Monday October 5th, is a focus of the thriving industry.

I really believe that this coming meeting will be a great one for those of you who’ve never been to a CocoaHeads meeting before. We will be having a roundtable discussion on indie software development and running your own micro-ISV. Whether you are a seasoned indie or just contemplating making the jump and what to find out what’s what, come along to the meeting. Share your anecdotes or questions with a group of like-minded developers and discover how one person can design, develop and market their applications.

You don’t need to register beforehand and there’s no door charge, just turn up and talk Cocoa. If you do want to discuss anything with other Swindon CocoaHeads, please subscribe to the mailing list.

posted by Graham Lee at 09:21  

Sunday, September 6, 2009

Unit testing Core Data-driven apps

Needless to say, I’m standing on the shoulders of giants here. Chris Hanson has written a great post on setting up the Core Data “stack” inside unit tests, Bill Bumgarner has written about their experiences unit-testing Core Data itself and PlayTank have an article about introspecting the object tree in a managed object model. I’m not going to rehash any of that, though I will touch on bits and pieces.

In this post, I’m going to look at one of the patterns I’ve employed to create testable code in a Core Data application. I’m pretty sure that none of these patterns I’ll be discussing is novel, however this series has the usual dual-purpose intention of maybe helping out other developers hoping to improve the coverage of the unit tests in their Core Data apps, and certainly helping me out later when I’ve forgotten what I did and why ;-).

Pattern 1: remove the Core Data dependence. Taking the usual example of a Human Resources application, the code which determines the highest salary in any department cares about employees and their salaries. It does not care about NSManagedObject instances and their values for keys. So stop referring to them! Assuming the following initial, hypothetical code:

- (NSInteger)highestSalaryOfEmployees: (NSSet *)employees {
NSInteger highestSalary = -1;
for (NSManagedObject *employee in employees) {
NSInteger thisSalary = [[employee valueForKey: @"salary"] integerValue];
if (thisSalary > highestSalary) highestSalary = thisSalary;
}
//note that if the set's empty, I'll return -1
return highestSalary;
}

This is how this pattern works:

  1. Create NSManagedObject subclasses for the entities.
    @interface GLEmployee : NSManagedObject
    {}
    @property (nonatomic, retain) NSString *name;
    @property (nonatomic, retain) NSNumber *salary;
    @property (nonatomic, retain) GLDepartment *department;
    @end

    This step allows us to see that employees are objects (well, they are in many companies anyway) with a set of attributes. Additionally it allows us to use the compile-time checking for properties with the dot syntax, which isn’t available in KVC where we can use any old nonsense as they key name. So go ahead and do that!

    - (NSInteger)highestSalaryOfEmployees: (NSSet *)employees {
    NSInteger highestSalary = -1;
    for (GLEmployee *employee in employees) {
    NSInteger thisSalary = [employee.salary integerValue];
    if (thisSalary > highestSalary) highestSalary = thisSalary;
    }
    //note that if the set's empty, I'll return -1
    return highestSalary;
    }

  2. Abstract out the interface to a protocol.
    @protocol GLEmployeeInterface <NSObject>
    @property (nonatomic, retain) NSNumber *salary;
    @end

    Note that I’ve only added the salary to the protocol definition, as that’s the only property used by the code under test and the principle of YAGNI tells us not to add the other properties (yet). The protocol extends the NSObject protocol as a safety measure; lots of code expects objects which are subclasses of NSObject or adopt the protocol. And the corresponding change to the class definition:

    @interface GLEmployee : NSManagedObject <GLEmployeeInterface>
    {}
    ...
    @end

    Now our code can depend on that interface instead of a particular class:

    - (NSInteger)highestSalaryOfEmployees: (NSSet *)employees {
    NSInteger highestSalary = -1;
    for (id <GLEmployeeInterface> employee in employees) {
    NSInteger thisSalary = [employee.salary integerValue];
    if (thisSalary > highestSalary) highestSalary = thisSalary;
    }
    //note that if the set's empty, I'll return -1
    return highestSalary;
    }

  3. Create a non-Core Data “mock” employee
    Again, YAGNI tells us not to add anything which isn’t going to be used.
    @interface GLMockEmployee : NSObject <GLEmployeeInterface>
    {
    NSNumber *salary;
    }
    @property (nonatomic, retain) NSNumber *salary;
    @end

    @implementation MockEmployee
    @synthesize salary;
    @end

    Note that because I refactored the code under test to handle classes which conform to the GLEmployeeInterface protocol rather than any particular class, this mock employee object is just as good as the Core Data entity as far as that method is concerned, so you can write tests using that mock class without needing to rely on a Core Data stack in the test driver. You’ve also separated the logic (“I want to know what the highest salary is”) from the implementation of the model (Core Data).

OK, so now that you’ve written a bunch of tests to exercise that logic, it’s time to safely refactor that for(in) loop to an exciting block implementation :-).

posted by Graham Lee at 15:36  

Saturday, September 5, 2009

CocoaHeads Swindon is this Monday!

The town of Swindon in the Kingsbridge hundred, Wiltshire is famous for two things. The first is the Wilts and Berks Canal, linking the Kennet and Avon at Trowbridge with the Thames at Abingdon. Authorised by act of parliament in 1775, the canal first passed through the town in 1804 and allowed an explosion in both the industrial and residential capacity of the hitherto quiet market cheaping.

The second is, of course, the local CocoaHeads chapter. Founded by act of Scotty in 2007, Swindon CocoaHeads quickly brought about a revolution in the teaching and discussion of Mac and iPhone development in the South-West, its influence being felt as far away as Swansea to the West and London to the East. Unlike the W&B canal, Swindon CocoaHeads is still thriving to this day. On Monday, 7th September at 20:00 there will be another of the chapter’s monthly meetings, in the Glue Pot pub near the train station. Here, Pieter Omvlee will be leading a talk on ImageKit, and the usual combination of beer and Cocoa chat will also be on show. As always, the CocoaHeads website contains the details.

posted by Graham Lee at 09:53  
Next Page »

Powered by WordPress