Chris Eidhof

Finding My Most Influential Twitter Followers

I spent an afternoon trying to find out who of my Twitter followers is the most influential. I wrote the script to fetch the data in Ruby, and stored the data in MongoDB.

Setup

First, we’ll need to set up some things. Make sure you have a recent version of Ruby installed, and install the following gems:

gem install twitter mongo bson_ext

Although the bson_ext isn’t really necessary, it will make things a bit faster.

Also install mongodb. One of the easier ways to do this is using homebrew:

brew install mongodb

Make sure to fire up mongodb, and we’re all set on the client.

The last step is to get an API key at Twitter. Go to https://dev.twitter.com/ and create a new application. Don’t worry about the details too much.

Fetching a list of Twitter follower IDs

First, we will write a file called twitter-config.rb:

Twitter.configure do |config|
  config.consumer_key = 'CONSUMER_KEY'
  config.consumer_secret = 'CONSUMER_SECRET'
  config.oauth_token = 'OAUTH_TOKEN'
  config.oauth_token_secret = 'O_AUTH_TOKEN_SECRET'
end

Make sure to replace the right-hand sides by the appropriate values (they should match the twitter application you just created).

The script for fetching your Twitter follower ids is now extremely simple:

require 'rubygems'
require 'twitter'
require 'twitter-config.rb'

follower_ids = Twitter.follower_ids
File.open("followers", 'w') { |f| 
  f.write(follower_ids.collection.join("\n")) 
}

We are just using the Twitter API to fetch the follower ids and store them in a file. The last part is essential when working with APIs and larger datasets: make sure you store intermediate results. Often, computations can take a long time, and by storing intermediate results you can resume where you’ve left off.

Write the script to fetch the data

Again, this script is very simple. We iterate over the list of followers, and make the corresponding Twitter API call. One thing to note here is that you will hit the rate limit if you have more than 350 followers. Therefore, the script checks if you already have fetched the data for a follower, and skips fetching the data. If you hit the rate limit, simply wait some minutes and run the script again. Eventually, it will finish.

require ‘rubygems’ require ‘twitter’ require ‘database-config.rb’ require ‘twitter-config.rb’

File.open(“followers”).each_line {|follower_id_s| follower_id = follower_id_s.to_i

 if COLL.find("id" => follower_id).count > 0 then
   puts "Already have data for #{follower_id}"
 else
   puts "Fetching data for #{follower_id}"
   follower_hash = Twitter.user(follower_id).attrs
   COLL.insert follower_hash
 end

}

The database-config.rb file looks like this:

require 'mongo'

connection = Mongo::Connection.new
db = Mongo::Connection.new.db("twitterinsight")
COLL = db.collection("followers")

It sets up a global variable COLL that you can use in the scripts for analyzing and populating the database.

Analyze the data

Once you have filled your database with data, you can analyze it by running queries on it. The reason I wrote this code is because I wanted to know who my most influential followers were:

require 'rubygems'
require 'database-config.rb'

COLL.find().
  sort(['followers_count', :desc]).
  limit(50).
  to_a.map { |x| 
  ratio = (100 * (x['friends_count'].to_f / 
                x['followers_count'].to_f)).to_i
  statuses = x['statuses_count']
  puts "#{x['screen_name']} - " +
       "#{x['followers_count']} / #{x['friends_count']}" +
       ", #{ratio} [#{statuses}] @ #{x['location']}" 
}

As you can see, this script prints the screen name, number of followers, number of friends, the friends/followers ratio, the amount they tweet and the location. Here’s some example output:

copumpkin - 22397 / 368, 1 [3597] @ Boston, MA
DotSauce - 15641 / 12400, 79 [7377] @ NC, USA
dominiek - 14487 / 1085, 7 [5342] @ AMS, SFO, TYO
TivoliUtrecht - 10935 / 442, 4 [3950] @ Utrecht, The Netherlands
theSeanCook - 10006 / 1137, 11 [2345] @ San Francisco
Duetschpire - 8194 / 584, 7 [2112] @ Australia
Polledemaagt - 7518 / 2563, 34 [37721] @ Gent, London, Amsterdam
NetBlueWeb - 6651 / 5520, 82 [9] @ British Columbia
andreisavu - 6648 / 6497, 97 [11496] @ Bucharest, Romania
StackMob - 5571 / 1835, 32 [1015] @ SF

