Delighting your users with SBJson 4

Delighting your users with SBJson 4

An argument for using event-based JSON parsing to improve user experience when downloading lists of records.

****

How many here have written an app that downloads a list of records? Raise your hands please! Do you cut page size to reduce latency, at cost of connection overhead more times?

Slide 3: Red arrows is “download one record”, blue is “parse one record”. With the traditional approach, your app won’t get hold of any of the result until all has been downloaded & parsed.

Slide 4: Notice there’s no difference in when we get the last record; only in how early we get the first one. I’ve called it “chunked delivery” because it resonates with HTTP Chunks. Twitter, for example, lets your subscribe to a HTTP chunked stream of JSON documents.

Slide 5: Because downloads are multi-core we actually continue downloading without waiting for the parser to do its thing. Thus, at the end of the complete download you only have to wait for the parser to parse the last record.

Slide 6: This is a somewhat silly and slightly misleading chart. Best you skip it and move on to the rest of the slides, grasshopper! I will change it if I do thin talk again.

Slide 10-11: Note that the input contains multiple top-level JSON documents concatenated together. (Assume the `data` function just creates an NSData instance containing UTF8 encoded data of the characters we pass it.) We can call the parse: method multiple times Twitter uses this style of streaming. Essentially each HTTP Chunk contains a complete JSON document.

Slide 14-15: In contrast to the previous example this input contains a single top-level document. The outer array is gray here.

Slide 16: In version 3 I supported unwrapping of objects as well, but it leaked keys. I also supported arbitrary-depth documents, but I removed it in the interest of clarity.

Slide 17: Normally a JSON parser would parse and validate a complete document, and would refuse to parse the above. However, that forces you to have seen the entire document before parsing so when parsing streams we have to relax that requirement.
The multi-root parser would still balk at the above, but the root unwrapping parser will call your value block twice, then call your error handler saying the next bit cannot be parsed.

Slide 18: The value block will be called once for each “chunk” of JSON we encounter. This will either be a top-level document (multi-root) or a first-level sub-document (unwrapping root array).

Slide 19: The -parse: method returns a status, which can be handy. An unwrap-root array parser will return …Complete when its root array is closed, for example. Or any parser will return …Stopped if you set the *stop argument in the value block to YES.

F83cfcc2713f34cf9db3f6488383cee1?s=128

Stig Brautaset

January 17, 2014
Tweet

Transcript

  1. Delighting your users with SBJson 4 Stig Brautaset @stigbra ~

    github.com/stig
  2. What’s more important to you? Time until your users see

    the last record? Or until they see the first? ! Do you cut page size to reduce the wait?
  3. Download-then-parse Time to first record Time to last record

  4. Chunked delivery Time to first record Time to last record

  5. Multi-core chunked delivery Time to first record Time to last

    record
  6. Relative time to first record 0 250 500 750 1000

    1 record 10 records 100 records 1000 records Chunked delivery Download-then-parse
  7. NSJSONSerialisation • Supports NSStream! • But can’t do chunked delivery.

    • We can do better!
  8. SBJson • Chunked delivery since version 3 (in June 2011)

    • But it was confusing and cumbersome to use… • …with multiple delegates required
  9. SBJson 4 • Focus on chunked delivery • Introduces a

    block-based interface • Drops category & OO interfaces
  10. Multi-root JSON id parser = [SBJson4Parser multiRootParserWithBlock:block errorHandler:eh]; ! !

    [parser parse:data("[]{")]; // calls block(@[]) ! [parser parse:data("}[]"]; // next part of stream // calls block(@{}) // calls block(@[])
  11. Multi-root JSON id parser = [SBJson4Parser multiRootParserWithBlock:block errorHandler:eh]; ! !

    [parser parse:data("[]{")]; // calls block(@[]) ! [parser parse:data("}[]"]; // next part of stream // calls block(@{}) // calls block(@[])
  12. Use with custom
 NSURLSessionDataDelegate - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dt didReceiveData:(NSData

    *)data { ! switch ([parser parse: data]) { // handle various returns here… } }
  13. –Me, early 2011 “But what if I am consuming a

    3rd-party API and they just give me a long list?”
  14. Unwrap the root array! id parser = [SBJson4Parser unwrapRootArrayParserWithBlock:block errorHandler:eh];

    ! [parser parse:data("[[],{")]; // calls block(@[]) ! [parser parse:data("},true]")]; // calls block(@{}) // calls block(@(YES))
  15. Unwrap the root array! id parser = [SBJson4Parser unwrapRootArrayParserWithBlock:block errorHandler:eh];

    ! [parser parse:data("[[],{")]; // calls block(@[]) ! [parser parse:data("},true]")]; // calls block(@{}) // calls block(@(YES))
  16. Unwrapping is for root-level arrays only!

  17. Compromise: Corrupted / incomplete JSON [ {}, [], ***###$#”>P !

    ! ! block(@{}) block(@[]) errorHandler(…)
  18. SBJson’s Blocks SBJson4ValueBlock block = ^(id obj, BOOL *stop) {

    if (some_condition) { *stop = YES; } else { // do something with obj } }; ! ! SBJson4ErrorBlock eh = ^(NSError *err) { NSLog(@"OOPS: %@", err); };
  19. SBJson4ParserStatus switch ([parser parse:data]) { case SBJson4ParserStopped: case SBJson4ParserComplete: NSLog(“Done!");

    case SBJson4ParserWaitingForData: break; case SBJson4ParserError: NSLog(@"Ouch, I'm hurt!"); break; }
  20. Two CocoaPods! To allow both version 3 and 4 in

    the same application.