XCode localization Reminder.
Just a quick reminder for myself for creating localization files in the terminal for xcode projects.
cd to project directory.
find . -name \*.m | xargs genstrings -o en.lproj
The above will find all .m files in all subdirectories and extract the NSLocalizedString().
Hints and Tips : Playbook default wallpapers.
New Post over at Liquid-photo
Hints and Tips : Playbook default wallpapers.
Native Extension Quick Tip : Convert Objectiv-C into ActionScript Objects
New Post over at Liquid-photo
Native Extension Quick Tip : Convert Objectiv-C into ActionScript Objects
Performance Improvements and Optimisations
To kick off our series on how we optimised and improved Liquid-Photo 4.0 I wanted to first discuss some of the issues we were facing and why.
When starting out to build Liquid-photo we decided to use ActionScript and the Air runtime. This decision was mostly due to our experience in ActionScript and with the Air runtime as a whole, however it also has the added benefit that we can, if we wish port the application more easily to other platforms such as iOS and Android.
Whilst development of the first iteration of the application was quick and we were able to get a finished application onto the Blackberry AppWorld within a month. However we soon started receiving information from customers that the application was not running at an acceptable frame rate. This lead us to re-look at the way in which the application was designed and how we could go about improving the speed and responsiveness of the application.
We are going to cover several topics over the next few days to highlight how we improved the newest release of Liquid-Photo.
- Persisting Data with parsley
- Optimising ItemRenderer's for TileLayouts
- Using GreenThreads to improve user interaction
Once these posts are finished we will also discuss some of the other techniques used for creating the application.
We hope that you find some of these tips useful.
Persisting Data with parsley
The first iteration of Liquid-Photo did not save any user data, once it was closed that was it every setting had to be re-set on startup and the users images all had to be re-loaded. This obviously was not an ideal solution. In comes Parsley Persistence Capabilities.
For those that do not know what Parsley is a brief explanation may be in order. Parsley in simple terms is an ActionScript IOC(Inversion Of Control) Dependency Injection Framework. That said it has a wealth of features that make it ideal for any size project from the simplest PlayBook Application to some of the largest Enterprise Applications in the world and I personally have used it on both.
I will not go into how to setup a project using Parsley as there are lots of examples and documentation over at the Parsley website, what we will look at here is firstly how to save some simple users settings using the default Parsley persistence mechanisms and then a slightly more complex version in which we show how to save the current application view state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package com.liquidphoto.settings.user { public class EffectSettings { //---------------------------------------------------------------------- // // Public Properties. // //---------------------------------------------------------------------- [PublishSubscribe(persistent="true", objectId="effectSettings.playEffects")] [Bindable] public var playEffects:Boolean; [PublishSubscribe(persistent="true", objectId="effectSettings.intensity")] [Bindable] public var intensity:Number; [PublishSubscribe(persistent="true", objectId="effectSettings.initialised")] [Bindable] public var initialised:Boolean; //---------------------------------------------------------------------- // // Public Methods. // //---------------------------------------------------------------------- [Init] /** * Initialise the settings. * This will be automatically called by Parsley * when this class is created. */ public function init():void { if (!initialised) { reset(); } } /** * Reset the settings to the initial values. */ public function reset():void { play = true; intensity = 6; initialised; } } } |
Ok so what is going on here? Firstly we setup some public properties that we want to persist to the local shared object on the device. We mark each one as [Bindable] and also add a Parsley [PublishSubscribe] metadata tag. By setting the persistent="true" value on the tag we tell parsley that every time this value changes persist that change to the local shared object and the next time the application is started parsley will then populate this variable with the value it persisted perviously. The important thing to note here is the use of the objectId property, You must set this property to something unique if you are persisting simple types like Numbers, Booleans etc.. If you do not then every variable that persists the same type of value without an objectId will be replaced with the last saved value.
For example the two variables initialised and playEffects above both have Boolean values, if they did not have any objectId properties then whenever the playEffects value was changed and persisted that value would also be set on the initialised value the next time the application was started.
Next is the Parsley [Init] tag which gets called as soon as Parsley instantiates this class, The first time this is run the initialised value is false so it runs the reset method which sets up our applications default settings, when it sets these they are then persisted to the Shared Object so the next time the application runs the same values are set on the values by parsley. This means that if the user changes any user settings the bindings will be fired and Parsley will persist the users changes for the next time the user opens the application.
Finally we have a reset method so that we can reset the values back to the defaults if we need to.
Now lets look at a more complex situation persisting user views.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
package com.liquidphoto.persistence { import mx.utils.NameUtil; import flash.utils.getDefinitionByName; public class NavigationPersistence { //---------------------------------------------------------------------- // // Constants // //---------------------------------------------------------------------- private static const LOG:Logger = LogContext.getLogger(NavigationPersistence); //---------------------------------------------------------------------- // // Public Properties. // //---------------------------------------------------------------------- [Inject] public var navController:NavigationController; [PublishSubscribe(persistent="true", objectId="navigation")] [Bindable] public var persistedViews:Array; //---------------------------------------------------------------------- // // Public Methods. // //---------------------------------------------------------------------- [Init] public function init():void { //Setup our application close handlers. setupCloseHandlers(); //If we have some persisted data try to push it to our navigation controller. if (persistedViews && persistedViews.length > 0) { var viewClass:Class; for each (var view:String in persistedViews) { try { //Try to get our persisted view class viewClass = getDefinitionByName(view) as Class; navController.pushView(viewClass); } catch (error:ReferenceError) { LOG.fatal("Applicaiton persisted view could not be found! {0} Starting over from the begining.", view); navController.pushView(SplashScreen); } } LOG.debug("Rebuilding persisted views {0}", persistedViews); } else { //No views to persist so load our splashscreen. navController.pushView(SplashScreen); } } //---------------------------------------------------------------------- // // Private methods. // //---------------------------------------------------------------------- private function setupCloseHandlers():void { //Listen for application exiting. NativeApplication.nativeApplication.addEventListener(Event.EXITING, nativeApp_exitingHandler); } private function nativeApp_exitingHandler(event:Event):void { var persistViews:Array = []; for each (var item:* in navController.stack) { persistViews.push(NameUtil.getUnqualifiedClassName(item)); } persistedViews = persistViews; LOG.debug("Persisting views {0}", persistedViews); } } } |
So to begin with we setup a simple logger so we can see what classes we are persisting and pushing to our navigationController, ( Our NavigationController is a simple class that instantiates the views pushed to it and pushes them to the main application view ). Using the Parsley [Init] metadata, when this class is instantiated by Parsley we first setup our event listeners to detect for when the application is about to close, we could persist this data constantly as our application changed but for our use case we can simply do it as the application is closing.
If we now look at the nativeApp_exitingHandler() method we can see that we are simply building an array of class names and pushing them to the persistedViews array. Now if we go back to the init() method we can see that if there are persistedViews we loop through them pushing the classes to the navigationController, if one of the persisted views is invalid then we fall back to displaying our SplashScreen.
And that is all that is required to use Parsley for persisting our application state.
In the next post we will be discussing Optimising ItemRenderer's for TileLayouts and how we reduced the TileLayout render time from over 10 seconds down to less than 900 milliseconds!
Hints and Tips: Screen Grabs
Hold down both the sound increase and decrease buttons at the same time to take a screenshot of the application.