The major takeaway for this post is to cache all intermediate data. First make sure you have all the data you wanted to analyze in one place on your computer (or your cluster). Optionally massage it into a format you want, and then run the analysis without hitting external servers all the time.



Changing Tech Education

Sometimes I talk to my friends about why I want to become rich. It’s to solve the money problem, and once it’s solved, I hope to have the time, connections and status to work on bigger problems. I already know exactly what I want to try once I’m there: change tech education.

When I did my bachelor’s degree in computer science, I was quite bored most of the time. Officially, you were supposed to work on it at least 40 hours per week, but for me and most of my friends it was closer to 10 hours. The other time we would spend working, or having a hangover.

One of the causes why I didn’t have to work so hard at university is the different levels of experience. When I started, I already had a few years of building websites under my belt. For example: I worked as the main developer at one of biggest soccer sites, and at the same time I had to take a course on the basics of HTML. Obviously, this disconnect was a problem for me, and for a lot of other motivated students around me. I want to solve this problem.

My solution is a program similar to a bachelor’s degree in length, but a lot more intense. In order to enter this program, you have to show your aptitude at programming. The program will have teachers from both academia and industry, because I think both theory and practice are very important. Only the best students and teachers can participate. Students have to study theory, but will also spent a lot of time doing internships and real projects. They will learn standard theory, but also topics such as node.js, statistics for big data analysis, native mobile programming, HTML5, git internals, et cetera. For each batch of students, I want to get financing from big companies who then get access to the talent.

To pull this off will be a huge and hard job, but I look forward to it. I’m still in the exploratory phase, and not sure whether I will actually do it, but at this moment in time I’m talking with people in both industry and academia to see whether it’s feasible. Instead of a bachelor’s program, a very good first step might be to organize a Hacker’s Summer School: 6 weeks of learning and hacking together with other talented hackers and awesome teachers.

Part of me thinks I’m too young, inexperienced and not connected enough to do this. But I still think I should at least explore whether it’s possible and should try. I have no idea in which form exactly. I don’t know what the topics are going to be, who the teachers are or even what the team should look like. I’ll keep you posted!



Radical Friendship

A few months ago I wrote about radical honesty. I tried being more honest, but in practice it’s quite awkward. Our society isn’t really used to being completely honest and open. For example, if a friend wears something I really dislike, I won’t tell him. It’s just my opinion, and by expressing it I don’t think I’ll help anybody.

In How To Win Friends and Influence People, Dale Carnegie says things like ” “Don’t criticize, condemn or complain”, “The only way to get the best of an argument is to avoid it” and “Show respect for the other person’s opinions. Never say, ‘You’re wrong.’”. In a way, you could interpret these statements as: don’t be honest when you disagree with somebody.

Recently, I was reading How Proust Can Change Your Life by my favorite modern philosopher Alain de Botton. He writes that Proust deliberately tried to avoid truth in friendship, lavishing his friends with compliments, sincere or not. This is pretty much the exact opposite of radical honesty, and equally exciting for me. A very interesting approach, although I don’t think I’ll use it.



Starting new projects

One of the most exciting things for me is starting new projects. Each time I start to build a new app, I get way too excited and see rainbows and ponies everywhere. “This time it’s going to beautiful, easy, quick to finish and lots of fun”. There is a blank slate and in my mind everything fits together perfectly.

However, in reality, after I’ve spent some time on a project, I start to see the design mistakes I made. Sometimes I even realize that the product/project itself isn’t a good idea. The longer I work on something, the harder it is to finish it. It definitely helps to work with a team, as you can pull each other through the hard times. When working with clients, it’s a lot easier to push yourself to finish something.

