David Janes' Code Weblog

November 10, 2013

Pretty Printing JSON in Objective-C

code fragments,iphone,json,objective-c · admin · 2:23 pm ·

Apple’s JSON serializer NSJSONSerialization – despite having years to get it right – makes a horrible hash of pretty printing JSON. It does not sort the keys and it puts backslashes in front of all slashes.

SBJson does a much nicer job of this. Here’s a code fragment that will use SBJson if you’ve linked to it, otherwise it will fall back to Apple’s code.

+ (NSString*) formatJSONPretty:(id)_obj
{
    /*
     *  If we are linked with SBJsonWriter, we use those libraries as
     *  it has a much nicer formatter
     */
    id writer = [[NSClassFromString(@"SBJsonWriter") alloc] init];
    if (writer) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [writer performSelector:NSSelectorFromString(@"setHumanReadable:") withObject:@YES];
        [writer performSelector:NSSelectorFromString(@"setSortKeys:") withObject:@YES];
        return [writer performSelector:NSSelectorFromString(@"stringWithObject:") withObject:_obj];
#pragma clang diagnostic pop
    }

    NSData* d = [NSJSONSerialization dataWithJSONObject:_obj options:NSJSONWritingPrettyPrinted error:nil];
    return [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
}

April 8, 2012

NSURLConnection, ETag and caching

code fragments,iphone,tips · admin · 7:40 am ·

If you’re using NSURLConnection and using If-None-Match/ETag and you’re depending on getting a 304 status to determine that “nothing changed”, make sure you do this:

[request setCachePolicy:NSURLRequestReloadIgnoringCacheData];

If you don’t, after your first request/response cycle, NSURLConnection will start returning status 200 instead as it’s pulling the result from its local cache.

October 23, 2011

Dynamically resize and stack many UIViews

code fragments,iphone · admin · 5:58 pm ·

The iOS’s UILabel is kind of an annoying beast – out-of-the-box it takes coding to get the correct height or to “top justify” it. If there’s too much text inside a UILabel, sometimes it shrinks the text which visually doesn’t make it look that great – the ideal solution would be to split the text over multiple lines and adjust everything else downward dynamically. Also if you have to have multiple labels with different styles a typical solution would be to use a UIWebView controller. However, this isn’t really suitable inside a UITableView where you have to have many views and you need to know the exact space to allocate.

Here’s a helper function that will take an Array of UIViews and stack them up, returning the height. If any of the UIViews are UILabels, it’ll resize them to the appropriate height.

/*
 *  Stack views on top of each other starting at a point.
 *  If they are labels, we resize. Return the height used.
 */
+ (CGFloat) uiStack:(NSArray*)_views startingAt:(CGPoint)_point
{
  if (!_views || !_views.count) {
    return  0;
  }

  for (UIView* view in _views) {
    if ([view isKindOfClass:[UILabel class]]) {
      UILabel* label = (UILabel*) view;
      CGSize labelSize = [label.text
                sizeWithFont:label.font
                constrainedToSize:CGSizeMake(label.frame.size.width, 1000.0) 
                lineBreakMode:label.lineBreakMode];
      
      label.frame = CGRectMake(_point.x, _point.y, view.frame.size.width, labelSize.height);
      _point.y += labelSize.height;
    } else {
      view.frame = CGRectMake(_point.x, _point.y, view.frame.size.width, view.frame.size.height);
      _point.y += view.frame.size.height;
    }
  }
  
  return _point.y;
}

Enjoy!

April 9, 2010

Djolt-obj: Django-like templates for Objective-C

code fragments,djolt,iphone · David Janes · 7:51 am ·

My company, Discover Anywhere Mobile, has open sourced a software component that many iPhone developers may be interested in: Djolt (Django-like Templates) for Objective C. The objective of this project is to be able to take NSObjects – particularly NSDictionary, NSArray, NSString and NSNumber – and be able to feed them to a text template, Django style, and get text output. This will be fantastic for working with APIs that deliver data via JSON or XML.

