vombat
vombat
My Web Log Book
36 posts
Don't wanna be here? Send us removal request.
vombat · 11 years ago
Text
super size me
A little gem I found in one of the code bases.
CGSize fittingSize = [super sizeThatFits:size];
3 notes · View notes
vombat · 11 years ago
Text
Core Data gets batch updates!
This had been a performance pain point with Core Data when you want to update thousands of objects and change a single property. An example given in this article points out a fairly typical use-case for RSS readers. If you want to update thousands of objects and set the read property to YES, then you'd have to load all those objects in memory, perform the change, and then save each object. Well, I hope that's not true anymore with the new NSBatchUpdateRequest class. Here's what the docs say about it:
// Used to request that Core Data do a batch update of data in a persistent store without // loading any data into memory. May not be supported by all store types. // WARNING: // It is up to the developer creating the request to ensure that changes made by the request to // the underlying store do not violate any validation rules specified in the model.
3 notes · View notes
vombat · 11 years ago
Text
NSOperation.threadPriority getting deprecated in favor of a more abstract NSQualityOfService
Seems like Apple is trying to get away from the concept of threads, or at least shield it completely from their external APIs. Go Apple!
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0); @property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
3 notes · View notes
vombat · 11 years ago
Text
Caching audio streamed using AVPlayer
AVPlayer is quite convenient for playing audio/video files from the disk or the network. However, you may want to cache the audio that was played by AVPlayer but it is not straightforward. A good alternative for this used to be the AudioStreamer library written by Matt Gallagher. I've been using the library for sometime now, and although it's a fantastic library, there are some quirks that I've had to deal with especially around race conditions that are hard to reproduce and an 'hwiu' exception. Nonetheless, this library had served us well. Moving on..
I was looking for solutions to get AVPlayer to play an audio file from the network but also cache the downloaded data so the next time I can play it locally. Unfortunately that's not a very straightforward thing to do. I found a couple of helpful hints that tapped into the audio processing and re-recorded the stream as it was being played back by the system. This article from venodesigns.net had been tremendously helpful in implementing that. It worked great for the most part but it was unsuited for my purposes because I wanted the entire audio cached without interruptions. If the user seeked anywhere in the audio, my cached audio had gaps in it.
So I started looking around digging through the APIs and came across the resourceLoader object in AVURLAsset. This is actually an amazing API using which you can provide controlled access to a remote audio file to AVPlayer. This works like a local HTTP proxy but without all the hassles.
The most import thing to remember about this resourceLoader is that it will only come into play when AVPlayer does NOT know how to load a resource. It is quite equipped to deal with the "http" protocol so as long as you have http(s) URLs, no callbacks will be made. The trick here is to change the protocol so AVPlayer is forced to defer the loading of the resource to our application. The resource loader has two methods we need to deal with. We'll store the requests that are still pending in an array called pendingRequests. Then we'll start off an NSURLConnection to fetch the audio file, and as and when we retrieve more data, we will try to process the pendingRequests, if possible. The two delegate methods of AVAssetResourceLoaderDelegate we have to implement are:
// Called when the resource loader needs data or information about the resource. To take over the resource loading, we'll return YES from this method. - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest // Called when a load request is being cancelled. - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
To start off, we'll create an AVURLAsset object, change the URLs protocol to a custom one, and set the resource loader's delegate to be our class.
// Change protocol to streaming from http AVURLAsset *asset = [AVURLAsset URLAssetWithURL:@"streaming://sampleswap.org/mp3/artist/earthling/Chuck-Silva_Ninety-Nine-Percent-320.mp3"]; // Set resource loader on the asset so that we can control the loading process [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; // This tracks all pending AVAssetResourceLoadingRequest objects we have not fulfilled yet self.pendingRequests = [NSMutableArray array]; AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset]; self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem]; [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:NULL];
The delegate methods are basically responsible for adding and removing a request from the pendingRequests array. When the first request is received and no NSURLConnection exists, we'll create one and start fetching the audio file.
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { if (self.connection == nil) { NSURL *interceptedURL = [loadingRequest.request URL]; NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:interceptedURL resolvingAgainstBaseURL:NO]; actualURLComponents.scheme = @"http"; NSURLRequest *request = [NSURLRequest requestWithURL:[actualURLComponents URL]]; self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; [self.connection start]; } [self.pendingRequests addObject:loadingRequest]; return YES; } - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { [self.pendingRequests removeObject:loadingRequest]; }
The NSURLConnection delegate callbacks try to process any pendingRequests in all callbacks.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { self.songData = [NSMutableData data]; self.response = (NSHTTPURLResponse *)response; [self processPendingRequests]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.songData appendData:data]; [self processPendingRequests]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [self processPendingRequests]; // Done loading, cache the file locally NSString *cachedFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cached.mp3"]; [self.songData writeToFile:cachedFilePath atomically:YES]; }
The request handling is the crucial part. An AVAssetResourceLoadingRequest has two parts to it - contentInformationRequest, and dataRequest. A contentInformationRequest is a request to identify the content length, content type, and whether the resource supports byte range requests. With byte range requests, AVPlayer can get fancy and apply various optimizations. The first request I've seen asks for the first two bytes of data. Here is an implementation of the request handling part:
- (void)processPendingRequests { NSMutableArray *requestsCompleted = [NSMutableArray array]; for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests) { [self fillInContentInformation:loadingRequest.contentInformationRequest]; BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; if (didRespondCompletely) { [requestsCompleted addObject:loadingRequest]; [loadingRequest finishLoading]; } } [self.pendingRequests removeObjectsInArray:requestsCompleted]; } - (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest { if (contentInformationRequest == nil || self.response == nil) { return; } NSString *mimeType = [self.response MIMEType]; CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); contentInformationRequest.byteRangeAccessSupported = YES; contentInformationRequest.contentType = CFBridgingRelease(contentType); contentInformationRequest.contentLength = [self.response expectedContentLength]; } - (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest { long long startOffset = dataRequest.requestedOffset; if (dataRequest.currentOffset != 0) { startOffset = dataRequest.currentOffset; } // Don't have any data at all for this request if (self.songData.length < startOffset) { return NO; } // This is the total data we have from startOffset to whatever has been downloaded so far NSUInteger unreadBytes = self.songData.length - (NSUInteger)startOffset; // Respond with whatever is available if we can't satisfy the request fully yet NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes); [dataRequest respondWithData:[self.songData subdataWithRange:NSMakeRange((NSUInteger)startOffset, numberOfBytesToRespondWith)]]; long long endOffset = startOffset + dataRequest.requestedLength; BOOL didRespondFully = self.songData.length >= endOffset; return didRespondFully; }
Link to the full gist.
4 notes · View notes
vombat · 11 years ago
Text
Simple tip for improving error handling in your Objective-C app
Objective-C prefers error objects in-place of exceptions most of the times. That error will get populated with a valid pointer if something failed, otherwise it will be nil. If you don't care about the error, you can just pass in nil in almost all APIs. However, if you are working on a large codebase or with a team, or if you want to write a good robust app, you should handle all errors meticulously. Once convention I've come up with is to force the callers to pass in a valid pointer instead of NULL. It's as simple as putting some asserts. Here's what I do when I want to force the callers to take care of error checking.
Next step is to ensure that the caller actually handles the error or bubbles it up appropriately. That's a whole different game, but at least we get the ball rolling this way.
- (BOOL)fetchProfilePictureWithError:(NSError * __autoreleasing *)error { NSParameterAssert(error != NULL); NSParameterAssert(*error == nil); // do the fetch }
It sounds very heavy-handed but my experience says it's better to enforce error handling sooner than later and this pattern has helped quite a bit.
0 notes
vombat · 11 years ago
Text
Synchronizing around a class
A common technique I've been using to synchronize access to class methods is to wrap a piece of code that needs synchronizing with an @synchronized block. For example, say we have an Animal class that has a class method +totalPopulation, I would've synchronize it as:
@interface Animal : NSObject + (NSUInteger)totalPopulation; @end @implementation + (NSUInteger)totalPopulation { @synchronized (self) { // do something fancy to figure out the // total population of all animals } } @end
The problem starts when there are subclasses of Animal and we call +totalPopulation from Animal, and a subclass in different threads simultaneously. Both methods would be allowed to run at the same time because the lock is held using two different objects.
@interface Dog : Animal @end @implementation Dog @end
So +totalPopulation is no longer thread-safe when we call [Dog totalPopulation] and [Animal totalPopulation]. The fix is to ensure that we hold the lock using a specific object. So in this case instead of using self, we would specify the class object on which we wish to lock.
+ (NSUInteger)totalPopulation { @synchronized ([Animal class]) { // much better } }
Lessons learnt: Be as specific as possible when locking methods.
5 notes · View notes
vombat · 11 years ago
Text
NSCalendar is always looking out for you
Just got this message from calling -[NSCalendar components:fromDate:toDate:options:]
2014-04-16 19:34:12.319 App[6140:3c0b] *** -[__NSCFCalendar components:fromDate:toDate:options:]: fromDate cannot be nil
I mean really, what do you think that operation is supposed to mean with a nil fromDate?
An exception has been avoided for now.
A few of these errors are going to be reported with this complaint, then further violations will simply silently do whatever random thing results from the nil.
Here is the backtrace where this occurred this time (some frames may be missing due to compiler optimizations):
( 0 CoreFoundation 0x33e8548f + 86 1 App 0x00153825 by me 2 App 0x00155ec9 by me 3 libdispatch.dylib 0x3bf7411f + 10 4 libdispatch.dylib 0x3bf7399b + 146 5 libdispatch.dylib 0x3bf73895 + 36 6 libdispatch.dylib 0x3bf82215 + 192 7 libdispatch.dylib 0x3bf823b9 + 84 8 libsystem_c.dylib 0x3bfa8a11 + 360 9 libsystem_c.dylib 0x3bfa88a4 start_wqthread + 8)
0 notes
vombat · 11 years ago
Text
Objective-C humor
I came across a bug in my application today where the -main method was overridden in a subclass but the superclass implementation was not being called. The solution was straightforward though. Thank you [super main].
1 note · View note
vombat · 12 years ago
Text
Protected stuff for Objective-C subclasses
That's a good name for protected extensions for a subclass that I came across in UIGestureRecognizerSubclass.h
@interface UIGestureRecognizer (ForSubclassEyesOnly) …
0 notes
vombat · 12 years ago
Text
Date parsing performance on iOS (NSDateFormatter vs sqlite)
Many web services choose to return dates in something other than a unix timestamp (unfortunately).
Recently I set out trying to identify performance bottlenecks with a large import operation in our iOS app. Having tweaked most of the variables, I was surprised to find out that date parsing was actually one of the biggest bottlenecks. The date parsing was done using NSDateFormatter using an ISO-8601 formatted date that looked like this:
2013-09-07T23:45:00Z
That looks simple enough. We had the NSDateFormatter's format setup with a timezone of +0 GMT, and everything was great, expect parsing dates like this was consuming around 20% of the entire import operation. To provide some context, we were testing the import performance by importing roughly 250,000 objects into a SQLite database, and each object had 4 dates associated with it. That meant that we were dealing with a million dates, and parsing a million dates can get expensive.
Almost all of that time was being spent inside NSDateFormatter's dateFromString: method, so there was not much we could do to optimize things ourselves.
The main goal was to get a unix timestamp from the ISO date. Luckily for us, SQLite is quite good at parsing some ISO-8601 dates and works blazingly fast. Here's how SQLite can parse the above date:
sqlite> SELECT strftime("%s", "2013-09-07T23:45:00Z"); 1378597500
Let's convert this to use SQLite's C library.
sqlite3_stmt *statement = NULL; sqlite3_prepare_v2(db, "SELECT strftime('%s', ?);", -1, &statement, NULL); sqlite3_bind_text(statement, 1, [dateString UTF8String], -1, SQLITE_STATIC); sqlite3_step(statement); sqlite3_int64 interval = sqlite3_column_int64(statement, 0); NSDate *date = [NSDate dateWithTimeIntervalSince1970:interval];
Looks ugly, but we're trying to solve a genuine problem here. So, how did this do in a performance run against NSDateFormatter? About 1400% faster. To parse a million randomly generated dates on an iPhone 5 running iOS 7, NSDateFormatter took a whooping 106.27 seconds, while the SQLite version took just 7.02 seconds.
Here's a link to a gist containing the source code used for this comparison.
If you're curious as to what SQLite is doing under the hood, checkout the date related code in SQLite at http://www.sqlite.org/src/doc/trunk/src/date.c. It mentions that the conversion algorithms it is using are from a book named Astronomical Algorithms by Jean Meeus.
23 notes · View notes
vombat · 12 years ago
Quote
95% of the performance problems are our assumptions.
Found this brilliant quote on a Stackoverflow comment. Unfortunately, I can't find a link to the source.
0 notes
vombat · 12 years ago
Text
iOS 7 and Skeumorphism
I'm glad to see Apple take skeuomorphism to the next level in iOS 7. It always felt like what the design community really hated was not skeuomorphism, but the emulation of existing materials like green felt or leather. Both these concepts would appear to be the identical at first sight, and they absolutely are, until we consider the idea that skeuomorphism extends beyond just visual emulation of materials. If the sound made by the Camera app in an iPhone is skeuomorphic because there is no mechanical shutter in an iPhone, then sound can also be skeuomorphic, and the definition of this term must include design elements beyond just visual looks. One concept that spans both space and time and is omnipresent in iOS 7 is depth which seems to be heavily baked in to iOS 7. The depth and the parallax is not just on the home-screen but it's pervasive throughout most elements in the OS. Let's visit the alternative definition of skeuomorphism on Wikipedia before we come back to the parallax. Wikipedia defines it as: "an element of design or structure that serves little or no purpose in the artifact fashioned from the new material but was essential to the object made from the original material" When we talk about a phone or a tablet, the new material is the digital screen, speakers, camera, etc. Parallax is an element of design or structure that serves little or no purpose on this screen. The original material here is perspective in a three dimensional space. By this reasoning, iOS 7 is heavily skeuomorphic, and Apple didn't get rid of skeuomorphism in this version, but instead turned it up a notch. Skeuomorphism is dead. Long live skeuomorphism.
1 note · View note
vombat · 13 years ago
Text
Should we use the contentStretch property or a stretchable UIImage?
This journey began when I started reading up on the View Controller Programming Guide for iOS on Apple's developer portal. This is what the guide recommended for creating stretchable views. Fun exercise.
Tumblr media Tumblr media Tumblr media
1 note · View note
vombat · 14 years ago
Text
Building Dart on Snow Leopard
I was stuck at the unsupported GCC error in building Dart on my Snow Leopard. The fix was simple - to remove the explicit version requirement, and let it use the system default. Hope it doesn't break anything. Unsupported compiler 'GCC 4.2' selected for architecture 'x86_64' Basically we have to clear out all references to GCC 4.2 from GCC_VERSION = 4.2; to GCC_VERSION = "" Since this version has been references in a thousand places, a global search and replace was needed. I am inside the dart/ directory. $ find . -name "*.pbxproj" -print -exec sed -i 's/GCC_VERSION = 4.2/GCC_VERSION = ""/g' {} \; And ** BUILD SUCCEEDED ** Cool!
1 note · View note
vombat · 14 years ago
Text
Responding with non-empty JSON when updating a resource via PUT in Rails 3
Rails 3 responds with an empty object when making PUT requests to update a resource. This could be undesirable for some cases, and certainly is in our case where expect the JSON representation of the modified object as the response.
The quick-fix is easy - we override the default behavior of respond_with using a block that returns forces the response to be the complete JSON object.
respond_with(@program) do |format| format.json { if @program.valid? render json: @program else render json: @program.errors, status: :unprocessable_entity end } end
A neater fix, at least for me, would be in Rails itself inside the Responder class. GET and POST requests already work as expected. For other requests Rails returns an empty response. This is how things look right now
# This is the common behavior for formats associated with APIs, such as :xml and :json. def api_behavior(error) raise error unless resourceful? if get? display resource elsif post? display resource, :status => :created, :location => api_location elsif has_empty_resource_definition? display empty_resource, :status => :ok else head :ok end end
Instead of responding with an empty resource for non GET/POST requests, we could have returned the resource itself for PUT requests as well. Here's how my ideal solution would look like (notice the elsif put? line):
# This is the common behavior for formats associated with APIs, such as :xml and :json. def api_behavior(error) raise error unless resourceful? if get? display resource elsif post? display resource, :status => :created, :location => api_location elsif put? display resource, :status => :ok elsif has_empty_resource_definition? display empty_resource, :status => :ok else head :ok end end
Now what can I do to get this merged in Rails?
100 notes · View notes
vombat · 14 years ago
Text
Linker errors with iOS 5 and libz
So this has happened a few times across different projects where my iOS app failed to compile. The first one was particularly sad because when building a static framework that I include in my work app, because of libz. We were referencing libz.1.2.3 which is apparently not included in iOS 5 SDK. I think it only contains version 1.2.5 now. The worst part was that libtool was failing, and it gave absolutely no clue as to why. The error simply said, libtool failed with error code 1 Bravo. After head-banging for a couple hours and trying random stuff, I removed libz and suddenly the project compiled. In another project, XCode for more helpful and guided me to the error rightaway by uttering some useful errors, ld: library not found for -lz.1.2.3 Lesson learnt. Now I'm just referencing libz.dylib in the project as it is a symbolic link to the actual library and hopefully will always be there when needed.
18 notes · View notes
vombat · 14 years ago
Text
How do you dynamically set AJAX request data with Rails 3.1, jQuery, CoffeeScript, and all that jazz?
Rails 3.1 is awesome. CoffeeScript is great. The jQuery addition is brilliant. But recently I came across a simple problem that took quite a while to figure out and with all these additions being fairly new, there was not much online help available, so I had to turn to the source code.
So what was the problem? How do we dynamically modify the AJAX request data with rails using a link_to. If I was using pure jQuery the solution would be as simple as,
$.ajax('/some/path', { type: 'POST', data: { foo: 'bar' }, success: function() { .. }, error: function() { .. } });
But since I was trying to keep it Railsy and use link_to to create a POST request, there were a few hurdles. Initially, I was trying to use the ajax:beforeSend event callback which was useless for this task, as all form data gets set before this callback is made by Rails. The only use of this callback would be to abort the ajax request altogether if you suddenly discovered something. So I had to use the ajax:before event. But inside the ajax:before callback, we are only passed an event object totally unrelated to ajax, so to set custom ajax data, I had to checkout how any data gets passed to jQuery.ajax by Rails in the first place. Here's the relevant snippet from Rails UJS. Stripped out the form handling part as we're dealing with a link here.
if (rails.fire(element, 'ajax:before')) { if (element.is('form')) { ... } else { method = element.data('method'); url = element.attr('href'); data = element.data('params') || null; } ... }
So Rails looks for custom data inside the params data object of the element triggering the ajax request which is where I had to implant it. The CoffeeScript code for intercepting the ajax request and injecting the data dynamically looked like,
$ -> $('#my_link').bind('ajax:before', -> $(this).data('params', { foo: 'bar' }) )
Or if you don't like CoffeeScript for some reason, here's the equivalent in JavaScript.
$(function() { $('#my_link').bind('ajax:before', function() { $(this).data('params', { foo: 'bar' }); }); });
The corresponding link in rails erb was,
<%= link_to 'Foo', foo_path, method: :post, remote: true, id: 'my_link' %>
This new syntax of creating hashes in Ruby 1.9 is cool and almost similar to JavaScript's object literals.
0 notes