If you're an iOS dev who wants to be mentored for a few months by a former UIKit author, hit me up. :) andy@khanacademy.org
567 karma · joined April 19, 2011
Working on tools that expand what people can think and do.
Past: led R&D at Khan Academy, helped build iOS at Apple.
http://andymatuschak.org
[ my public key: https://keybase.io/andymatuschak; my proof: https://keybase.io/andymatuschak/sigs/cXR2O704tD-HTFzScbqwt5b81_3bhHaU-xHGAHmHkfc ]
If you're an iOS dev who wants to be mentored for a few months by a former UIKit author, hit me up. :) andy@khanacademy.org
You should read the whole article, but to motivate:
> This post was triggered by Kent's comment about triply-nested mocks. I doubt that he intended to claim that mocking three levels deep is inherent to, or even common in, isolated testing. However, many others have proposed exactly that straw man argument. That argument misrepresents isolated testing to discredit it; it presents deep mocks, which are to be avoided in isolated testing, as being fundamental to it; it's fallacious. It's at the root of the claim that mocking inherently makes tests fragile and refactoring difficult. That's very true of deep mocks, but not very true of mock-based isolation done well, and certainly isn't true of isolation done without mocks.
> In a very real sense, isolated testing done test-first exposes design mistakes before they're made. It translates coupling distributed throughout the module into mock setup centralized in the test, and it does that before the coupling is even written down. With practice, that preemptive design feedback becomes internalized in the programmer, granting some of the benefit even when not writing tests.
Studying SICP (or anything) can involve different "modes":
Reading and doing exercises will often be solitary. Q&A could definitely be asynchronous (IRC works great for that!). But we're talking about explaining what you've learned; only in synchronous conversation will you get the back-and-forth "wait what? How's that work?"
A hard part of learning is figuring out exactly what you don't know; conversation is remarkably effective at elucidating that. Where do you feel uncomfortable while speaking? Where does your partner look especially confused or skeptical? The human connection gives you expression, body language, tone of voice, etc. in a quick feedback loop.
(disclaimer: not involved in creating Understudy, but I'm a happy user!)
But here's probably the next thing I'd address: http://en.m.wikipedia.org/wiki/Gimbal_lock
That defeats some of the purpose of monad as general abstraction. One of the big ideas here, as far as I'm concerned, is that monads represent an abstraction over a common structure of programs. Lists, Maybes, threading models, etc, all share this same language of structuring computations. This is significant because it means that one can build functions which manipulate those structures, and then those structures can be deployed generically. Depending on how you're counting, that means third-order abstraction.
For instance, consider liftM from Control.Monad:
-- | Promote a function to a monad.
liftM :: (Monad m) => (a1 -> r) -> m a1 -> m r
liftM f m1 = do { x1 <- m1; return (f x1) }
This represents a common idea across e.g. both List and Maybe. I've got a function of values. I want to turn it into a function of Lists / Maybes. Well, liftM recognizes the similarity of List's and Maybe's structure and provides a single implementation suitable for both places.The decorator described in this article cannot function at liftM's level of abstraction: at least as far as I can tell (as a non-Python programmer), it requires a monad type parameter. If that's true, I think it's missing a key idea behind the power of monads.
For more about this stuff, check out the two WWDC sessions I mentioned above.
To make a resizable image at runtime, draw into an image as described in approach #2, but make the image have width of left cap + 1 point + right cap, then use -[UIImage resizableImageWithCapInsets:] to generate a wrapper with the correct resizing behavior.
Then: make sure that if you have 100 buttons on-screen which all use the same gradients, you end up reusing the same generated UIImage. You don't want to redraw the same thing for each of them.
If you're using a masked CALayer, and that layer's in a scroll view or is otherwise animating around the screen, there's a very real chance that you'll drop frames, just from that layer's off-screen rendering pass. Depends on how big the button is.
Certainly, though, if you need parameterizable imagery, you need parameterizable imagery, and so you can't load them from disk. But you can still make your runtime-generated images resizable!
The resizable image background technique will be the fastest option (provided you don't need to programmatically tweak the appearance). Next would be the full-sized background (which needlessly wastes memory). Then the CG-based approach, which could actually perform better than the full-sized background if you had generated a resizable image—the technique presented here needlessly wastes memory and CPU time by generating a full-width image. But it will still perform worse in basically all cases than the resizable-image-from-disk technique, so you would only want to do that if the parameters need to be tweaked at runtime.
The CAGradientLayer-based approach (as written) is a very poor idea unless you need the animated transitions because it requires extremely expensive off-screen drawing passes due to the masking. If your situation permits you to use "overdrawn" masking (as I discussed in WWDC 2012's "Polishing Your Rotation Animations"), this would actually perform quite well—less memory consumption than all the other options; small rendering cost each frame. See also WWDC 2011's "Understanding UIKit Rendering" for more on graphics performance with UIKit.
I guess it's also worth noting that the cost of masking CALayers with a borderRadius is much lower in iOS 6 than iOS 5, but don't go nuts: it's still way higher than all these other options.
The app, as it stands, does not behave like any of the others on my screen. This greatly increases the startup cost for me, since it doesn't follow the idioms I've already learned (and which I'm concurrently employing in these other apps).
For instance, the code editor doesn't behave like any other text editor on my screen (including the Xcode and TextMate source editors open alongside): Ctrl+Arrow doesn't navigate; double-click-and-drag doesn't select by word; my services are not available in the contextual menu; etc.
The keyboard shortcut for "change tab" that works in all the other tabbed windows on my screen does not work in this interface. The same goes for zooming shortcuts, "find" within the source editor, etc.
Then there are more fundamental disconnects: there's a "save" button in the interface, for example. That's an extremely rare thing to see in a Mac app. In fact, as of Lion, the user should never have to explicitly save.
Now, of course, building a native UI layer (in terms of design, not just ABI) would be a great deal of work. You could reasonably argue that it wouldn't be worth that work, but I'd just like to point out that it would indeed provide a much better experience.