It’s dependent on RegexKit library for now, though if the iPhone OS 4 NSRegularExpression is powerful enough we may transition to that.

The code is being tested against the Django template test cases, with the goal of having as complete as possible (given different language idioms) compatibility. We only cover a small subset of Django yet – the stuff we need – but if you’re interested in working on the project, please by all means join in.

Code

NSDictionary* d = [[NSData dataWithContentsOfFile:@"test.json"] yajl_JSON];

DjoltTemplate* t = [[DjoltTemplate alloc] initWithTemplateFile:@"test.djolt"];
NSString* r = [t render:d];

test.json

{
 "title" : "Test 1",
 "items" : [
  {
   "href" : "http://www.example.com/1",
   "title" : "Example 1"
  },
  {
   "href" : "http://www.example.com/2",
   "title" : "Example 2 & Example \"2.5\""
  },
  {
   "href" : "http://www.example.com/3",
   "title" : "Example 3"
  }
 ]
}

test.djolt

<html>
<head>{{ title }}</head>
<body>
{% if fred or items or bleck %}
<ul>
{% for itemd in items %}
<li><a href='{{ itemd.href }}'>{{ itemd.title|safe }}</a> ({{ forloop.counter }})</li>
{% endfor %}
</ul>
{% endif %}
<p>
{{ nothing|default:"something" }}
</p>
</body>
</html>

Output

<html>
<head>Test 1</head>
<body>

<ul>

<li><a href='http://www.example.com/1'>Example 1</a> (1)</li>

<li><a href='http://www.example.com/2'>Example 2 & Example "2.5"</a> (2)</li>

<li><a href='http://www.example.com/3'>Example 3</a> (3)</li>

</ul>

<p>
something
</p>
</body>
</html>

April 3, 2010

Optimizing JSON database loading on an iPod Touch

iphone · David Janes · 3:13 pm ·

This is the story of how we sped up loading of our application’s database on a 1 generation iPod Touch from 19.28 seconds to 8.75 seconds. We test all our applications of the oldest hardware in use out there to get a sense of what our “floor” performance will be – generally if it’s bearable there, it’ll be better to awesome on a newer iPhone.

The Battle Plan

Our current database schema/code is called the “B” database. It loads from a JSON file (actually several, but only one counts) which is a list of dictionary. Each dictionary represents a location, listing, event, coupon or whatever in our application.

To speed up loading times, we decided to create a “C” database with the following characteristics:

  • still load from a JSON file
  • empty values would be removed from the database
  • default values – e.g. “country_name” : “Canada”, where the country is always Canada – would be removed
  • we would “lazy” load most of the data
    • the database would only contain the ID for each item, the lat/lon and a few other essential items
    • a separate JSON file for each location would be made also, containing all the data
    • because we well encapsulate access to our data objects (no peeking inside of the dictionary that holds all the data!), when the lazy data was needed we’d load it on the spot

We ended up only doing the the first two of these options. We’re likely to do the lazy loading in the future, but because this would require implementing a proper search (because search looks at every record) we decided to defer to another release. Our testing shows that this will shave about another 25% of load time, which at this point wasn’t worth the trouble but may be later when we start getting into 2500+ items to load.

Initially we planned also to do the following, but after learning that most of the work was in object creation and not in data loading, we decided not to pursue this

  • well known keys would be replaced by a single letter (i.e. “title” with “T”)
  • hash keys, represented as a 32 byte hex string, would be replaced by 20 character base64 strings

Because it turns out that object allocation and messing around with dictionaries is relatively expensive, and loading strings into memory not so much, we probably will never do these.

Performance Numbers

All times in seconds, measured from the start of loading

B database starting point
  • loading data from disk: 0.41
  • finishing converting JSON data to NSObjects: 11.34
  • finished wrapping NSObjects into Location objects: 19.28

We learned here that we have two obvious problems: converting JSON and creating the Location objects.

