In the meantime, here’s a review my bandmate found for our show at XPO929 last October.
By the way, our new record, Freedom Now!, is out now. Bandcamp iTunes
]]>I hope to be more regular with the posts ongoing.
]]>Apple announced what was probably the worst kept secret yesterday: the iPad mini. Pretty much what everyone expected and people with more access than me covered it here, here, and here for example.
Of all the things announced, the one I found the most interesting was the Fusion Drive.
On the hardware side, it’s a combination SSD and HD. That’s nothing really new, as hybrid SSD/HD’s have been around for a little while. Interestingly, most hybrid drives have a small SSD (~8GB) to use purely as a cache, Apple’s Fusion Drive apparently uses a 128GB SSD. ArsTechnica has a nice analysis, where they speculate that it’s an implementation of automatic tiering. When Phil Schiller first mentioned it in the presentation, I was thinking of Hierarchical Storage Management. Which I remember from my days of playing on Vaxen.
Clearly, there’s a software component to Fusion Drive that’s implemented in Mountain Lion, and ArsTechnica references something called “Core Storage”. The only other reference I could find to “Core Storage” was here and a one-liner in the OSX Lion release note under File Vault.
This seems like a fascinating solution, and I’m going to do more research to understand how it all works.
]]>I left off with the rem
command-line app parsing the command-line arguments and stubbed out the functions to handle each command. I’m going to finish up the app by fleshing out each command function.
Before working on the handler functions, I want to do one more thing. I want to check that my command-line arguments actually specify an actual reminder list (calendar) and reminder id (index).
The EKCalendarItem
has two identifier properties: calendarItemIdentifier
and calendarItemExternalIdentifier
. At first thought, it would be a good idea to use one of those properties as the reminder id. However, both identifier properties return a GUID, which is great for programming purposes, but not so ideal for a command-line app. So, reminder id will be a simple integer to represent the reminder position in the reminder list.
Basically, we want to check the reminder list name specified and compare it to the known names in the calendars
dictionary. If the name is valid, then we check to see if the specified reminder id is within the index range of the reminder array for a given reminder list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
The ls
command allows the calendar
and reminder_id
variables to be nil
. Other than that, we use the arguments to assign values to the variables calendar
and reminder
.
Before implementing each function, I need some special characters for displaying the reminders. Let’s take another look at the pass output:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
It turns out that pass
uses the file system to store everything. To get the nicer output, it uses a program called tree, which prints out file-system hierarchies. We could hack something that writes out to the file-system, then use tree
to display it, but that’s overkill. Our reminders aren’t stored in a tree; they’re only two levels deep: the reminders and their list.
Looking at the pass
output, there are three “special” strings: “│ “, “├──”, “└──”. Looking at the tree
source, I found the unicode encodings for them. But, I’m cheating and just cutting and pasting into the code.
1 2 3 4 |
|
Now, there are two things to consider: 1. The last calender and reminder need to use the corner character. 2. The reminders of the last calendar don’t have the vertical bar. For expediency’s sake, I’m using two functions for output. One for calendars and one for reminders.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now, the listReminders
function is suppose to handle two cases: reminders in a specific reminder list or all reminders. The actually calls to printCalendarLine
and printReminderLine
will be handled by listCalendar
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Trying it out, it gives us this output.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Looks good, but one more tweak. All the other commands use a reminder_id to identify which reminder to use. Recall, that I’m just printing a simple integer index as the reminder_id. printReminderLine
and listCalendar
change to this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Removing a reminder is a simple call to removeReminder:commit:error:
to the event store.
1 2 3 4 5 6 7 8 |
|
Showing a reminder is a little verbose, but pretty straight-forward. The only catch is using an NSDateFormatter
instance to display the date properties of a reminder. The reminder properties I chose to display are: title, calendar (reminder list) name, creation date, last modification date (if different than creation date), start date (if set), due date (if set), and notes (if set).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Completing reminder is a simple matter of setting the completed property to YES and saving it.
1 2 3 4 5 6 7 8 9 |
|
That’s it. Now I have a simple command-line utility to let me list, view, remove and complete reminders. The project is up on Github. Feel free to use and give me your feedback. I have plans to extend rem
, check the issues for what’s in the queue.
I want a command-line tool to access Reminders.app data. I was inspired by the command-line password manager, pass. Based on the man page, pass
uses the following parameters:
1
|
|
This seems like a good model to follow. I’m going to name this command rem
, short for reminders. It also reminds me of my old BASIC days, where REM
started a comment line.
Here’s the functionality I want for the first iteration:
rem [ls] [calendar]
list remindersrem rm <calendar> <reminder_id>
remove a reminderrem cat <calendar> <reminder_id>
show a reminder detailsrem done <calendar> <reminder_id>
mark a reminder as completedI’m not including functionality to add or modify a reminder (yet). I just don’t need this functionality right now, as I usually add reminders via my iPhone.
Start with the OSX Command Line Tool Application template, with default options of using Foundation and ARC. Add EventKit.framework
to the rem
target and away we go.
I need to process the application arguments to know what I want to do. Before doing that, some set up. I #define
an array of command strings, and an enumerated type that maps to each element of the command string array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
The three global variables hold the parsed values from the command arguments. Some consider it bad form to use a global like this, but it’s just easier to keep it global rather than passing it around.
The function, parseArguments
, handles the argument parsing and is called from main
. I won’t go over the entire implementation, but want to make note of the of a few things.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
I use NSProcessInfo to get our command-line arguments. The first element will be the application, so I pop that off.
You can see where how the COMMAND
string array and CommandType
enumeration have to lineup. I can search the COMMAND
array for the matching string, and the index with be the CommandType enumeration value. Better than making a giant if
or switch
statement. If the command argument isn’t found the command string array, then I print an error message and spit out the usage.
The rest of the function reads the reminder list (calendar) name and reminder id from the args
array.
Two functions to handle reminders. fetchReminders
will load the reminders from the event store. sortReminders
will organize the fetched reminders into a dictionary keyed on reminder list (calendar) name.
Here’s fetchReminders
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
The EKEventStore
instance is a global that’s instantiated at application start. I use it create a predicate to fetch all the reminders across all calendars. Recall that in Part 1, I noted that the event store can be expensive to create and release. For a GUI (Cocoa or iOS), I’d create the event store in the application delegate and have it persist for the lifetime of the app.
I have some funky coding going on here. What’s up with the while
loop after the call to fetchRemindersMatchingPredicate:completion:
? I have a boolean flag, fetching
, that gets flipped inside the completion handler block. Until it gets flipped, the application’s runloop will run for 1/100th of a second. During this 1/100th of second, the completion handler block may get invoked. If so, the fetching
flag will be flipped, and the app will leave the while
loop.
fetchRemindersMatchingPredicate:completion:
is an asynchronous call. It gets sent to a another thread to run in the background. The problem is that the completion handler block gets invoked back on the main thread which can cause problems. Ideally, I would try to use Grand Central Dispatch like this:
1 2 3 4 5 6 |
|
Since the completion handler block gets invoked on the main thread, the semaphone blocks, and the signal is never sent. So looping over the run loop seems like the best bet.
Note: This is also the reason I’m loading all the reminders, then sorting them. If I had access to a synchronous reminder fetch, my first instinct would be to loop over each reminder list. However, loading all at once, then sorting is probably faster.
Now that I have an array of reminders, I sort them into a dictionary, keyed on calendar names. I ignore the completed reminders.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
I’ve got my command-line arguments, and I’ve got my (sorted) reminders. Now we need to do something. handleCommand
is a function around a switch statement for command-specific functions. We’ll just stub out those functions right now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
At this point, I can build rem
and run it. Each command I defined goes to the function that should handle it. But we’re not printing anything out yet. I’ll get to that in Part 3.
On the other hand, sometimes newer things work for me as well. When Reminders came out in iOS 5, I started using it, but wasn’t diligent about it. Once it got iCloud and Mountain Lion integration, I started using it a lot more. I even use Siri to make a reminder. I make a reminder on my iPhone, and I have it on all my devices, so I actually keep track of things.
Recently, I started using the command line password manager, pass, to manage my passwords. I tried other password managers, and this is a case where the simpler command-line interface works for me. You type pass
and get a list of all your accounts and passwords, which you catagorize. There are simple commands to add, extract and delete passwords. It’s all encrypted using GPG, so I don’t need to worry if I lose my laptop. All the data is stored in my home directory, so it’s part of my Time Machine backups.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
I wanted some similar for to access reminders. Now Apple’s been pretty good about making command-line equivalents for a lot of UI tools (think DiskUtility.app and diskutil), but this is one where they don’t have anything. A quick Google didn’t really find anything for me either.
So I have roll up my sleeves and write something myself.
Turns out that along with releasing Reminders in Mountain Lion, Apple has given us an SDK to access the data store behind Calendar and Reminders: EventKit. EventKit was first introduced in the OSX and iOS SDKs to give access to Calendar. Now with OSX Mountain Lion and iOS 6, Apple extended EventKit to grant access to Reminders. This extension to EventKit was possible because underneath it all, items in Reminders (“reminders”) are an extension of events in Calendar (“events”). In fact, both sets of data are stored in a single database, which Apple calls the Calendar database.
Why not use something like SQLite and let us access the calendar database directly? Odds are that your local calendar(s) are synchronized with calendars on server (i.e. iCloud, gCal, etc), typically using CalDAV. In order to maintain that synchronization, it’s easier to mediate all access to the local Calendar database to know when changes need to propagated to the server and known when changes have arrived from the server.
Note: I’m building an application to run in OSX. Most of what I cover is applicable for iOS, but there are some differences. For example, instantiating the event store is different between OSX and iOS. Read the Calendar and Reminders Programming Guide. Also, I’m building an application to access reminder, I won’t be covering event access.
Let’s review the EventKit framework with respect to reminders.
At the root of EventKit is the class EKEventStore
. This represents the Calendar database. EKEventStore
is a fairly heavy-weight object, taking a (relatively) long time to instantiate and release. As a result, your application should only instantiate a single EKEventStore
.
To instantiate your event store, you invoke the initializer, initWithAccessToEntityType:
, and EKEntityMask
for reminders.
1
|
|
With iOS, you instantiate your event store with a simple call to init
. Then, you request access to reminders using requestAccessToEntityType:completion:
. This invoking this method will cause iOS to ask the user if your application is allowed to access your Calendar database. You need to handle both cases in the completion block. This code is not in the OSX EventKit, since access to the Calendar database is granted automatically.
1 2 3 4 5 |
|
EventKit defines the class EKCalendar
to represent a calendar. Events are represented with the class EKEvent
. Reminders are modeled with the EKReminder
class. EKEvent
and EKReminder
are extensions of the abstract class EKCalendarItem
. Rather than defining a new class to contain a list of reminders, Apple chose to leverage the EKCalendar
class as the container of reminders. Conceptually, Reminders calls these Reminder Lists, but internal to the event store, they are just instances of EKCalendar
.
EKEventStore
defines two properties to access default the calendar and reminder list.
1 2 |
|
If you want to retrieve all calendars or reminder lists in the event store, you use the calendarsForEntityType:
method, specifying the EKEntityType
you want.
1 2 |
|
There’s another method, calendarWithIdentifier:
, that fetches a specific calendar, assuming you know the calendar’s unique identifier.
To create a new reminder, use the EKReminder
class method reminderWithEventStore:
. In order for the reminder to be valid, you need to provide values for the title
and calendar
properties.
1 2 3 |
|
To specify a start date, you use the startDateComponents
property. To specify a due date, use the dueDateComponents
property. These two properties are of the NSDateComponents
(doc) class, not the NSDate
class.
There are two EKReminder properties associated with reminder completion:
completed
a BOOL
flagcompletionDate
an NSDate
instance marking when the reminder was completed.Setting on of these properties will adjust the other. For example, if you set completed
to YES
, then completionDate
will be set to the current date. Setting completed
to NO
, sets completionDate
to nil
. Conversely, setting completionDate
to a date will set completed
to YES
; and setting completionDate
to nil
will set completed
to NO
.
To save a reminder, call the saveReminder:commit:error:
method. To delete a reminder, use the removeReminder:commit:error:
method.
1 2 3 4 5 |
|
1 2 3 4 5 |
|
In both cases, the commit
flag is used to tell the event store whether to apply the save/delete immediately, or queue the action as part of a batch. If you pass commit:NO
, then the changes will not happen until you invoke the EKEventStore method, commit:
.
1 2 3 4 5 |
|
To retrieve your reminders from the event store, you use the fetchRemindersMatchingPredicate:completion:
method. Predicates are objects that encapsulate the conditions to perform a search or query. Even though we use an NSPredicate
, we can’t construct our EventKit predicate by hand. We have to use one of the three predicate construction methods on EKEventStore
.
predicateForIncompleteRemindersWithDueDateStarting:ending:calendars:
find all incomplete Reminder items within a date range, for a given array of Reminder Lists.predicateForCompletedRemindersWithCompletionDateStarting:ending:calendars:
find all completed Reminder items within a date rage for a given array of Reminder Lists.predicateForRemindersInCalendars:
find all Reminder items for a given array of Reminder Lists.To fetch across all reminder lists (calendars), you can simply specify nil
for the calendar array.
1
|
|
For the predicate generator methods that take date ranges, you can use the NSDate
class methods distantPast
and distantFuture
to fetch all reminder items.
1
|
|
Once you have your predicate, you can perform your fetch, with a completion handler block.
1 2 3 4 5 |
|
The completion handler block is send an array of reminder items that match the predicate. This fetch is performed asynchronously and doesn’t need to be dispatched to another thread. However, the completion handler block is invoked on the main thread, which caused problems for me (I’ll get to that in a little bit).
As with calendars, there is a method to retrieve a specific reminder if you know its unique identifier, calendarItemWithIdentifier:
. You probably won’t use this method as it’s unlikely that you’ll have an item’s unique identifier handy.
I’m not going to cover these features in any detail, since I don’t need them for my command-line application. I’ve provided some links to the Apple documentation.
Ok, that’s a pretty good review of the EventKit framework. My next post will use this information to build the command-line tool.
]]>My niece is having a little trouble, so my sister says, “You give me a number and I’ll say the answer.”
“One” “Nine”
“Three” “Seven”
“Five” “Five”
“Nine” “One”
“Mom, you sound like an app.”
]]>Rather than beat up on Maps, Let’s take a look at some cool little features that you might not know about.
Emoji Keyboard. Enter Settings and navigate to General > Keyboard > Keyboards > Add New Keyboard. Scroll down and look for “Emoji”. Once you’ve added it, your keyboard should show a little globe icon on the lower left. If you tap it, you can toggle between your English keyboard and Emoji one. (If you’ve already installed another keyboard, tapping the globe with cycle through all your keyboards.
Limit Ad Tracking. Inside Settings, navigate to General > About > Advertising (it’s near the bottom). From there, you have the option to “Limit Ad Tracking.” iOS 6 introduces the concept of the “Advertising Identifier”, a temporary, anonymous device identifier. If you turn “Limit Ad Tracking” on, apps are not allowed to send you targeted ads. It’s not clear if this feature is working 100% yet, but in theory, it seems like a nice idea.
Do Not Disturb Mode. Settings > Do Not Disturb. Your phone won’t light up, ring or chime when messages, calls or notifications arrive. I’ve been setting every night before bed. You can customize it inside Settings > Notifications > Do Not Disturb. From there, you can schedule it to activate automatically, allow calls from specific groups of people, and allow a repeated (i.e. urgent) call to ring through. No more getting woken up by your friend’s late night Facebook updates, Tweets, or drunk dials.
Custom Alarm Sounds. Now when you set your clock alarm, you can use a custom ringtone or song. Now you can start your day with the theme from Rocky and take on the world.
Really delete Gmail. When you want to delete a message from your Gmail account, tap and hold the Archive Box icon. You’ll be asked if you want to delete or archive the message. Not perfect, but at least now I can delete my Gmail messages.
(Bonus for giving your phone to kids) Guided Access. Technically, Guided Access is an “Accessibility” feature, but I use it when my niece or nephew wants to play a game on my phone. Last time I did that, they called everyone in my Contacts (and got Siri to call me “Poopie Pants”). Turn on Guided Access via Settings > General > Accessibility > Guided Access. Launch the app you want and triple-tap the Home button. Once Guided Access is turned on, you can’t leave the app unless you triple-tap and enter a passcode.
Right now, I’m in Denver, attending 360iDev.
It’s the last day right now, but I did do a book signing for Pro iOS Tools. Here’s a picture:
]]>> caffeinate -d
Via OneThingWell.
]]>More here
]]>Early one morning, a programmer asked the great master:
“I am ready to write some unit tests. What code coverage should I aim for?”
The great master replied:
“Don’t worry about coverage, just write some good tests.”
The programmer smiled, bowed, and left.
…
Later that day, a second programmer asked the same question.
The great master pointed at a pot of boiling water and said:
“How many grains of rice should put in that pot?”
The programmer, looking puzzled, replied:
“How can I possibly tell you? It depends on how many people you need to feed, how hungry they are, what other food you are serving, how much rice you have available, and so on.”
“Exactly,” said the great master.
The second programmer smiled, bowed, and left.
…
Toward the end of the day, a third programmer came and asked the same question about code coverage.
“Eighty percent and no less!”
Replied the master in a stern voice, pounding his fist on the table.
The third programmer smiled, bowed, and left.
…
After this last reply, a young apprentice approached the great master:
“Great master, today I overheard you answer the same question about code coverage with three different answers. Why?”
The great master stood up from his chair:
“Come get some fresh tea with me and let’s talk about it.”
After they filled their cups with smoking hot green tea, the great master began to answer:
“The first programmer is new and just getting started with testing. Right now he has a lot of code and no tests. He has a long way to go; focusing on code coverage at this time would be depressing and quite useless. He’s better off just getting used to writing and running some tests. He can worry about coverage later.”
“The second programmer, on the other hand, is quite experience both at programming and testing. When I replied by asking her how many grains of rice I should put in a pot, I helped her realize that the amount of testing necessary depends on a number of factors, and she knows those factors better than I do – it’s her code after all. There is no single, simple, answer, and she’s smart enough to handle the truth and work with that.”
“I see,” said the young apprentice, “but if there is no single simple answer, then why did you answer the third programmer ‘Eighty percent and no less’?”
The great master laughed so hard and loud that his belly, evidence that he drank more than just green tea, flopped up and down. “The third programmer wants only simple answers – even when there are no simple answers … and then does not follow them anyway.”
The young apprentice and the grizzled great master finished drinking their tea in contemplative silence.
For more, read The Way of Testivus
]]>The consultant’s finding: The Japanese team had eight people rowing and one person steering; the American team had one person rowing and eight people steering. After a year of study and millions spent analyzing the problem, the consultant firm concluded that too many people were steering and not enough were rowing on the American team. So as race day neared again the following year, the American team’s management structure was completely reorganized. The new structure: four steering managers, three area steering managers, and a new performance review system for the person rowing the boat to provide work incentive.
The next year, the Japanese won by TWO miles!!!
Humiliated, the American corporation laid off the rower for poor performance and gave the managers a bonus for discovering the problem.
]]>Just to satisfy my curiousity, I ran my iPad charge through a Kill-A-Watt 4400 to see how much electricity I was “leaking” when not charging my iPad. The result: unmeasurable by the meter after a day.
]]>My friend Bruce is a member of the inner circle (cabal?). Everytime I come to San Francisco, I try to get him to take me there. My first time was at WWDC last year. Since then, I’ve gone three more times, and brought many of my colleagues with me. Now it’s expected that I make arrangements for any trip to SF.
This time I brought my colleagues Simon and Sue.
We had to wait for the previous seating (a birthday party) to finish.
Rumor has it that you can get banned for violating the rules.
Don’t know if you can tell, but that’s a big bottle.
I forgot to snap a picture of the first dish. It was mackeral with a ginger soy sauce, garnished with scallions.
A salad with calamari, sprouts and dressing.
Three mushroom with a soy-based sauce, baked.
Our host at work.
That’s a lot of fish!
Pumpkin, Burdock (I think) and Zucchini.
Neck or Shoulder of a yellow mackeral
Broiled Eel. Unagi!
Melon with Meyer lemon. Never had lemon juice on a melon before. It was delicious!
]]>I can’t believe I forgot to take a picture of Hair Force One
The rest of the day was a blur, and we ended it at a party hosted by our friends at [Crashlytics](http://www.crashlytics.com]
]]>I built on top of the work in progress in the OCMock master. It works, but will probably need some refinement.
Let’s the example of a static constructor in NSString
:
NSString *s = [NSString string]; // returns empty string
In order to mock this, we do this:
id mockStringClass = [OCMockObject mockForClassObject:[NSString class]];
NSString *mockString = @"Mocked String Result";
[[[mockStringClass stub] andReturn:mockString] string];
NSString *actualString = [NSString string];
STAssertTrue(0 != [actualString length], @"Expected String Not Be Empty");
I sent the pull request about two weeks ago, still waiting to hear back. Read on if you’re interested in the details…
OCMock is one of the more popular Objective-C/iOS mocking frameworks out there. It actually predates iOS.
One of the biggest problems I’ve always had with OCMock was its inability to handle class methods. There are many times when I’ve had to call a class method, that for various reasons I needed to mock.
1 2 |
|
If you’re running this on the simulator, you don’t have a media library (aka iTunes library). So how do you test your code? Besides, testing against “live” data is probably a bad idea.
Looking at the OCMock Github repository, there was some preliminary work done on supporting class methods. The class is named OCMockClassObject (header, source). So why doesn’t this work?
Looking into the unit tests for OCMock, I found the tests for OCMockClassObject. Interestingly, one of the tests,
// - (void)testForwardsUnstubbedMethodsToRealClassObjectAfterStopIsCalled
is commented out. Why? First, let’s understand how OCMock works.
When you mock a class, you declare something like this:
1 2 3 4 |
|
mockObject
is declared as mock for NSString
. Also, we state that we expect the method lowercaseString
to be invoked on mockObject
. Furthermore, lowercaseString
should return “mockedstring”. We defined some test code where mockObject
is used. Typically, it is passed into the method we are testing. Finally, we invoke [mockObject verify]
to confirm that lowercaseString
was called. If not, the test fails.
Without delving too deeply into the specifics of the OCMock, how are these method calls intercepted? Via NSProxy.
In short, NSProxy
is an abstract class, and requires its subclasses to implement
- (void)forwardInvocation:(NSInvocation *)anInvocation`
An OCMock
class (OCMockRecorder
specifically) implements forwardInvocation
to handle the method call as specified. In the example above, the call is forwarded to a return the value specified in the andReturn:
call.
For other mocking options in OCMock
+ (id)mockForProtocol:(Protocol *)aProtocol;
+ (id)partialMockForObject:(NSObject *)anObject;
+ (id)niceMockForClass:(Class)aClass;
+ (id)niceMockForProtocol:(Protocol *)aProtocol;
+ (id)observerMock;
the forwardInvocation relies on method swizzling to redirect methods to expectations and/or stubs.
So why doesn’t this work for class methods? Well, it turns out it does. The real problems is resetting back to the original state.
One of the important features of unit testing is to make sure each test is atomic. That is, the results on one test should not impact the results of another. To that end, we use setUp
and tearDown
methods to initialize and clean up our environment before and after running each test. Furthermore, we may do additional configuration inside each test, and it’s incumbent upon us as developers to make sure we clean up after ourselves.
Looking back at the OCMock
source, we can see that the mocking of class methods works fine. It’s just that our swizzled class method stays swizzled. Why does it work for instance methods? Because the instance is destroyed with each test (hopefully), so any evidence of method swizzling just disappears with the instance. Class method swizzling will persist for the run life of the application (or in this case, the test harness). So we need a away to undo our swizzling.
My solution was simple: create an NSMutableDictionary
to store the IMP pointer to the original class method, keyed on the string representation of the class method selector. When the mocked class object is deallocated, simply iterate over the dictionary, replacing all the swizzled class methods for their originals.
I’m making the assumption that the location of the method (and class definintion) pointed to by the IMP
pointer won’t change during the run life of the application/test harness. I figured this was unlikely, but I needed to make sure.
Who better to ask than Mr. Objective-C: Steve Naroff? His response:
In theory, no. In practice, yes.
If the code were dynamically loaded/unloaded, the address could
change. If the code were compiled on the fly, you could imagine
the runtime purging infrequently used code and re-instantiating it
later if necessary (to keep the working set of methods down on a
smaller footprint device). Unless things have changed a great deal
in the past 2 years, I'd be surprised if my scenarios happen often
(or at all).
Well, I’m pretty sure that won’t happen while running tests, so this should be safe.
This can be pretty esoteric stuff, that most iOS won’t ever have to deal with, but to make testing frameworks, know the Objective-C runtime is pretty essential. I recommend the following for more information:
]]>