I might have found a way to utilize the starting of new projects in order to be more productive. In an earlier post, I wrote about how I create lots of test projects. I still do this, and sometimes when I am not looking forward to implementing some new feature, I instead start implementing it in a blank slate test project. As an additional advantage, the new code will probably be nicely isolated and loosely coupled.

Anyway, to get back to starting new projects: I think I will struggle with this throughout the rest of my life. It will always be more attractive to start something new than to finish something. However, realizing this problem is the first step in combatting it. And by using tricks like the one above I think it could even be bent to an advantage.



Berlin Tech Meetups

I’m trying to come up with a list of tech-related meetups in Berlin. This list is far from complete: please help me to add more meetups, and improve the descriptions. Just send me an email with corrections and/or suggestions. A next step would be to create a Google Calendar just like Peter Robinett did for Dutch Tech Meetups.

For people who don’t speak German: Germans are very friendly and speak English, especially the techies. Sometimes the announcements, webpages or conversations are in German, but Germans are very polite and quickly switch to English when necessary.

Here’s the preliminary list I’ve come up with:

Berlin Google Technology User Group

We meet regularly every 6 - 8 weeks to talk about recent developments in Google technologies or to spread practical knowledge through presentations. Next meeting: March 26th.

Berlin HTML5 User Group

This group was created to gather the HTML5 community in Berlin. Meetups/Webinars will be organized every month once moment we will have a certain number of people. All HTML5, CSS or Javascript enthusiasts (Developers, designers, webmasters…) are welcome to join.

The Berlin Semantic Web Meetup Group

Meet local people interested in standardized and future (Semantic Web) Internet technologies aimed at turning the Web into a huge repository of cross-referenced, machine-understandable data and executable artifacts, processes and services.

CouchDB User Group

We talk about everything couchdb, its future, our future, related technology and (our) projects connected to it.

Berlin DevOps

The group aims to bring Berlin’s system administrators, system engineers, system developers, web operators, software developers, test engineers, software architects and alike together and create a media for information and knowledge exchange.

Scrumtisch Berlin

Talking about everything scrum and agile.

Java User Group Berlin Brandenburg

Talk about Java.

CocoaHeads Berlin

CocoaHeads is a group devoted to discussion of Apple Computer’s Cocoa Framework for programming on MacOS X (including the iPhone).

Erlang Berlin

Talk about everything Erlang.

Clojure User Group Berlin

The Clojure User Group Berlin is a get together of people interested in Clojure—a Lisp dialect for the JVM. Come and discuss what you’re working on, have questions, and meet other like-minded people.

Django User Group Berlin

Wir sind eine Gruppe von 10 bis 20 Personen, die sich regelmäßig monatlich zusammenfinden, um über Django und die Welt zu philosophieren. Wir freuen uns über jeden, der Spaß an Django hat oder sich dafür interessiert!

FSFE Berlin

Wir sind eine aktive Gruppe von Fellows der Free Software Foundation Europe und Freie-Software-Interessierte. Wir machen auf die gesellschaftlichen Aspekte Freier Software aufmerksam.

Berlin.js

Berlin.JS is a usergroup focused on Javascript and related topics. We meet regularly on the 3rd Thursday each month at 7p.m. at co.up Offices, Adalbertstraße 7-8 in Berlin-Kreuzberg.

Berlin Tech Meetup

Every month we will have 4-10 companies do a short (3-5 min.) demo of something cool to Berlin’s tech community (geeks, investors, entrepreneurs, hackers, etc). We’ve seen it in New York and want to bring it to Berlin.

Silicon Allee Breakfast Meetup

German tech news in English - Berlin-based Community of Entrepreneurs. Not just tech, but all kinds of people involved in the startup scene.

Scala Meetup

Talk, Demo, Hack, Breathe, and just about anything else - Scala. In and around Berlin.