Use C database

The C database JSON file is about 20% of the size of the B database.

  • loading data from disk: 0.08
  • finishing converting JSON data to NSObjects: 5.56
  • finished wrapping NSObjects into Location objects: 13.30

Already we’ve seen a big improvement of almost 6 seconds.

Turn off slideshow

Here we just turned off the slideshow to see what would happen

  • loading data from disk:0.05
  • finishing converting JSON data to NSObjects: 4.42
  • finished wrapping NSObjects into Location objects: 11.57

Almost two seconds saved! Our new code now doesn’t start the animation (or in fact, any other non-critical background task) until the DB is loaded. For the most we can say that 4.42s is the floor for loading a database from disk using JSON – it really isn’t going to get smaller than this.

Slightly optimized B database, better Location constructor

There’s a lot of steps I’ve left out here – I just turned off the Location “init” function altogether, found that things worked at almost the speed shown below. Looking at the code, I discovered a totally unnecessary dictionary copy, a couple of computations that could be done lazily, and some unnecessary initializations. After that was all done, we’ve got the the Location creating step down to about 1.1 seconds – originally it was almost 8!

  • loading data from disk: 0.40
  • finishing converting JSON data to NSObjects: 7.6
  • finished wrapping NSObjects into Location objects: 8.76

Note that we we lost 3 seconds (4.42 to 7.6) going back to the B database. However, this saved us another long programming and testing stint, so we’ll let this ride for now. However, we know that we can get this down to about 25% of the original speed when we need to.

Conclusions

  • Look for things inside of loops – the costs add up quickly
  • Object creation isn’t free, and object copying is expensive
  • Don’t make assumptions whether you’re CPU bound or IO bound – test and find out
  • Where you’re CPU bound – don’t do other stuff (e.g. animation cuteness) that’s competing for the CPU!
  • Where you’re IO bound – make things smaller (duh)
  • we’re likely to do some experimenting with SQLdb in the future, but given that loading isn’t really the problem, it probably won’t make much difference
  • CoreData didn’t exist when we started this application, so we’re not even looking at that
  • On an iPhone 3GS the complete database loads in about 3.5 seconds, which is sweet enough

March 1, 2010

Key Preserving YAJL

code fragments,iphone · David Janes · 8:35 am ·

Of all the JSON parsing libraries for the iPhone, the one I like the most is YAJL-obj, which is based on the “event driven/SAX style” YAJL. When I first started creating iPhone applications, I used a lot of Plists; I’ve now switched this all over to JSON not only because it’s more standards-y, but also because I can use the same data sets with my Python/Django and Android applications.

The JSON data I’m working with looks something like a dump of a SQL Table: lots of rows using the same column names — i.e. dictionary keys – over and over. If you’re loading a couple of thousand rows of data, each with 20 or or 30 columns/keys, the shear size of the key strings can add up — even though really there’s only a couple of dozen keys in total really in use. All these duplications waste a lot of memory – which is especially tight on a 8Gb iPod Touch – so I decided it might help out to actually reuse keys in my JSON loaders.

Here’s the changes I made to YAJL:

YAJLDocument.h

@interface YAJLDocument : NSObject  {
...
    NSMutableDictionary *keyStore_;
}
@property(nonatomic,retain) NSMutableDictionary *keyStore_;

YAJLDocument.m

- (id)initWithParserOptions:(YAJLParserOptions)parserOptions {
	if ((self = [super init])) {
...
		keyStore_ = [[NSMutableDictionary alloc] initWithCapacity:16];
	}
	return self;
}

- (void)parser:(YAJLParser *)parser didMapKey:(NSString *)key {
#if 1 // DPJ 2010-03-01
	key_ = [keyStore_ objectForKey:key];
	if (!key_) {
		[keyStore_ setObject:key forKey:key];
		key_ = key;
	}
#else
	key_ = key;
#endif
	[keyStack_ addObject:key_]; // Push
}

