<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4974525831038192874</id><updated>2011-12-21T14:51:45.069-08:00</updated><category term='Tri-Pic'/><category term='Surakarta'/><category term='TheBoard'/><title type='text'>BJ Homer's Blog</title><subtitle type='html'>I'm an iOS developer at Instructure. I write about code and stuff.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>12</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-7960419979624697773</id><published>2011-11-17T07:17:00.001-08:00</published><updated>2011-11-17T10:43:21.975-08:00</updated><title type='text'>Detecting backspace in a UITextField</title><content type='html'>I recently had need for a token field for iOS while implementing a "To:" field like the one in Mail.app. I ran into a problem, though.&amp;nbsp;The token field consists of a label, some number of tokens, and a borderless UITextField at the end where the user can enter text for the next token. It looks something like this, if you'll forgive the ASCII art:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;-------------------------------------------------&lt;br /&gt;| To:  (token1) (token2) (token3) _____________ |&lt;br /&gt;-------------------------------------------------&lt;br /&gt;   ^         ^     ^      ^             ^&lt;br /&gt;UILabel          tokens           UITextField&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;In Mail.app, if you're at the far left of the text field and hit the backspace key, the rightmost token will be highlighted. Another backspace deletes the token and puts focus back in the text field. But there's a problem. &lt;code&gt;UITextField&lt;/code&gt; provides no way to detect a backward delete at the beginning of the field.&lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;&lt;h3&gt;Part 1: Discovery&lt;/h3&gt;&lt;br /&gt;You'd think this would be simple, but it's not. Some of my early attempts  included:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Implementing &lt;code&gt;-textField:shouldChangeCharactersInRange:replacementString:&lt;/code&gt;   in the text field's delegate and looking for an attempt change the range at (0,0). No dice—it's not called if the text isn't actually going to change.&lt;/li&gt;&lt;li&gt;Subclassing &lt;code&gt;UITextField&lt;/code&gt; and overriding the &lt;code&gt;-deleteBackward&lt;/code&gt; method (part of the &lt;code&gt;UIKeyInput&lt;/code&gt; protocol, which &lt;code&gt;UITextField&lt;/code&gt; conforms to via &lt;code&gt;UITextInput&lt;/code&gt;). &lt;code&gt;-[UITextField deleteBackward]&lt;/code&gt; is never called when the backspace key is pressed.&lt;/li&gt;&lt;li&gt;Subclassing &lt;code&gt;UITextField&lt;/code&gt; and overriding various methods declared in the  &lt;code&gt;UITextInput&lt;/code&gt; protocol, such as &lt;code&gt;-replaceRange:withText:&lt;/code&gt;, &lt;code&gt;-setSelectedTextRange:&lt;/code&gt;, etc. Surprisingly, none of these are called  either—despite declaring conformance to the &lt;code&gt;UITextInput&lt;/code&gt; protocol, it seems that UIKit doesn't actually use any of those methods when handling keyboard input.&lt;/li&gt;&lt;/ul&gt;So, we've got a &lt;code&gt;UITextField&lt;/code&gt; that claims to implement all these text-handling methods, and yet it doesn't seem to use them when handling keyboard input. It's probably forwarding them on to some other object, but how could we find out who that other object is?&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;DTrace&lt;/strong&gt;, of course.&lt;br /&gt;&lt;br /&gt;Using a boilerplate app containing a single UITextField, I pulled out Instruments and added a Trace instrument for &lt;code&gt;-[* delete*]&lt;/code&gt;.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-qeq6LSnawGw/TsUpCBxn4uI/AAAAAAAAAao/dObawPTD6ec/s1600/Screen+Shot+2011-10-29+at+11.07.23+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="128" src="http://1.bp.blogspot.com/-qeq6LSnawGw/TsUpCBxn4uI/AAAAAAAAAao/dObawPTD6ec/s320/Screen+Shot+2011-10-29+at+11.07.23+PM.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;That is, "Trace every  ObjC call to any object where the selector starts with the word 'delete'." I launched the app and waited for things to quiet down, then tapped in the text field and hit the backspace key on the keyboard. Immediately, a few delete-related methods showed up, including this stack trace:&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-LP7QvaO_ghk/TsUlsuEGemI/AAAAAAAAAag/_dInEAEr6nU/s1600/Screen+Shot+2011-10-29+at+11.14.27+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-LP7QvaO_ghk/TsUlsuEGemI/AAAAAAAAAag/_dInEAEr6nU/s1600/Screen+Shot+2011-10-29+at+11.14.27+PM.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Why hello there, &lt;code&gt;UIFieldEditor&lt;/code&gt; with a &lt;code&gt;UITextInputAdditions&lt;/code&gt; category. We've been looking for you.&lt;br /&gt;&lt;br /&gt;I love DTrace.&lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;&lt;h3&gt;Part 2: Digging through the dump&lt;/h3&gt;&lt;br /&gt;Okay, so we know there's something called a &lt;code&gt;UIFieldEditor&lt;/code&gt;, and since  &lt;code&gt;UITextField&lt;/code&gt; wasn't involved at all in the above stack trace, we can guess that the field editor may be doing all of the heavy lifting. So let's look at &lt;code&gt;UIFieldEditor&lt;/code&gt; a bit deeper.&lt;br /&gt;&lt;br /&gt;If we &lt;a href="http://www.codethecode.com/projects/class-dump/"&gt;class-dump&lt;/a&gt; &lt;code&gt;UIFieldEditor&lt;/code&gt; in UIKit, we see that the first three methods in its method list are these:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;+ (void)releaseSharedInstance;&lt;br /&gt;+ (id)sharedFieldEditor;&lt;br /&gt;+ (id)activeFieldEditor;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;So it appears that the &lt;code&gt;UIFieldEditor&lt;/code&gt; receiving keyboard input is likely a shared object that is reused whenever the first responder needs keyboard input. Since there can only be one first responder at a time, there's probably no need for more than one &lt;code&gt;UIFieldEditor&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Class-dump also tells us that &lt;code&gt;UIFieldEditor&lt;/code&gt; is a subclass of  &lt;code&gt;UIWebDocumentView&lt;/code&gt;. This is interesting, because a bit of time in DTrace will also confirm that &lt;code&gt;-[UITextField deleteBackward]&lt;/code&gt; calls down to &lt;code&gt;[UIWebDocumentView deleteBackward]&lt;/code&gt;. It looks like the field editor is a view that gets overlaid on top of text input views, and probably handles most of the text editing experience.&lt;br /&gt;&lt;br /&gt;So rather than subclassing &lt;code&gt;UITextField&lt;/code&gt;, it looks like we really want to be  subclassing &lt;code&gt;UIFieldEditor&lt;/code&gt; if we want to do something special with  &lt;code&gt;-deleteBackward&lt;/code&gt;. &lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;&lt;h3&gt;Part 3: Dark Runtime Magic&lt;/h3&gt;&lt;br /&gt;We've got two problems down the "Subclass UIFieldEditor" path, though:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;UIFieldEditor&lt;/code&gt; is a private class in UIKit, so we don't have its    headers. Without the headers, &lt;code&gt;@interface MyFieldEditor : UIFieldEditor&lt;/code&gt;    is going to cause a compiler error, since it won't know how to inherit    from &lt;code&gt;UIFieldEditor&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;We don't control the &lt;code&gt;UIFieldEditor&lt;/code&gt; instance used by &lt;code&gt;UIKit&lt;/code&gt;. Even    if we &lt;em&gt;could&lt;/em&gt; create our own &lt;code&gt;MyFieldEditor&lt;/code&gt;, we still don't know how    to swap out the existing shared object for our own.&lt;/li&gt;&lt;/ol&gt;Fortunately, we can handle both of these problems with the same solution: &lt;strong&gt;dynamic subclassing&lt;/strong&gt;. We'll create our own subclass of &lt;code&gt;UIFieldEditor&lt;/code&gt; &lt;em&gt;at runtime&lt;/em&gt; and change the class of the existing field editor to be out new dynamic subclass. This sounds crazy—and it is. But it works&lt;sup id="fnref:kvo"&gt;&lt;a href="http://draft.blogger.com/blogger.g?blogID=4974525831038192874#fn:kvo" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;, and it allows us to add our own &lt;code&gt;-deleteBackward&lt;/code&gt; functionality without having to  swap out existing objects and somehow inform UIKit of what we're doing.&lt;br /&gt;&lt;br /&gt;Dynamically creating classes at runtime isn't something you're likely to do very often, but it's fairly well documented, and there have been some &lt;a href="http://www.mikeash.com/pyblog/friday-qa-2010-11-6-creating-classes-at-runtime-in-objective-c.html"&gt;great&lt;/a&gt; &lt;a href="http://funwithobjc.tumblr.com/post/1482787069/dynamic-subclassing"&gt;posts&lt;/a&gt; about it in the community. I won't bother going into more detail here—it's enough to know that the dynamically-created class works just like any other, once you get it all set up. You just need to allocate a new class pair, register the new class, and add the methods we want to override to the new class. (One caveat of note: calls to &lt;code&gt;objc_allocateClassPair()&lt;/code&gt; and &lt;code&gt;objc_registerClass()&lt;/code&gt; fail under ARC, so if you're using ARC you'll have to do those in a file that's not using ARC.) You can see how this works in my sample implementation, linked at the end of this post.&lt;br /&gt;&lt;br /&gt;Once we've created our &lt;code&gt;MyFieldEditor&lt;/code&gt; class, though, we still have to actually change the class of the existing &lt;code&gt;UIFieldEditor&lt;/code&gt;. This requires using the runtime function &lt;code&gt;object_setClass(id object, Class newClass)&lt;/code&gt;. The &lt;code&gt;newClass&lt;/code&gt; parameter is easy enough, but what are we going to pass it for the &lt;code&gt;object&lt;/code&gt;? We know there's a &lt;code&gt;UIFieldEditor&lt;/code&gt; out there, but we still don't have a reference to it.&lt;br /&gt;&lt;br /&gt;Let's go back to class-dump for a moment. Looking through the method list on &lt;code&gt;UITextField&lt;/code&gt;, you'll see a &lt;code&gt;-(id)_fieldEditor&lt;/code&gt; method. Sounds like exactly what we want. Unfortunately, we can't just toss that method declaration in a category and then call it directly; that's sure to fail App Store validation for using private API. So we need some way of calling that method without making it &lt;em&gt;look&lt;/em&gt; like we're using that method.&lt;br /&gt;&lt;br /&gt;We could probably do it with &lt;code&gt;-(id)performSelector:&lt;/code&gt;, but we clearly can't just create the selector with &lt;code&gt;@selector(_fieldEditor)&lt;/code&gt;; that will fail App Store validation just as quickly as calling it directly. We could construct it dynamically from a string, but ARC introduces some caveats when calling &lt;code&gt;-performSelector:&lt;/code&gt; with a dynamically-constructed selector because it can't guarantee to get the memory management right. It would be nice to have something that would work correctly without a lot of overhead.&lt;br /&gt;&lt;br /&gt;Key-Value Coding to the rescue! Key-Value Coding is built around the idea that if you know the name of a property, Cocoa can figure out what the appropriate getters and setters should be. So, rather than trying to figure out the exact method we want to call, let's just ask Cocoa to get the fieldEditor for us:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;id fieldEditor = [someTextField valueForKey:@"fieldEditor"];&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;It's as easy as that.&lt;br /&gt;&lt;br /&gt;&lt;hr /&gt;&lt;h3&gt;Part 4: Making the call&lt;/h3&gt;&lt;br /&gt;At this point, we have a reference to the field editor, and we've created a dynamic &lt;code&gt;UIFieldEditor&lt;/code&gt; subclass that we can use to customize its behavior. We never actually added any methods, though; &lt;code&gt;MyFieldEditor&lt;/code&gt; doesn't do anything differently from &lt;code&gt;UIFieldEditor&lt;/code&gt; yet. We'll need to dynamically add a method to &lt;code&gt;MyFieldEditor&lt;/code&gt;, but before we can do that, we need to &lt;em&gt;write&lt;/em&gt; the method.&lt;br /&gt;&lt;br /&gt;Our needs are actually quite simple; when &lt;code&gt;-[MyFieldEditor deleteBackward]&lt;/code&gt; is called, we want to call a method letting someone know that a backward deletion happened. Ideally, that "someone" would be the text field itself.  Then we want to call through to the superclass implementation. &lt;br /&gt;&lt;br /&gt;Here's my implementation:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;- (void)fieldEditor_deleteBackward {&lt;br /&gt;&lt;br /&gt;    MyTextField *textField = objc_getAssociatedObject(self, BackwardDeleteTargetKey);&lt;br /&gt;    [textField my_willDeleteBackward];&lt;br /&gt;&lt;br /&gt;    // Call through to super&lt;br /&gt;    Class superclass = class_getSuperclass([self class]);&lt;br /&gt;    SEL deleteBackwardSEL = @selector(deleteBackward);&lt;br /&gt;    IMP superIMP = [superclass instanceMethodForSelector:deleteBackwardSEL];&lt;br /&gt;    superIMP(self, deleteBackwardSEL);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;It's really quite simple. We get a reference to the text field using ObjC  associated objects, and call &lt;code&gt;-my_willDeleteBackward&lt;/code&gt; on it. Then we pass the &lt;code&gt;-deleteBackward&lt;/code&gt; method up to the superclass, &lt;code&gt;UIFieldEditor&lt;/code&gt;. We have to use the runtime methods to do the superclass call because of the dynamic subclassing game;  otherwise, we'd get the wrong superclass.&lt;br /&gt;&lt;br /&gt;I'm a little nervous about unilaterally changing the behavior of &lt;code&gt;UIFieldEditor&lt;/code&gt;, because it seems likely that &lt;em&gt;every text input area in your app&lt;/em&gt; uses the same instance of the field editor. So we do a little dance in &lt;code&gt;MyTextField&lt;/code&gt;'s implementations of &lt;code&gt;-becomeFirstResponder&lt;/code&gt; and &lt;code&gt;-resignFirstResponder&lt;/code&gt;. It looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;- (BOOL)becomeFirstResponder {&lt;br /&gt;    BOOL shouldBecome = [super becomeFirstResponder];&lt;br /&gt;    if (shouldBecome == NO) {&lt;br /&gt;        return NO;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    Class myFieldEditorClass = objc_lookUpClass([SubclassName UTF8String]);&lt;br /&gt;    if (myFieldEditorClass == nil) {&lt;br /&gt;        myFieldEditorClass = registerMyFieldEditor();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    id fieldEditor = [self valueForKey:@"fieldEditor"];&lt;br /&gt;&lt;br /&gt;    if (fieldEditor &amp;amp;&amp;amp; myFieldEditorClass) {&lt;br /&gt;        object_setClass(fieldEditor, myFieldEditorClass);&lt;br /&gt;        objc_setAssociatedObject(fieldEditor, BackwardDeleteTargetKey,&lt;br /&gt;                                 self, OBJC_ASSOCIATION_ASSIGN);&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    return YES;&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;- (BOOL)resignFirstResponder {&lt;br /&gt;    BOOL shouldResign =  [super resignFirstResponder];&lt;br /&gt;    if (shouldResign == NO) {&lt;br /&gt;        return NO;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    id fieldEditor = [self valueForKey:@"fieldEditor"];&lt;br /&gt;&lt;br /&gt;    if (fieldEditor) {&lt;br /&gt;        objc_setAssociatedObject(fieldEditor, BackwardDeleteTargetKey,&lt;br /&gt;                                 nil, OBJC_ASSOCIATION_ASSIGN);&lt;br /&gt;        Class uiFieldEditorClass = objc_lookUpClass("UIFieldEditor");&lt;br /&gt;        if (uiFieldEditorClass) {&lt;br /&gt;            object_setClass(fieldEditor, uiFieldEditorClass);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    return YES;&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;With this implementation, the shared &lt;code&gt;UIFieldEditor&lt;/code&gt; instance will only be of class &lt;code&gt;MyFieldEditor&lt;/code&gt; while the text field is actively the first responder.  As soon as the text field resigns, it goes back to being a regular old &lt;code&gt;UIFieldEditor&lt;/code&gt;. No other text field in the app will be affected, and this text field will hear about all the backward deletion calls as soon as they come in.&lt;br /&gt;&lt;br /&gt;This is about the point where everyone working on &lt;code&gt;UIKit&lt;/code&gt; starts squirming vigorously. If you'd like people to &lt;em&gt;not&lt;/em&gt; do this kind of stuff (which I'd heartily agree with), then let me direct your attention to Radars &lt;a href="rdar://problem/10265826"&gt;#10265826&lt;/a&gt; and &lt;a href="rdar://problem/10377565"&gt;#10377565&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In the meantime, use with caution. I haven't tried it in the App Store, but I suspect it will pass validation as there are no symbols referencing any private API in this implementation.&lt;br /&gt;&lt;br /&gt;The code is &lt;a href="https://github.com/bjhomer/HSBackspaceFriendlyTextField"&gt;on GitHub&lt;/a&gt;.&lt;br /&gt;&lt;div class="footnote"&gt;&lt;hr /&gt;&lt;ol&gt;&lt;li id="fn:kvo"&gt;This is actually the same mechanism Cocoa uses to implement Key-Value Observing; when you start observing a property of some object, Cocoa generates a new subclass of that object's class and implements a setter method that wraps your own with calls to &lt;code&gt;-willSetValueForKey:&lt;/code&gt; and &lt;code&gt;-didSetValueForKey:&lt;/code&gt;. When all observers on an object are gone, its class is set back to the original class.&amp;nbsp;&lt;a href="http://draft.blogger.com/blogger.g?blogID=4974525831038192874#fnref:kvo" rev="footnote" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-7960419979624697773?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/7960419979624697773/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=7960419979624697773' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/7960419979624697773'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/7960419979624697773'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2011/11/detecting-backspace-in-uitextfield.html' title='Detecting backspace in a UITextField'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-qeq6LSnawGw/TsUpCBxn4uI/AAAAAAAAAao/dObawPTD6ec/s72-c/Screen+Shot+2011-10-29+at+11.07.23+PM.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-3726982635432230692</id><published>2011-10-14T10:17:00.000-07:00</published><updated>2011-10-14T10:17:01.925-07:00</updated><title type='text'>Adding a fixed image background to UIWebView</title><content type='html'>A short post for iOS people.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;UIWebView&lt;/code&gt; doesn't (as of iOS 5.0) honor &lt;code&gt;background-attachment: fixed;&lt;/code&gt; in CSS, so if you need a fixed background image on a &lt;code&gt;UIWebView&lt;/code&gt;, you're probably searching Google trying to find the answer.&lt;br /&gt;&lt;br /&gt;Hopefully, this will help.&lt;br /&gt;&lt;br /&gt;&lt;div style="font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;UIWebView *someWebView;&lt;/div&gt;&lt;div style="font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;someWebView.&lt;span style="color: #7141a3;"&gt;opaque&lt;/span&gt; = &lt;span style="color: #bc319c;"&gt;NO&lt;/span&gt;;&lt;/div&gt;&lt;div style="color: #7141a3; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span style="color: black;"&gt;someWebView.&lt;/span&gt;backgroundColor&lt;span style="color: black;"&gt; = [&lt;/span&gt;UIColor&lt;span style="color: black;"&gt; &lt;/span&gt;&lt;span style="color: #3e227c;"&gt;clearColor&lt;/span&gt;&lt;span style="color: black;"&gt;];&lt;/span&gt;&lt;/div&gt;&lt;div style="color: #7141a3; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span style="color: black;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;UIImageView *imageView;&lt;/div&gt;&lt;div style="font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;imageView.&lt;span style="color: #7141a3;"&gt;image&lt;/span&gt; = [&lt;span style="color: #7141a3;"&gt;UIImage&lt;/span&gt; &lt;span style="color: #3e227c;"&gt;imageNamed&lt;/span&gt;:&lt;span style="color: #d32d26;"&gt;@"something"&lt;/span&gt;];&lt;/div&gt;&lt;div style="color: #3e227c; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span style="color: black;"&gt;imageView.&lt;/span&gt;&lt;span style="color: #7141a3;"&gt;autoresizingMask&lt;/span&gt;&lt;span style="color: black;"&gt; = (&lt;/span&gt;UIViewAutoresizingFlexibleHeight&lt;span style="color: black;"&gt; |&amp;nbsp;&lt;/span&gt;UIViewAutoresizingFlexibleWidth&lt;span style="color: black;"&gt;);&lt;/span&gt;&lt;/div&gt;&lt;div style="color: #3e227c; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;span style="color: black;"&gt;imageView.&lt;/span&gt;&lt;span style="color: #7141a3;"&gt;contentMode&lt;/span&gt;&lt;span style="color: black;"&gt; = &lt;/span&gt;UIViewContentModeScaleToFill&lt;span style="color: black;"&gt;;&lt;/span&gt;&lt;/div&gt;&lt;div style="font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;imageView.&lt;span style="color: #7141a3;"&gt;frame&lt;/span&gt; = someWebView.&lt;span style="color: #7141a3;"&gt;bounds&lt;/span&gt;;&lt;/div&gt;&lt;div style="font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;[someWebView &lt;span style="color: #3e227c;"&gt;insertSubview&lt;/span&gt;:imageView &lt;span style="color: #3e227c;"&gt;atIndex&lt;/span&gt;:&lt;span style="color: #2834cf;"&gt;0&lt;/span&gt;];&lt;/div&gt;&lt;div style="color: #4d8186; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"&gt;&lt;code&gt;&lt;span class="Apple-style-span" style="font-family: monospace;"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-3726982635432230692?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/3726982635432230692/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=3726982635432230692' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/3726982635432230692'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/3726982635432230692'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2011/10/adding-fixed-image-background-to.html' title='Adding a fixed image background to UIWebView'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-8819996477831417149</id><published>2011-09-29T07:43:00.000-07:00</published><updated>2011-09-29T07:43:19.116-07:00</updated><title type='text'>@synchronized vs dispatch_once</title><content type='html'>In the comments on a &lt;a href="http://stackoverflow.com/questions/4251087/singleton-or-class-methods/4251872#4251872"&gt;recent Stack Overflow question&lt;/a&gt;, someone asked me if there was a significant performance difference between &lt;code&gt;@synchronized&lt;/code&gt; and &lt;code&gt;dispatch_once&lt;/code&gt; in implementing a singleton. So I wrote a simple test harness to access a singleton using the &lt;code&gt;@synchronized&lt;/code&gt; method shown here:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;@synchronized(self) {&lt;br /&gt;    if (!synchronizedVar) {&lt;br /&gt;        synchronizedVar = [[Test alloc] init];&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;return synchronizedVar;&lt;/pre&gt;&lt;pre&gt;&lt;/pre&gt;and the &lt;code&gt;dispatch_once&lt;/code&gt; method shown here:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;static dispatch_once_t onceToken;&lt;br /&gt;dispatch_once(&amp;amp;onceToken, ^{&lt;br /&gt;	dispatchVar = [[Test alloc] init];&lt;br /&gt;});&lt;br /&gt;return dispatchVar;&lt;/pre&gt;&lt;br /&gt;Each test accessed the singleton object 10 million times. I ran both single-threaded tests and multi-threaded tests. Here were the results:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;br /&gt;&lt;pre&gt;&lt;b&gt;Single threaded results&lt;br /&gt;-----------------------&lt;br /&gt;  @synchronized: 3.3829 seconds&lt;br /&gt;  dispatch_once: 0.9891 seconds&lt;br /&gt;&lt;br /&gt;Multi threaded results&lt;br /&gt;----------------------&lt;br /&gt;  @synchronized: 33.5171 seconds&lt;br /&gt;  dispatch_once: 1.6648 seconds&lt;br /&gt;&lt;/b&gt;&lt;/pre&gt;So yeah, dispatch_once is a lot faster, especially under thread contention.You can find my test harness &lt;a href="https://github.com/bjhomer/Demos"&gt;on github&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-8819996477831417149?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/8819996477831417149/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=8819996477831417149' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/8819996477831417149'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/8819996477831417149'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2011/09/synchronized-vs-dispatchonce.html' title='@synchronized vs dispatch_once'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-4803119460993859640</id><published>2011-05-27T20:34:00.000-07:00</published><updated>2011-05-27T20:44:01.506-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Tri-Pic'/><title type='text'>Tri-Pic is in the App Store!</title><content type='html'>&lt;a href="http://itunes.com/app/tripic"&gt;Tri-Pic&lt;/a&gt; is finally in the App Store! I've been working on this as a side project for a few months, and finally got around to finishing it up. It's a fun little app to mix faces and share them via email, Twitter, Facebook, and other services. And it's available now in the App Store, &lt;b style="font-style: italic;"&gt;free&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-gVtufV388eU/TeArkKaFToI/AAAAAAAAAYA/Ou5hq0bqf6I/s1600/Tri-Pic.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="320" src="http://3.bp.blogspot.com/-gVtufV388eU/TeArkKaFToI/AAAAAAAAAYA/Ou5hq0bqf6I/s320/Tri-Pic.png" width="213" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;FrankenMe/my sister/my nephew&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;Tri-Pic started out as a project to help my brother with a marketing assignment. In the class they had to come up with a game and do a bunch of market research on it. But it had to be a real game. They were allowed to use an iPhone app only if they knew someone who could actually make that app.&lt;br /&gt;&lt;br /&gt;As it turns out, my brother knows a guy who can make apps.&lt;br /&gt;&lt;br /&gt;The arrangement was pretty simple: I'd make the app, they'd use it with for class and get the grade, and then I could do whatever I wanted with it. Awesome. I got the basics done in time for them to finish the course, but it never felt quite ready to put in the App Store.&lt;br /&gt;&lt;br /&gt;The biggest problem was the initial experience. It's fun to mix faces, of course, but when you first launch the app, it doesn't&amp;nbsp;&lt;i&gt;have&lt;/i&gt;&amp;nbsp;any faces. I could have included a bunch of stock faces, but it's at least &lt;i&gt;twice&lt;/i&gt; as fun to mix faces of people you know, so I really wanted to avoid stock faces. But the first experience with the app was pretty terrible: you spent at least 2 minutes finding a half-dozen good pictures to use and cropping the face in each one before you ever got to mix any faces. Two minutes of tedium isn't a great introduction to &lt;i&gt;any&lt;/i&gt;&amp;nbsp;app.&lt;br /&gt;&lt;br /&gt;So I added face detection using &lt;a href="http://opencv.willowgarage.com/wiki/"&gt;OpenCV&lt;/a&gt;. Tri-Pic will scan your photo library and look for faces, automatically adding them to the database.&amp;nbsp;And even better is that on devices that support multitasking, this detection can continue in the background while off browsing the web or tweeting about Tri-Pic. The initial experience went from super lame to magical.&lt;br /&gt;&lt;br /&gt;Is Tri-Pic going to earn an Apple Design Award? Not likely. Nor is it going to change the world, or fix the economy, or anything else monumental. But it's fun, it's free, and you'll at least get a smile or two out of it.&amp;nbsp;And really, who can't use a smile?&lt;br /&gt;&lt;br /&gt;&lt;a href="http://itunes.com/app/tripic"&gt;Download Tri-Pic&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;P.S. If you download it before the next update is approved, you may catch a reference to a previous working name. Let's just call it an easter egg, and not a really lame error, okay?&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-4803119460993859640?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/4803119460993859640/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=4803119460993859640' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/4803119460993859640'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/4803119460993859640'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2011/05/tri-pic-is-in-app-store.html' title='Tri-Pic is in the App Store!'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-gVtufV388eU/TeArkKaFToI/AAAAAAAAAYA/Ou5hq0bqf6I/s72-c/Tri-Pic.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-8360000046456589561</id><published>2011-04-14T21:14:00.000-07:00</published><updated>2011-04-14T21:52:55.502-07:00</updated><title type='text'>Subclassing NSInputStream</title><content type='html'>Cocoa's NSInputStream is great, but sometimes it doesn't have all the functionality you need. For example, you might want to dynamically encrypt the file as you were streaming it off the disk, or you might want to put up a progress bar indicating how far the input stream had progressed through a large file. NSInputStream doesn't support either of these options natively, but this sounds like a great place for an NSInputStream subclass, right?&lt;br /&gt;&lt;br /&gt;Wrong.&lt;br /&gt;&lt;br /&gt;Or rather, right, with the caveat that subclasses of NSInputStream don't work correctly when used with Cocoa's URL loading mechanism. ( folks: See &lt;a href="rdar://problem/3222783"&gt;rdar://problem/3222783&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The Problem&lt;/h3&gt;&lt;br /&gt;When you make an NSInputStream subclass and try to pass it to -[NSURLRequest setHTTPBodyStream:], your app will quickly crash with an 'unrecognized selector' for the following method:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;- (void) _scheduleInCFRunLoop: (CFRunLoopRef) inRunLoop forMode: (CFStringRef) inMode&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;If you implement that, you'll then get another unrecognized selector:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;- (BOOL) _setCFClientFlags: (CFOptionFlags)inFlags&lt;br /&gt;                  callback: (CFReadStreamClientCallBack) inCallback&lt;br /&gt;                   context: (CFStreamClientContext *) inContext&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;If you give that one an empty implementation too (&lt;code&gt;return YES;&lt;/code&gt;) and the file you're uploading is small, you may manage to make it to a third unimplemented selector:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;- (void) _unscheduleFromCFRunLoop:(CFRunLoopRef)inRunLoop forMode:(CFStringRef)inMode&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Unfortunately, while you can avoid the unrecognized selector crashes by implementing these methods, you're very unlikely to get through your whole stream. Further, &lt;a href="http://lists.apple.com/archives/macnetworkprog/2007/May/msg00056.html"&gt;Apple engineers have stated&lt;/a&gt; that such naive implementations are "definitely not safe" and will likely lead to failing in "strange and unexpected ways."&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The backstory&lt;/h3&gt;&lt;br /&gt;The real question here is what these methods are for in the first place. It turns out that these three methods are simply the toll-free bridging versions of &lt;code&gt;CFReadStreamScheduleWithRunLoop&lt;/code&gt;, &lt;code&gt;CFReadStreamSetClient&lt;/code&gt;, and &lt;code&gt;CFReadStreamUnscheduleFromRunLoop&lt;/code&gt;, respectively. Calling &lt;code&gt;CFReadStreamScheduleWithRunLoop&lt;/code&gt; from &lt;code&gt;_scheduleInCFRunLoop:...&lt;/code&gt;, for example, is a quick way to infinite recursion.&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;NSStream&lt;/code&gt; documentation indicates that subclasses must override &lt;code&gt;-(void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode&lt;/code&gt; and &lt;code&gt;-(void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode&lt;/code&gt;. Why? Because the stream's delegate usually needs to be notified when there are bytes available to be read, and that getting that information often requires being scheduled on a run loop. &lt;i&gt;Our three mystery methods serve the same purpose.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;NSInputStream&lt;/code&gt; is toll-free bridged with &lt;code&gt;CFReadStream&lt;/code&gt;. Mostly. From the &lt;code&gt;CFReadStream&lt;/code&gt; Reference:&lt;br /&gt;&lt;blockquote&gt;CFReadStream is “toll-free bridged” with its Cocoa Foundation counterpart, NSInputStream. This means that the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object. Therefore, in a method where you see an NSInputStream * parameter, you can pass in a CFReadStreamRef, and in a function where you see a CFReadStreamRef parameter, you can pass in an NSInputStream instance. &lt;i&gt;Note, however, that you may have either a delegate or callbacks but not both.&lt;/i&gt; &lt;/blockquote&gt;These methods are required to support the CFReadStream client callbacks, which are distinct from the delegate callbacks.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;The solution&lt;/h3&gt;&lt;br /&gt;&lt;code&gt;-[NSInputStream _scheduleInCFRunLoop:forMode:]&lt;/code&gt; is the equivalent of &lt;a href="http://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFReadStreamRef/Reference/reference.html#//apple_ref/doc/uid/20001440-CH1g-F17791"&gt;&lt;code&gt;CFReadStreamScheduleWithRunLoop&lt;/code&gt;&lt;/a&gt; for your stream. Do whatever you need to do so that you can give proper &lt;code&gt;kCFStreamEventHasBytesAvailable&lt;/code&gt; notifications (and any other notifications requested) at the proper time. That may involve scheduling a timer on the run loop, or if your subclass is just wrapping a vanilla NSInputStream, simply scheduling &lt;i&gt;that&lt;/i&gt; stream on the run loop. Implement this method as if you were implementing &lt;a href="http://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFReadStreamRef/Reference/reference.html#//apple_ref/doc/uid/20001440-CH1g-F17791"&gt;&lt;code&gt;CFReadStreamScheduleWithRunLoop&lt;/code&gt;&lt;/a&gt; for your stream&lt;br /&gt;&lt;br /&gt;&lt;code&gt;-[NSInputStream _setCFClientFlags:callback:context:]&lt;/code&gt; is the equivalent of &lt;a href="http://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFReadStreamRef/Reference/reference.html#//apple_ref/doc/uid/20001440-CH1g-F17789"&gt;&lt;code&gt;CFReadStreamSetClient&lt;/code&gt;&lt;/a&gt;, you need to do a few things. If the &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;callback&lt;/code&gt; arguments are not &lt;code&gt;NULL&lt;/code&gt;, the caller is trying to set up a callback client.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Inspect the flags, and record which notifications are requested. The possible values are listed in the &lt;a href="http://developer.apple.com/library/ios/#documentation/CoreFoundation/Reference/CFStreamConstants/Reference/reference.html#//apple_ref/c/tdef/CFStreamEventType"&gt;CFStream Event Type Constants documentation&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Copy the pointer to the callback function. You'll need to use it later.&lt;/li&gt;&lt;li&gt;Copy the context. The documentation for &lt;code&gt;CFReadStreamSetClient&lt;/code&gt; indicates that the context struct passed by the caller should be copied, and that the caller is not responsible for preserving it. &lt;code&gt;memcpy(&amp;amp;myLocalContextCopy, thePassedContext, sizeof( CFStreamClientContext))&lt;/code&gt; works just fine.&lt;/li&gt;&lt;li&gt;Retain the context-&amp;gt;info. The context struct includes a &lt;code&gt;void *info&lt;/code&gt; member and a &lt;code&gt;CFAllocatorRetainCallBack retain&lt;/code&gt; member. Call the retain function on the info pointer (if the retain function is not nil).&lt;/li&gt;&lt;/ol&gt;If the &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;callback&lt;/code&gt; parameters &lt;i&gt;are&lt;/i&gt; &lt;code&gt;NULL&lt;/code&gt;, then the caller is removing the callback client, and you need to do the following: &lt;br /&gt;&lt;ol&gt;&lt;li&gt;Call the &lt;code&gt;release&lt;/code&gt; function (from the context you previously copied) on the &lt;code&gt;info&lt;/code&gt; pointer in that context.&lt;/li&gt;&lt;li&gt;Remove your copy of the context and callback; they are no longer needed.&lt;/li&gt;&lt;/ol&gt;Finally, return &lt;code&gt;YES&lt;/code&gt; to indicate that the asynchronous scheduling was successful.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;-[NSInputStream _unscheduleFromCFRunLoop:forMode]&lt;/code&gt; is the equivalent of &lt;a href="http://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFReadStreamRef/Reference/reference.html#//apple_ref/doc/uid/20001440-CH1g-F17793"&gt;&lt;code&gt;CFReadStreamUnscheduleFromRunLoop&lt;/code&gt;&lt;/a&gt;. You should remove anything you scheduled in the &lt;code&gt;_scheduleInCFRunLoop:forMode:&lt;/code&gt; method.&lt;br /&gt;&lt;br /&gt;Once you've done these, make sure you've implemented the other methods required for subclasses as documented in the NSInputStream and NSStream reference. Now, when you pass off your NSInputStream subclass to NSURLRequest, your stream will be scheduled on the run loop and the client callbacks will be set up. Once you're scheduled on the run loop, you should be able to notify your client when you have bytes available to read, when you've reached the end of the file, and when an error has occurred. Make sure you're passing those along correctly, and everything will be good to go.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Sample&lt;/h3&gt;&lt;br /&gt;I've put together a sample implementation of an NSInputStream subclass that demonstrates what I've presented. &lt;a href="https://github.com/bjhomer/HSCountingInputStream"&gt;HSCountingInputStream&lt;/a&gt; is a simple class that wraps an NSInputStream, counting the number of instances of a given character have passed through it. Nearly every call is simply passed through to the underlying NSInputStream; the only real code of interest is in the &lt;code&gt;-read:maxLength:&lt;/code&gt; and in the undocumented methods discussed above.&lt;br /&gt;&lt;br /&gt;I'll keep an eye on the comments, so if you have any questions, please let me know. I'll probably write up another blog here shortly about how I figured all of this out, for those who are interested.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-8360000046456589561?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/8360000046456589561/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=8360000046456589561' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/8360000046456589561'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/8360000046456589561'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2011/04/subclassing-nsinputstream.html' title='Subclassing NSInputStream'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-354932922947957838</id><published>2009-08-28T12:22:00.000-07:00</published><updated>2010-07-11T06:54:20.883-07:00</updated><title type='text'>Creating a Finder alias programmatically on Snow Leopard</title><content type='html'>My co-worker Dan posted a blog yesterday entitled "&lt;a href="http://www.danandcheryl.com/2009/08/how-to-create-an-alias-programmatically"&gt;How to Create an Alias Programmatically&lt;/a&gt;". His example is 21 lines long, and is compatible with Leopard and earlier.  It only works if the original item is a folder (though creating an alias to a file is not much different).  The code messes with resource forks, and the C interface is generally difficult to read for someone who spends most of their time in Objective-C.  The code is also not guaranteed to be 100% accurate, since Apple explicitly did not support programmatic alias creation in Leopard.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This is a great example of how Apple has improved the developer experience for OS X in Snow Leopard.  Snow Leopard contains some new APIs for "bookmark" creation, which appears to be the new behind-the-scenes name for aliases.  The following four lines of code create a functioning alias on Snow Leopard:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;NSURL *src = [NSURL URLWithString:@"file:///Users/bjh/Desktop/temp.m"];&lt;br /&gt;NSURL *dest = [NSURL URLWithString:@"file:///Users/bjh/Desktop/myalias"];&lt;br /&gt;&lt;br /&gt;NSData *bookmarkData = [src bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile&lt;br /&gt;includingResourceValuesForKeys:nil&lt;br /&gt;relativeToURL:nil&lt;br /&gt;error:NULL];&lt;br /&gt;[NSURL writeBookmarkData:bookmarkData&lt;br /&gt;toURL:dest&lt;br /&gt;options:0&lt;br /&gt;error:NULL];&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Isn't that about a billion times easier?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm not sure what the relativeToURL: parameter is for yet; comments are welcome.&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-354932922947957838?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/354932922947957838/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=354932922947957838' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/354932922947957838'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/354932922947957838'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2009/08/creating-finder-alias-programmatically.html' title='Creating a Finder alias programmatically on Snow Leopard'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-6963670666474762119</id><published>2009-07-09T18:07:00.000-07:00</published><updated>2009-07-09T18:12:39.429-07:00</updated><title type='text'>100 Hour Board iPhone app v1.2 released</title><content type='html'>As of this previous Sunday, 100 Hour Board for iPhone was updated to version 1.2.  This update includes support for iPhone OS 3.0, improves the rendering of quotes in Board posts, and teaches the app to remember where you were when you last closed it.  There are also a couple minor user interface tweaks; notably, when posts download they should appear in the list with a smooth animation.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Go check it out!  &lt;a href="http://tinyurl.com/100hourapp"&gt;http://tinyurl.com/100hourapp&lt;/a&gt; (iTunes Music Store link)&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-6963670666474762119?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/6963670666474762119/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=6963670666474762119' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/6963670666474762119'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/6963670666474762119'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2009/07/100-hour-board-iphone-app-v12-released.html' title='100 Hour Board iPhone app v1.2 released'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-3210918021858744698</id><published>2009-06-16T21:52:00.000-07:00</published><updated>2010-03-30T21:18:28.349-07:00</updated><title type='text'>Singleton Pattern in Cocoa</title><content type='html'>Peter Hosey recently &lt;a href="https://twitter.com/boredzo/status/2201415345"&gt;tweeted&lt;/a&gt;:&lt;blockquote&gt;From the “how NOT to implement a Cocoa singleton” dept: &lt;a href="http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html#//apple_ref/doc/uid/TP40002974-CH4-SW32"&gt;http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html#//apple_ref/doc/uid/TP40002974-CH4-SW32&lt;/a&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;a href="https://twitter.com/boredzo/status/2201642358"&gt;followed by&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;If something over-releases an object, the bug is not in that object, and overriding methods in it to break retain/release is not fixing it.&lt;/blockquote&gt;&lt;br /&gt;The code sample in question is in the Apple documentation, and prescribes overriding the following methods for a singleton object: &lt;code&gt;allocWithZone&lt;/code&gt;:, &lt;code&gt;copyWithZone:&lt;/code&gt;, &lt;code&gt;retain&lt;/code&gt;, &lt;code&gt;release&lt;/code&gt;, &lt;code&gt;autorelease&lt;/code&gt;, and &lt;code&gt;retainCount&lt;/code&gt;.  In addition, it also recommends implementing a &lt;code&gt;sharedFloozit&lt;/code&gt; class method, so that users of your class are aware they're using a singleton.  Go read that section, or you'll be lost for the rest of this post.&lt;br /&gt;&lt;br /&gt;Really, I mean it.  Go read that documentation.  I'll be referring to it a lot.&lt;br /&gt;&lt;br /&gt;That's a lot of methods to override just to implement a singleton.  More to the point, a lot of it seems unnecessary.  After all, as long as everyone else is doing their memory management correctly, retain counts shouldn't matter, right?&lt;br /&gt;&lt;br /&gt;Wrong.&lt;br /&gt;&lt;br /&gt;First, though, we need to get a definition straight.  A singleton is a class of which there should only be &lt;span style="font-weight:bold;"&gt;one&lt;/span&gt; instance in any given process.  There are actually very few singleton classes in the Cocoa framework including &lt;code&gt;NSDocumentController&lt;/code&gt; and &lt;code&gt;NSFontManager&lt;/code&gt;. [1]  You cannot create more than one of these objects; if you try to call&lt;code&gt; [[NSDocumentController alloc] init]&lt;/code&gt;, you'll get back the exact same object as you do when you call &lt;code&gt;[NSDocument sharedDocumentController]&lt;/code&gt;, no matter how many times you call alloc and init.  &lt;code&gt;NSApplication&lt;/code&gt; is arguably a singleton as well; you can alloc another one, but you'll get an assertion failure when you call init.&lt;br /&gt;&lt;br /&gt;Singletons are generally useful when initializing an object takes an inordinate amount of time.  &lt;code&gt;NSFontManager&lt;/code&gt;, for example, has to search several locations on the filesystem in order to do its job.  You really don't want to be constantly initializing an &lt;code&gt;NSFontManager&lt;/code&gt;.  Further, it makes very little sense to have more than one instance of &lt;code&gt;NSApplication&lt;/code&gt; within your application. Having one shared object for the whole system can simplify and optimize certain things. [2]&lt;br /&gt;&lt;br /&gt;So that's a singleton. Only one instance allowed in the entire process.&lt;br /&gt;&lt;br /&gt;There are other classes that have a &lt;code&gt;+[SomethingClass sharedSomething]&lt;/code&gt; method or a &lt;code&gt;+[SomethingClass defaultSomething]&lt;/code&gt; method.  These methods provide access to an instance of the object intended to be shared by all users of the class, but do not prohibit the creation of another instance.  Indeed, the &lt;a href="http://developer.apple.com/releasenotes/Cocoa/Foundation.html#NSFileManager"&gt;Leopard Developer Release Notes&lt;/a&gt; specifically note that creating multiple instances of &lt;code&gt;NSFileManager&lt;/code&gt; is possible and thread-safe, despite the existence of a globally-available object through &lt;code&gt;+[NSFileManager defaultManager]&lt;/code&gt;.  &lt;span style="font-weight:bold;"&gt;These are not singletons&lt;/span&gt;.  If you're writing a class which provides a shared instance but doesn't not prohibit creation of other instances, then you should absolutely not override &lt;code&gt;retain&lt;/code&gt;, &lt;code&gt;release&lt;/code&gt;, &lt;code&gt;autorelease&lt;/code&gt;, and &lt;code&gt;retainCount&lt;/code&gt;, and should probably not override &lt;code&gt;allocWithZone:&lt;/code&gt; either.&lt;br /&gt;&lt;br /&gt;Most of the time, you don't need a singleton.  Just the mention of a singleton is enough to get some people up in arms.  But if you &lt;span style="font-weight:bold;"&gt;really truly&lt;/span&gt; need a singleton, then there's good reason for overriding the methods listed above.&lt;br /&gt;&lt;br /&gt;Consider the following example:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;MyFloozit *floozit1 = [[MyFloozit alloc] init];&lt;br /&gt;[floozit1 doSomething];&lt;br /&gt;[floozit1 release];&lt;br /&gt;&lt;br /&gt;MyFloozit *floozit2 = [[MyFloozit alloc] init];  // MyFloozit is a singleton, so this should be the same object as floozit1&lt;br /&gt;[floozit2 doSomething];  // CRASH HERE&lt;br /&gt;[floozit2 release];&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;When &lt;code&gt;floozit1&lt;/code&gt; is set, a new &lt;code&gt;MyFloozit&lt;/code&gt; is allocated, and a static &lt;code&gt;MyFloozit&lt;/code&gt; pointer is set.  When &lt;code&gt;floozit1&lt;/code&gt; is released, that static pointer &lt;span style="font-style:italic;"&gt;is still pointing to the old instance&lt;/span&gt;.  As a result, when we try to set &lt;code&gt;floozit2&lt;/code&gt; (or when anyone else tries to call &lt;code&gt;[MyFloozit sharedFloozit]&lt;/code&gt;), we get back a pointer to that same instance.  &lt;span style="font-style:italic;"&gt;The one that has been dealloc-ed&lt;/span&gt;.  Right there, despite following all the standard rules of memory management, you've crashed.  The example might seem contrived, but if &lt;code&gt;floozit1&lt;/code&gt; and &lt;code&gt;floozit2&lt;/code&gt; are in separate methods (or separate threads calling the same method), this could be a very common scenario.&lt;br /&gt;&lt;br /&gt;The point is, if you override &lt;code&gt;allocWithZone:&lt;/code&gt; to force a class to be a singleton, then you &lt;span style="font-style:italic;"&gt;must&lt;/span&gt; override &lt;code&gt;release&lt;/code&gt; (and &lt;code&gt;autorelease&lt;/code&gt;), or else anyone who believes they have ownership (after to calling &lt;code&gt;init&lt;/code&gt;) will crash your program.  Once you're disabling retain counting by overriding &lt;code&gt;release&lt;/code&gt;, you might as well override &lt;code&gt;retain&lt;/code&gt; and &lt;code&gt;retainCount&lt;/code&gt; as well, to be consistent.&lt;br /&gt;&lt;br /&gt;In the Apple Documentation linked above, there is a short paragraph right after the code showing the recommended way to override the various methods.  It says:&lt;br /&gt;&lt;blockquote&gt;Situations could arise where you want a singleton instance (created and controlled by the class factory method) but also have the ability to create other instances as needed through allocation and initialization. In these cases, you would not override &lt;code&gt;allocWithZone:&lt;/code&gt; and the other methods following it as shown in Listing 2-15.&lt;/blockquote&gt;In this situation, I agree with Peter (and Apple) completely; don't override the memory management methods.  I disagree with calling it a singleton, but whatever you want to call it, it's clear that you need those memory management methods.&lt;br /&gt;&lt;br /&gt;If you think you need a singleton, think again.  If you still think you need a singleton, bounce it off someone else to disabuse you of the notion.  If you &lt;span style="font-style:italic;"&gt;still&lt;/span&gt; need a singleton, then follow Apple's advice and override the memory management methods.  Otherwise, you'll crash.&lt;br /&gt;&lt;br /&gt;Of course, if you're using garbage collection, all retain count operations are no-ops, so none of this matters anyway.  Feel free to go on your merry way, pitying the poor souls still living in a retain-counted world.&lt;br /&gt;&lt;br /&gt;[1] The documentation referenced by Peter on "Creating a Singleton Instance" lists &lt;code&gt;NSFileManager&lt;/code&gt; and &lt;code&gt;NSWorkspace&lt;/code&gt; as examples of singletons, but this is incorrect.  It is possible to create more than one instance of both these classes; Cocoa just happens to provide easy access to a shared instance which they suggest you use.&lt;br /&gt;&lt;br /&gt;[2] It can also complicate a lot of things by introducing shared state.  I'm not arguing for the use of the singleton pattern; I &lt;a href="https://twitter.com/boredzo/status/2201754228"&gt;agree with Peter&lt;/a&gt; that most of the time, if you're using a singleton then you're "Doin It Rong".  I'm simply arguing for Apple's implementation of the singleton pattern in the case where you really &lt;i&gt;do&lt;/i&gt; need a singleton.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-3210918021858744698?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/3210918021858744698/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=3210918021858744698' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/3210918021858744698'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/3210918021858744698'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2009/06/singleton-pattern-in-cocoa.html' title='Singleton Pattern in Cocoa'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-5007932842241499514</id><published>2009-03-31T11:05:00.000-07:00</published><updated>2009-03-31T11:10:55.918-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='TheBoard'/><title type='text'>100 Hour Board released... a week ago</title><content type='html'>On March 19, my 100 Hour Board app was officially available in the App Store.  There were nearly 100 downloads in the first two days, and that was before I'd even started doing any advertising.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway, if you haven't downloaded it yet, it's available &lt;a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=307845884&amp;amp;mt=8"&gt;in the App Store here&lt;/a&gt;.  (Link opens in iTunes.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I've got a small update waiting for approval right now.  It adds a popup alert when a search returns no results, so that users don't think it's just a slow search.  Next on the feature list is read/unread tracking for daily posts.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-5007932842241499514?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/5007932842241499514/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=5007932842241499514' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/5007932842241499514'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/5007932842241499514'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2009/03/100-hour-board-released-week-ago.html' title='100 Hour Board released... a week ago'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-2658606388867133099</id><published>2009-03-09T09:43:00.000-07:00</published><updated>2009-03-09T10:44:56.686-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='TheBoard'/><title type='text'>100 Hour Board iPhone app coming soon</title><content type='html'>I'm a big fan of BYU's &lt;a href="http://theboard.byu.edu"&gt;100 Hour Board&lt;/a&gt;, an online Q&amp;A forum that provides answers to nearly any question imaginable within 100 hours.  I've been working with their webmasters over the last few weeks to get a web API to their content, and today I'm uploading an app that provides an iPhone-optimized interface to the 100 Hour Board.  It's a fantastic interface on an awesome website, and I'm excited for it to go live.&lt;br /&gt;&lt;br /&gt;I'll be sure to post as it's available.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-2658606388867133099?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/2658606388867133099/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=2658606388867133099' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/2658606388867133099'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/2658606388867133099'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2009/03/100-hour-board-iphone-app-coming-soon.html' title='100 Hour Board iPhone app coming soon'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-6016408839313382760</id><published>2009-02-11T17:00:00.000-08:00</published><updated>2009-03-09T09:41:44.032-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Surakarta'/><title type='text'>Surakarta is now available</title><content type='html'>Surakarta for the iPhone/iPod Touch is &lt;a href="http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304075492&amp;amp;mt=8"&gt;now available for download&lt;/a&gt;. (Link opens in iTunes.)  I haven't yet been able to find it directly by searching, but hopefully the App Store index will update shortly.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'm currently working on version 1.1, where I'll add single-player support.  In the meantime, download Surakarta and write a review!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;UPDATE &lt;/b&gt;(6:04 pm)&lt;b&gt;: &lt;/b&gt;Sometime in the last 10 minutes, it showed up through search.  Hooray!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-6016408839313382760?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/6016408839313382760/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=6016408839313382760' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/6016408839313382760'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/6016408839313382760'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2009/02/surakarta-is-now-available.html' title='Surakarta is now available'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4974525831038192874.post-5954047555552057669</id><published>2009-02-01T10:02:00.001-08:00</published><updated>2009-03-09T09:41:44.032-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Surakarta'/><title type='text'>Surakarta 1.0 Released</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_O9cQOCPATkA/SYXljHOTN3I/AAAAAAAAAKA/1vKBF2OO-0I/s1600-h/Picture+1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 222px; height: 320px;" src="http://3.bp.blogspot.com/_O9cQOCPATkA/SYXljHOTN3I/AAAAAAAAAKA/1vKBF2OO-0I/s320/Picture+1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5297892928068466546" /&gt;&lt;/a&gt;&lt;br /&gt;Today I'm releasing Surakarta v1.0 for the iPhone (and iPod Touch).  It's based on a traditional Javanese game and was included in "The Book of Classic Board Games" published by Klutz Press, under the name "Roundabouts."  It currently only has two-player mode, but I plan to release an upgrade to support single-player play (against a computer opponent) soon.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I hope you enjoy the game!  For feedback, please &lt;a href="mailto://bjhomer@gmail.com"&gt;e-mail me&lt;/a&gt;.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4974525831038192874-5954047555552057669?l=bjhomer.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bjhomer.blogspot.com/feeds/5954047555552057669/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4974525831038192874&amp;postID=5954047555552057669' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/5954047555552057669'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4974525831038192874/posts/default/5954047555552057669'/><link rel='alternate' type='text/html' href='http://bjhomer.blogspot.com/2009/02/surakarta-10-released.html' title='Surakarta 1.0 Released'/><author><name>BJ Homer</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://1.bp.blogspot.com/_O9cQOCPATkA/SOOLCgJ5DcI/AAAAAAAAAIg/IIFEeMFGbN0/S220/DSCN1945.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_O9cQOCPATkA/SYXljHOTN3I/AAAAAAAAAKA/1vKBF2OO-0I/s72-c/Picture+1.png' height='72' width='72'/><thr:total>0</thr:total></entry></feed>
