Wednesday, October 29, 2008
Saturday, October 18, 2008
jad WOSwitchComponent and walk through and explain to yourself why WOSwitchComponent doesn't show the same instance of a component for every session in your app, particularly given that there will only be one instance of a WOSwitchComponent on a given parsed page (meaning that if there's a WOSwitchComponent for an element in your Main component, there will only be ONE INSTANCE of WOSwitchComponent servicing that slot on the page for all of your users). Prepare to get a much deeper understanding and appreciation of how WO works and how clever the original authors were.
So the magic is WOComponentReference + WOComponentDefinition + WOComponent. What happens is that when your template is parsed, it gets parsed into a bunch of WOComponentReferences. If you understand WOAssociation, WOComponentReference is like the equivalent concept for components. That is, you have a handle that says "I represent an AjaxInPlace with a set of bindings" but it's not a stateful instance, it just describes how you're going to use it.
During parsing an entire tree of these is built up. It represents your parsed template without state. The sort of interesting part is that these things actually extend WODynamicElement, so they LOOK like components from the outside.
What happens is, and this is the kind of neat and clever part, is that at runtime, you say pageWithName("MyPage"). You get a new instance of the top level component. For each parsed element on the page (a WOComponentReference), the framework will walk down the tree and say "give me a stateful peer for this WOComponentReference for a component of this name, and use this set of bindings". It's here, btw, that it can decide whether subcomponents get new instances or come from the shared pool. Then the part that I DIDN'T know about -- the way these are connected is that the new instance of a child component is pushed into a dictionary in the WOComponent parent keyed off of element ID, so the framework can walk down the parsed tree and say to the parent, "give me your child component with the elementID x". If this component has already been materialized, the component will hand back a subcomponent from its dictionary. If one isn't there, it will make a new one and push it into the dictionary. So this starts to make sense, now, why you always get a new top level component instance -- it's the root of the state tree. There has to be a root node to hang all these stateful components off of. The magic method for all of this is actually WOComponentDefinition._componentInstanceInContext(context), btw.
So fundamentally, there's this stateless tree of elements that represents my entire parsed page and there's this stateful tree of WOComponent instances that represent the actual components of the page. So in normal components, the structure of your page is basically predetermined -- you have a bunch of WOConditionals, WOStrings, AjaxInPlace's, etc, all in a tree and page state determines which path through this graph you take. WOSwitchComponent, though, is a crazy twist on this. WOSwitchComponent DYNAMICALLY determines its page structure -- that is, the "parsed side" is indeterminate for a WOSwitchComponent. And if you look at WOSwitchComponent, this is exactly what it's doing. When you ask for a "SomeComponent", the cache doesn't return to you a WOComponent of the class you asked for, but instead a WOComponentReference -- those stateless handles. It's clever as hell. When it needs the component to work on, it looks up the correct instance of WOComponent based on the reference that it has cached (that is shared by everyone!) by asking the context().component() for a child with the current elementID, and will make one if it doesn't exist. So WOSwitchComponent takes advantage of this peer concept and basically builds this structure up itself on-the-fly. When this clicked for me, it was a "woah .. that's really slick" moment.
All of this finagling on my part was because I wanted to implement a new type of component that would allow you to, for instance, create a component on your page that would act like an "embedded page," where instead of having the result of invokeAction replace the entire page, it would instead just replace the embedded page component. To do this, you have to muck with page structure on-the-fly, which is pretty similar to what WOSwitchComponent does, but changing over time. So I just pulled up WOSwitchComponent and thought "I'll just base my code on this - -should be pretty easy" except that I looked at WOSwitchComponent and decided that it's impossible that it actually worked right :) But it does, and I began my quest to find out how :)
Incidentally, I THINK (haven't verified yet) that the third parameter to the constructor of a WODynamicElement, which I've always sort of wondered its exact purpose, is your stateless-parsed-element-peer. So you're being handed the reference to your stateless equivalent in the parse tree. This is my theory, at least. This gives you access to the structure of your children components, which during the R-R loop can also be used to access the stateful peers, etc.
So anyway ... That's the long story that probably nobody else cares about, but I think it's sort of a fascinating aspect of WO, because it's this crazy and clever underpinning that makes the entire framework function that I really had no idea about the inner workings of.
Thursday, October 9, 2008
There are two functions you can call for encoding UIImages on the iPhone -- UIImageJPEGRepresentation and UIImagePNGRepresentation. For my app, I was curious about the performance of the two image formats. I ran some tests on JPEG with various compression qualities, PNG, and Simulator vs iPhone 3G on 2.1.
The image tested was a 320x416 image (about 120k JPG @ max quality) of line drawing, repeated 10 times for each scenario and averaged. For the writing tests, the images were encoded and then written to disk. For the reading tests, the image is read and then drawn to an offscreen bitmap to make sure the bytes were actually read off the drive.
Note that q=0.0 is max compression, min quality; and q=1.0 is min compression, max quality.
jpg, q=0.000000, 0.018185 seconds avg
jpg, q=1.000000, 0.017662 seconds avg
png, 0.034456 seconds avg
jpg @ q=0.000000, 0.005052 seconds avg
jpg @ q=1.000000, 0.000838 seconds avg
png, 0.021330 seconds avg
iPhone 3G 2.1 Writing
jpg, q=0.000000, 0.255950 seconds avg
jpg, q=1.000000, 0.265089 seconds avg
png, 0.592170 seconds avg
iPhone 3G 2.1 Reading
jpg @ q=0.000000, 0.138772 seconds avg
jpg @ q=1.000000, 0.049013 seconds avg
png, 0.065347 seconds avg
In this test scenario, JPG writing is about 2.3 times faster than PNG writing on the 3G regardless of quality. On the flip side, JPG reading at max quality is 1.3x faster than PNG, but 2.1x SLOWER than PNG at max compression.
So if we take the best quality comparison, JPG is 2x faster to write and 1.3x faster to read. You'll have to compare image quality in your particular case, but JPG seems to win hands-down for me.
You can also look at the comparisons between iPhone and Simulator and prove to yourself that you should definitely not use the simulator to judge anything about runtime performance of your app on the real device, which I don't guess anyone REALLY had to tell you.