Credits: I’ve added the meetups that Isabel wrote about a few months ago: Berlin Tech Meetups.



Entitled

This post is inspired by Marco’s excellent piece: Right versus pragmatic.

I grew up in a small village. When I was 13, I didn’t really have money to buy music, but I still saved up and went to the record store so I could buy Americana. When I was 15, I got my first computer-job (as a php programmer). One month, I even made 1000 guilders (which was a lot for me). On Saturdays, I would cycle to Germany with my brother to buy CD’s at the big electronics store just across the border. For about two years, I bought a couple of CD’s per week.

However, around that time, my musical taste started developing to slightly more unknown bands, and the easiest way for me to get the music was by downloading it (I vividly remember using Kazaa and Napster, those were the days). The nearest record store where they had a decent selection of music was a 2 hour bike ride, which was not really an option.

For movies, it was the same. Once bandwidth started increasing, it just became easier to download movies. At the movie rental, they did not have the kind of movies I wanted to watch. I would buy them sometimes, but more often than not I decided that I wanted to see a movie after dinner, and downloading it enabled me to watch it the same night, rather than the next day (or even a few days later, if I had to order it online). TV shows generally lagged behind a whole season, if they would even broadcast them at all.

Pirating music, movies and also software became second nature. I didn’t think twice about it. Money was never the reason, it’s just that it’s so much easier to download something off of the Pirate Bay than order it online. However, after doing this throughout my student time, money did start to matter. Once you’re really accustomed to getting something for free, you start to feel entitled to getting it for free. And of course, virtually all my peers did it.

After graduating, I hardly ever used any pirated software, except for a certain graphics editing application. I still pirated music and movies (by the way, in The Netherlands you are allowed to download music and movies).

Recently, I had some hardware problems with my hard drive. For the first time in ever, I did a clean install. I copied back my old files, but not the pirated music, movies or software. My laptop is 100% free of pirated content. It feels good. My mindset has changed too: I realized I am not entitled to download it for free.

I think there are lots of opportunities for entrepreneurs because of this problem, especially in Europe. Spotify is one example, but we Europeans want to watch movies and the latest TV shows too. At this moment, I will refrain from watching movies that are not in the cinema or available in local stores.

An affordable graphics editor is definitely an opportunity too. Or maybe some subscription-based business models: there are some expensive applications that I would like to use for half an hour each month. For example, about once a year a client’s designer sends me a Fireworks file. The only thing I want to do with it is slice it up and produce some png’s. Instead of asking the designer to do it for me, I would rather rent an hour of Fireworks: it saves time and energy for both me and the designer, and Adobe would make more money. Everybody would win.



Accessing an API using CoreData’s NSIncrementalStore

Note: I’ve also posted this article on github as gist, for better readability of the code: nsincrementalstore.markdown.

In this article, we will see how to use Core Data for accessing your API. We will use the Bandcamp API as our running example. I’ve only been experimenting with this code for a few days, so there might be mistakes in there.

One of the problems with accessing an API is that you typically have API calls everywhere in your code. If the API changes, there are probably multiple spots in your code that you have to change too. Your code knows about the structure of the API results in lots of places, for example, in the Bandcamp API there is a track entity which has the property title. It would be easy to pass the API results around as an NSDictionary and lookup the title key in that dictionary. However, if they would change it to songtitle, you have to find this everywhere in your code.

Another problem is that most APIs are not object oriented. Suppose you have an Album entity that has a to-many relationship with a Track entity: each album can have multiple tracks. In your controller, you will probably have multiple API calls, one for getting the album and another for getting its tracks.

By using a new feature in Core Data we can solve these problems by adding another layer on top of the API which allows us to access the API as if it were an object graph. Entities can be concrete subclasses of NSManagedObject.

First, we will build a regular class that accesses the API and parses the JSON. Then, we will create a CoreData data model that represents the API in an object-oriented way. Finally, we will create an NSIncrementalStore subclass and implement the necessary methods to fetch the entities and relationships.