To rebuild your library:

  • select build target Device – 3.0 | Release | Combine Libs
  • the output file is Project-IPhone/build/libYAJLIPhone-*.zip
  • just add all the things in that ZIP file to your iPhone project and you’re good to go

Final thoughts:

  • whether this happens or not should be controlled by the YAJLParserOptions
  • I’m probably going keep modifying this code, adding a version with a “scrubbing delegate” that allows dictionaries to be scrubbed on the spot of data not needed in the application

November 28, 2009

UIImage imageNamed: and caching

code fragments,iphone · David Janes · 7:07 am ·

There may be some temptation to improve caching for UIImage imageNamed:

static UIImage* starred = nil;
if (starred == nil) starred = [UIImage imageNamed:@"starred.png"];

Don’t do this, it’s really stupid. It’s already being held by a cache, and maybe dropped out of it at any time, leading to miraculous crashes.

November 8, 2009

Next Generation iPhone to include RFID reader?

ideas,iphone · David Janes · 5:38 am ·

This was something I was hoping would be in the 3GS. Rumor: Next generation iPhone to be RFID enabled:

A highly reliable source has informed me that Apple has built some prototypes of the next gen iPhone with an RFID reader built in and they have seen it in action. So its not full NFC but its a start for real service discovery and I’m told that the reaction was very positive that we can expect this in the next gen iPhone.

If Apple does it, expect every phone manufacturer and their sister to begin pumping out NFC enabled phones, at least for service discovery and sync.

This just reinforces what we knew based on the two separate patents Apple submitted that had the iPhone enabled to read RFID tags. I’m told that the touch project video and the BT SIG’s specs were all driving forces to push this forward as well as other factors.

Guess I’ll be touching my iPhone to my Mac to link them together to sync iTunes by next year.

My thoughts:

  • this will be a game changer for RFID, bringing applications down to the small business and hobbyist layer
  • RFID + applications is a wicked combination, making the mobile phone an Anything Device; Microsoft, Motorola and RIM should all try to get the jump on this
  • RFID + in app purchase is a wicked combination; put your thinking cap on for this one
  • RFID + Augmented Reality makes context sensitive layers start popping up
  • I’m not a game person, but you know there’s got to be gaming implications for this
  • what are the implications for big chains — groceries, consumer electronics — when anyone can walk in the door and get a better price buy waving their phone at a shelf

September 26, 2009

UIPageControl + UIScrollView

code fragments,iphone,tips · David Janes · 7:58 am ·

This is an example of how to combine a UIPageControl and a UIScrollView together to create a “snap to page”-like effect that is seen on the iPhone’s home screen. This sample is partially based on Apple’s UIPageControl example.

XCode Sample Project

June 27, 2009

How iPhone OS 3.0 delivers location services

iphone,maps · David Janes · 6:38 am ·

Alec Saunders has a seriously cool article on his blog about how your iPhone knows where it is. I got to experience how well this works yesterday when we used TimmyMe in the McCowan Road/Ellesmere Avenue neighborhood to grab a coffee pre-meeting:

Many folks assume that iPhone’s location services are GPS based. In fact, they’re not. [...]

Skyhook’s innovation is to augment that with WiFi hotspot locations. Why WiFi? It can be wonderfully accurate, to within 30 to 60 feet, or 10 to 20m which is roughly the same as GPS. WiFi can establish a fix within less than a second. And, WiFi is not vulnerable to overhead obstructions, so it can be used indoors.

Skyhook’s technology relies on a database of WiFi access points in over 2,000 cities (and growing). Much like Google, they use vehicles to drive cities, and using a laptop in the vehicle, they chart the location of WiFi access points. In addition, on iPhone as users use the mapping application, new locations and newly discovered WiFi access points are automatically added to the database. It’s a kind of automated crowdsourcing, based on usage.

My emphasis on that last sentence. Very cool.

Older Posts »

Powered by WordPress

Switch to our mobile site