Step 1: Wrap the API

The first step is to create a simple class that implements your API. Doing this is straightforward, and I will not go into details here. This is the header file for the Bandcamp API:

@interface BandCampAPI : NSObject
+ (NSArray*)apiRequestEntitiesWithName:(NSString*)name 
                             predicate:(NSPredicate*)predicate;
+ (NSArray*)apiDiscographyForBandWithId:(NSString*)bandId;
@end

In summary, you can search for bands, get a band by id, get an album by id, get a track by id. If you request the info for an album, you also get a list of its tracks included in the response. Finally, there is a method for getting the discography of a band.

To find all bands named “Rue Royale” we can do the following API call:

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"name == %@", @"Rue Royale"];
NSArray* bands = [BandCampAPI apiRequestEntitiesWithName:@"Band"
                                               predicate:predicate];

This will return an NSArray with an NSDictionary for each found band. Please have a look at the source to see how it is implemented. The result is as following:

({
    "band_id" = 4246760315;
    name = "Rue Royale";
    "offsite_url" = "http://rueroyalemusic.com";
    subdomain = rueroyale;
    url = "http://rueroyale.bandcamp.com";
})

The discography call looks like this:

NSString* sideditchId = [NSString stringWithFormat:@"2721182224"];
NSArray* albums = [BandCampAPI apiDiscographyForBandWithId:sideditchId];

And the result like this:

({
    "album_id" = 3366378415;
    artist = Sideditch;
    "band_id" = 2721182224;
    downloadable = 1;
    "large_art_url" = "http://f0.bcbits.com/z/70/81/70810089-1.jpg";
    "release_date" = 1267401600;
    "small_art_url" = "http://f0.bcbits.com/z/37/58/3758272301-1.jpg";
    title = "Mary, Me Demo";
    url = "http://sideditch.bandcamp.com/album/mary-me-demo?pk=191";
})

Now that we have access to the raw API, we can continue by making an object-oriented version of the API.

Step 2: Define the model

The next step is to create a new CoreData data model. For each API entity, we create a corresponding CoreData entity (named Band, Album and Track). There are properties for all of the entities, and more importantly: relationships between the entities. For example, the Album entity has a relationship tracks which is a to-many relationship to the Track entity. Creating this data model is exactly the same as creating a normal CoreData data model.

In this project, I’ve reused all the keys that Bandcamp uses. For example, an album has a key large_art_url, so our entity has a key like that as well. However, this is not necessary. We can name the keys anything we want, we just have to make sure that we convert them in our NSIncrementalStore subclass.

Step 3: implement the NSIncrementalStore methods

Now the hard bit: creating a subclass of NSIncrementalStore. There is a really interesting article by Drew Crawford about how to do this. However, it lacks a concrete example. I created a subclass BandCampIS of NSIncrementalStore. In the documentation, you can see which methods to implement. We will start with executeRequest:withContext:error:. This method is called for multiple purposes, but we will now focus on only one case: when it’s called with an NSFetchRequest.

The first argument is of type NSPersistentStoreRequest which is the request we have to act upon. By inspecting its requestType we can turn it into a specific subclass, such as NSFetchRequest. For clarity, the handling code is factored out into a method fetchObjects:withContext, which we will define later.

- (id)executeRequest:(NSPersistentStoreRequest*)request 
         withContext:(NSManagedObjectContext*)context 
               error:(NSError**)error {
    if(request.requestType == NSFetchRequestType)
    {
        NSFetchRequest *fetchRequest = (NSFetchRequest*) request;
        if (fetchRequest.resultType==NSManagedObjectResultType) {
            return [self fetchObjects:fetchRequest 
                          withContext:context];
        }
    }
    return nil;
}

The fetchObjects:withContext method should return an NSArray containing NSManagedObject items. In the fetchObjects:withContext method, we call the appropriate API method, and get back an NSArray with an NSDictionary for each item. For each item, we create a new NSManagedObjectID and cache the values. Again, this is factored out into a separate method. Then, we call the objectWithID method of NSManagedObjectContext to create an empty NSManagedObject for the item.

- (id)fetchObjects:(NSFetchRequest*)request 
       withContext:(NSManagedObjectContext*)context {
    NSArray* items = [BandCampAPI apiRequestEntitiesWithName:request.entityName 
                                                   predicate:request.predicate];
    return [items map:^(id item) {
        NSManagedObjectID* oid = [self objectIdForNewObjectOfEntity:request.entity 
                                                        cacheValues:item];
        return [context objectWithID:oid];
    }];
}

In the Bandcamp API, each entity can uniquely be identified by its key. For example, a Band entity has the key band_id that uniquely identifies a band. Using the CoreData method newObjectIDForEntity:referenceObject we can create an NSManagedObjectID based on this id. Finally, we cache the values for an entity in the cache instance variable (which is an NSDictionary with NSManagedObjectID as keys and NSDictionary objects as values).

- (NSManagedObjectID*)objectIdForNewObjectOfEntity:(NSEntityDescription*)entityDescription
                                       cacheValues:(NSDictionary*)values {
    NSString* nativeKey = [self nativeKeyForEntityName:entityDescription.name];
    id referenceId = [values objectForKey:nativeKey];
    NSManagedObjectID *objectId = [self newObjectIDForEntity:entityDescription 
                                             referenceObject:referenceId];
    [cache setObject:values forKey:objectId];
    return objectId;
}

Note that when we created the NSManagedObject, the properties were not set. The managed objects only contain their unique ID. Core Data uses faulting when you access the properties, and we have to implement another method to support it: newValuesForObjectWithID:withContext:error:. This method will get called when we access the property of a managed object. Each NSManagedObject is backed by an NSIncrementalStoreNode that holds the values. In a database backend, the NSIncrementalStoreNode would correspond to a database record. In our API, it will be filled with an NSDictionary returned from the API. Note that in the previous method, we already cached this NSDictionary, so we don’t need to do an API request:

- (NSIncrementalStoreNode*)newValuesForObjectWithID:(NSManagedObjectID*)objectID 
                                        withContext:(NSManagedObjectContext*)context
                                              error:(NSError**)error {
    NSDictionary* cachedValues = [cache objectForKey:objectID];
    NSIncrementalStoreNode* node = 
        [[NSIncrementalStoreNode alloc] initWithObjectID:objectID
                                              withValues:cachedValues 
                                                 version:1];
    return node;
}

There is one more important method to implement: newValueForRelationship:forObjectWithID:withContext:error:. As you can guess from its name, this is where we lookup the relationships. We look at the relationship source and target entity and name, and call the appropriate API methods to fetch the relationship objects. Again, we cache the API results and return an NSArray with NSManagedObjectID for each result.

We will implement two relationships in this method: discography, which relates a Band to its Albums, and tracks which relates an Album with its Tracks. In this method, we dispatch on the relationship name. In a more complicated situation where multiple relationships with the same name exist you can also inspect the relationship’s entities.

- (id)newValueForRelationship:(NSRelationshipDescription*)relationship 
              forObjectWithID:(NSManagedObjectID*)objectID
                  withContext:(NSManagedObjectContext*)context
                        error:(NSError**)error {
    if([relationship.name isEqualToString:@"discography"]) {
        return [self fetchDiscographyForBandWithId:objectID 
                                       albumEntity:relationship.destinationEntity];
    } else if([relationship.name isEqualToString:@"tracks"]) {
        return [self fetchTracksForAlbumWithId:objectID 
                                   trackEntity:relationship.destinationEntity];
    }
    NSLog(@"unknown relatioship: %@", relationship);
    return nil;
}

Note that the return value of the method should be an NSArray with NSManagedObjectIDs, not NSManagedObjects! To give an example, for the discography we do an API call to fetch the raw data, and then create NSManagedObjectIDs for each album returned by the API:

- (NSArray*)fetchDiscographyForBandWithId:(NSManagedObjectID*)objectID
                              albumEntity:(NSEntityDescription*)entity {
    id bandId = [self referenceObjectForObjectID:objectID];            
    NSArray* discographyData = [BandCampAPI apiDiscographyForBandWithId:bandId];
    return [discographyData map:^(id album) {
        return [self objectIdForNewObjectOfEntity:entity cacheValues:album];

    }];
}

Usage

Now we can leverage the power of CoreData to access our API. Consumers of our model don’t know whether they are accessing an API, an SQLite database or an XML file. It’s all abstracted away into our NSIncrementalStore subclass.

To give an example, here’s how you can find a band using Core Data:

NSEntityDescription *entityDescription = [NSEntityDescription
                                          entityForName:@"Band" inManagedObjectContext:moc];

NSPredicate* predicate = [NSPredicate predicateWithFormat:@"name == %@", @"Rue Royale"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = entityDescription;
fetchRequest.predicate = predicate;

NSArray *results = [moc executeFetchRequest:fetchRequest error:nil];
Band* band = [results lastObject];

This code will make the appropriate API request and return an object of type Band. The Band class is generated from our CoreData model, and all of the properties are accessible as Objective-C properties.

When you then want to find the bands albums, it’s as simple as doing:

for(Album* album in band.discography) {
  NSLog(@"album title: %@", album.title);
}

The nice thing about this code that there are no implementation details which bleed through. We could change the backend to an SQLite store and the code won’t break. Additionally, the code is more type-safe: if we mistype a property name (for example, discograhpy instead of discography) we get a compiler error.

Next steps / Problems

Because this is all very new and not too well documented, I might have made a couple of mistakes. I would be really interested in hearing about it if you do spot something, and will update this post accordingly.

All the API calls and Core Data calls are done in a synchronous way. This is not a good idea in production code, as it will block the main thread. I’m experimenting myself with how to deal with that, and don’t have a single answer yet. The comments on Drew’s article are really helpful.

Finally, we implemented a readonly API. By implementing some more things in our NSIncrementalStore subclass we can add support for changing, saving, deleting and creating objects.

I can imagine it would be really interesting to write a subclass of NSIncrementalStore that can deal with CouchDB or Parse. Implementing a backend would then be as simple as defining your CoreData data model and initializing the class and you’re up and running.

This is the first long technical blogpost I’ve written, and I would love to hear your thoughts on it. Especially parts that are not clear or written in a bad way. Please email me with your thoughts.

Running the example code

To run the example code, clone the project from github and open it in XCode. I just used a standard iOS template, and pressing ‘Run’ will not do much. The documentation is in the tests: open IncrementalStoreTestTests.m to see how to use the code. You can run the tests by pressing Cmd+U or Product > Test.

References

Project on Github
SealedAbstract
NSIncrementalStore Programming Guide
NSIncrementalStore Class Reference
Core Data talks
Core Data Programming Guide



I Use This

I was inspired by people writing about their setup on The Setup, so I decided to write about my own setup.

Hardware

I use a Mid 2010 Macbook Pro 15” with an SSD. I only switched last week, when my old hard drive was about to die. I don’t use any external screen, keyboard or mouse. Because I travel a lot and work from pretty much everywhere, the HD display is really convenient. I would like a more lightweight laptop, but don’t feel like compromising on the screen size.

I also carry around an iPhone, and a very basic A4 note taking pad. The last one is essential, I like taking notes and doing sketching on real paper. I own an iPad, but don’t use it that much (mostly for development). For reading, I have a Kindle (which is probably the device I treasure the most).

Development

Being an iOS developer, I spend most of my working hours in XCode. It’s not the most advanced IDE, but I think it’s important to stay with the tools Apple provides. To be fair, I don’t hate it either. And it keeps getting better with every release, although it lacks features that were in Eclipse many years ago.

When I’m not doing iOS development, I use MacVim. I write all my other code in it, and do lots of text editing in it (this blog post is written in MacVim). I started learning Emacs as well, but the many years of learning vim make me feel slow in any other editor.

For versioning, I only use Git. I do think it has a bad interface and often have problems remembering the exact commands. One of the things that did help me understand git a bit better was the chapter on The Git Object Model in the git community book.

I recently changed my default shell from bash to zsh, and I really like it. I installed Oh My Zsh which provides you with awesome defaults and plugins. (Thanks for pointing it out to me Maik).

Other

For browsing, I use Chrome. I like the speed and minimalism of it.

For note taking, I use Evernote. It works on my Mac, iPhone and iPad. And it’s really easy to have shared notebooks. Although I can definitely imagine a better product, it does the job.

Another important app for me is OmniFocus. I practice GTD and OmniFocus helps me keep my current projects and next actions organized. Any other GTD stuff I have is in Evernote.

Wallet takes care of my passwords. It integrates nicely with Chrome, so I never have to remember all my different passwords.

I used to run a mail server with my friends, but switched to GMail last year. I think Google probably takes better care of my data than I could do myself. I wouldn’t mind switching to a better, paid service which gives me good guarantees about my data.

Most people don’t talk about this, but for exploring darker corners of the internet, I use the VPN service by IPredator. It gives me at least some sense of security.

Backups

For backups, I put a lot of stuff in my Dropbox folder. Also, I have Backblaze installed which backs everything up online. And finally, I have an external hard drive for Time Machine backups.



Becoming a better programmer: Test-Driven Development

As I’m enjoying programming again (and more so every week), I started to think consciously again about improving myself as a programmer. After a recommendation of breun I started reading Clean Code, which I can definitely recommend.

One of the things I have always felt bad about professionally is not doing Test-Driven Development. Back when I was programming Haskell a lot I did do a lot of type-driven development, which overlaps a bit: you write down your types and then try to implement it. However, in Objective-C the type system isn’t of much use (compared to Haskell), so I really had to do it: write tests.

Another really big motivation is that I took over a project from another developer. The project was huge, and there were no tests at all. I ended up breaking things multiple times without realizing it (and I only found out after deploying it to the testers). I pledged to myself to do better than that.

Before I set off to write some tests, I had to find a testing framework. There is a great community wiki on StackOverflow called iOS Tests/Specs TDD/BDD and Integration & Acceptance Testing, but it’s also a bit overwhelming.

I started out by using the built-in Unit Testing framework that’s provided by Apple (see: XCode Unit Testing Guide). However, writing unit tests is quite hard for GUI code. As I wanted to test a new UIView subclass that I was developing, I had to test things such as tapping and inspecting other views.

Then I stumbled upon KIF (again, after a recommendation from breun), an open source integration testing framework by Square. As advertised, it was easy to configure and set up, and I was writing tests very soon after that. For example, here’s a scenario that tests whether my view closes, and even when you haven’t used KIF before you can understand exactly what it does:

KIFTestScenario *scenario = [KIFTestScenario scenarioWithDescription:@"Test that the picker closes when tapped outside"];

[scenario addStep:[KIFTestStep stepToReset]];
[scenario addStep:[KIFTestStep stepToAppear]];
[scenario addStep:[KIFTestStep stepToWaitForViewWithAccessibilityLabel:pickerAccessibilityLabel]];
[scenario addStep:[KIFTestStep stepToTapViewWithAccessibilityLabel:@"Tap to close"]];
[scenario addStep:[KIFTestStep stepToWaitForAbsenceOfViewWithAccessibilityLabel:pickerAccessibilityLabel]];

So far, I’m quite happy with the combination of KIF and the built-in unit testing. I’ll try to apply it to more of my projects, and probably be a better programmer because of that. At least when other people will read my code they can use the tests as documentation to see what the code should do.



I am thinking of building another “personal commitment device”, as Daniel Goldstein calls it. Interesting video (but a bit meta) on the relation between present self and future self. Thanks to Erik for telling me about this video.