`
rensanning
  • 浏览: 3552555 次
  • 性别: Icon_minigender_1
  • 来自: 大连
博客专栏
Efef1dba-f7dd-3931-8a61-8e1c76c3e39f
使用Titanium Mo...
浏览量:38217
Bbab2146-6e1d-3c50-acd6-c8bae29e307d
Cordova 3.x入门...
浏览量:607590
C08766e7-8a33-3f9b-9155-654af05c3484
常用Java开源Libra...
浏览量:682773
77063fb3-0ee7-3bfa-9c72-2a0234ebf83e
搭建 CentOS 6 服...
浏览量:89527
E40e5e76-1f3b-398e-b6a6-dc9cfbb38156
Spring Boot 入...
浏览量:402129
Abe39461-b089-344f-99fa-cdfbddea0e18
基于Spring Secu...
浏览量:69759
66a41a70-fdf0-3dc9-aa31-19b7e8b24672
MQTT入门
浏览量:91834
社区版块
存档分类
最新评论

【转】How to (NOT to) build and structure large Titanium Mobile application

 
阅读更多
http://zenborgium.blogspot.com.au/2012/01/how-to-not-to-build-and-structure-large.html

Why to structure application

Well, you could put all source code files in same (root) application directory and everything would work fine. The thing here is ... when application grows, it becomes difficult to find particular file or to know where particular part of application is located if files in it are not organized in some meaningful way (for initial application developer, but also for all developers that will work on application at some time).

That's human nature. If you imagine large number of books, finding particular one can be difficult if they are not organized in any meaningful way. Search for particular book can be accelerated if books were sorted alphabetically. Human will search from left to right for first letter first and then for particular book. If books were sorted vertically in rows by first letter, human could find particular one even more faster because letters are clearly separated.

So, organizing things can be beneficial and the way they are organized is important.

That's why good application structure can be very useful. And not only application structure, but conventions in application also.


Why "NOT to"

The answer is simple. Latest versions of Titanium framework introduced CommonJS standard that brought some new functionality, while changed old. That's why this way of structuring application is no longer good (and won't work in future SDK versions), but similar principles can be translated to new, CommonJS, functionality (and I'll show how).


Basic concepts in my library

To understand application structure that I use, I need to explain some basic concepts that I use. This approach might not suit your needs, but it might give you some directions or ideas how to build your library for Titanium framework or how to structure your own application.

I'm using wrapped Object object as basic component. That component is basically container that has methods like add, remove, get, removeAll and getAll.

var component = Component();
var view = View();
component.add('MyView', view);
component.get('MyView'); //view


View is special component that is wrapper around Titanium UI View component. I wrote wrapper for every Titanium UI component (so, I don't use generic wrapper). Every wrapper can do everything what basic component can do, but beside that it proxies Titanium functionality (for example it adds or removes Titanium UI components in the background) and alters Titanium provided functionality where necessary (workarounds, simpler or enhanced usage, ...).

A good thing about this approach is that you can use this wrappers with native JS functionality without a fear that something will brake because they are native JS objects and not Titanium host objects. Titanium host objects in many cases do not behave like native JS objects and it's dangerous to do anything with them that is not documented. Also, this wrappers provide secure layer in cases when Titanium API changes or brakes (which is not so rare case). If you write code that depends directly on Titanium API, you could eventually end up in rewriting a lot of code. For example, "children" property in recent version was removed from some components. It is not so rare case that we need to get all child components of some component. If you depend directly on Titanium API, you would need to rewrite every part of your application that uses that property.

To simplify functionality of getting particular component, I add its reference as a property.

component.MyView // same as component.get('MyView')


I use string names to differentiate component instances of same type because it is easier to remember string component name, than integer index position in array of child components. Also, code that uses child component is not dependent on position in parent component so position can be changed without consequences.

I make component for everything that can be reused:

function Toolbar(config) {
    var view = View({ bottom: 0, height: '10%' });
    return view;
}

...
var toolbar = Toolbar();
toolbar.add('SaveButton', Button({ title: 'Save' });
toolbar.add('CancelButton', Button({ title: 'Cancel' });


"Toolbar" can again become some kind of component (everything is a component):

function ActionBar() {
    var toolbar = new Toolbar();
    toolbar.add('SaveButton', Button({ title: 'Save' });
    toolbar.add('CancelButton', Button({ title: 'Cancel' });
    return toolbar;
}


This approach is very similar to the one that can be found in EnyoJs (http://enyojs.com/). So, basic idea is to build small components first and then use them to build more and more complex components until whole UI is built. This approach has showed to be very flexible and scalable. Also, once all low level components are built, building higher level components is easy task.

I implemented some things that Titanium framework lacks, like FormView and GridView. FormView is awesome component, basically, every component it contains and that implements "setValue" or "getValue" method FormView can simply use to set its value and to get its value. That way I made that component very generic and it can use literally anything that somehow implements that methods (for example, I use table view in forms, table view is actually collection of data that can be dynamically added or removed). FormView component also implement that methods, so they can be nested.

function Login() {
    var formView = FormView();
    formView.add('Content', View());
    formView.add('Toolbar', ActionBar());
    var textField = TextField({ value: 'iskugor' }); //implements "getValue" and "setValue"
    formView.Content.add('UserName', textField).
    formView.getValue(); // returns { 'UserName': 'iskugor' }
    formView.setValue({ 'UserName': 'anonymous' }); //sets text field's value
    return formView;
}


Basic application would look like this:

//very simplified
var win = Context();
var form = Login();
win.add('Form', form);
win.open();


Context is window abstraction. It is different from all other components because it is important for memory management. Before 1.8 and especially before 1.7 SDK, Android part of Titanium was unstable and building something complex was mission impossible. Tom Cruise in me made this Context window abstraction that would remove all components and their events when window would close. From what it seemed to me at that time, in some cases, some event listeners were not removed so after window was re-created and re-opened, application would crash. By removing events manually, application would not crash. Also, at that time, many people had memory leak problem that sometimes just could not be solved even with some workarounds that came from community (workarounds that I tried did not work for me). By removing components manually, I did not have memory problems. From what my tests shows, 1.8 has much better memory and event handling and I actually don't use this features any more.

Removing components is easy task when there are wrappers that have "getAll" method, but what about listeners? In Titanium, event system is very basic and don't have much features. Also, Titanium component's "listeners" property was the only way to get list of all listeners, but it was unfortunately removed (partially, at least).

That's why I implemented my own publish/subscribe pattern.

Events.subscribe(button, 'click', function(e) {
     //do something
});


Internally, Events is a container that can register events, publish events, remove (all) events,  remove events by particular event type and so on.

Events.subscribe(button, 'CustomEvent', function(e) {
    Ti.alert('Custom event!');
});
Events.publish(button, 'CustomEvent');
Events.removeByEventName(button, 'click'); //removes all click events
Events.removeAll(button); //removes all events


In the background, I don't use "listeners" property, but my own container, similar to the one I use for basic component. Events are registered by event type and because of that it is easy to remove all events associated with particular event type. Also, to remove events by some event type, there is no need to store anonymous function anywhere (it is stored internally in "Events" container).

If first argument is omitted, app-level event is created. App-level events are dangerous to use because they they remain in memory all the time application runs and they can potentially create memory leaks, so functionality described above is great to have in cases then app-level events are used (all app-level events can be removed with one statement).

With all this functionality, removing events is trivial task.

Because I don't manually remove events when context closes anymore (starting from 1.8), some extra measures are needed to be done in this implementation to prevent memory leaks. I found that this event system is limited (and IMHO it forces some bad practices) and I have a design idea for new, more advanced, event system that would suit better to usage with my library.


Namespaces and naming convention

To easily name and find components, I split components to different namespaces. I have one root namespace that has subnamespaces.

Ns = {}; //root namespace
Ns.components = {}; //components namespace
Ns.components.ui = {}; //components ui namespace
Ns.components.filesystem = {}; // components filesystem namespace
Ns.events = {}; // events namespace


Concrete implementation of some component would be added to particular namespace where it belongs:

Ns.components.ui.Toolbar = function() {
    ...
};


Component name, as every other function constructor, starts with capital letter, while namespace names are lower case.

I made convention that component's code is located in one file and that component namespace name is mapped to file path relative to "Resources" directory and file name. For example Toolbar component's code would be stored in toolbar.js file. Since Toolbar is in "components.ui namespace",  toolbar.js file would be located in "Resources/components/ui" directory (FormView component's file name would be form_view.js).

I never put concrete, instantiated component to namespace, because that's a bad practice that might work on iOS, but brakes on Android. Using one component, or even more, adding one component to different windows (even if that doesn't happen in the same time) can have strange side effects. The best approach is to create twin-component whenever it is needed by using function constructor.

To load particular component, I use custom loader:

Ns.require('Ns.components.ui.Toolbar');


That method first checks if component is already present in the namespace, if not it includes it:

Ti. include('/components/ui/toolbar.js');


Every component can have default preferences, which I store to global configuration object (although, code is located in component's file):

Ns.config.add('Toolbar', {
    height: '10%',
    bottom: 0,
    layout: 'horizontal'
});


which is usually merged with configuration object passed to component's function constructor:

Ns.components.ui.Toolbar = function(configuration) {
    var config = Ns.config.merge(configuration, Ns.config.get('Toolbar'));
    var component = View(config);
    return component;
};


That way other components can change default behavior of some component by passing configuration object modifications as function parameter.


MVC

To separate concerns and to make simple implementation of MVC pattern, I separated how components are defined in the interface and how do they behave (this is actually very useful when one component is used in different situations and therefore they have modified behavior but same appearance).

To do this, I enhanced some Titanium wrappers so they can automatically add other components through configuration object. That is done by convention, configuration object can have special "items" property which defines components that will be automatically added:

var actionBar = Ns.components.ui.View({
    height: '10%',
    bottom: 0,
    layout: 'horizontal',
    items: {
        'SaveButton': function() {
            return Ns.components.ui.Button({ title: 'Save' });
         },
        'CancelButton': function() {
            return Ns.components.ui.Button({ title: 'Cancel' });
         }
    }
});
actionBar.SaveButton; //button 


This basically is used for "View" part of MVC.

"View" is defined through configuration object defined for every component.

For example, "View" part of ActionBar component would be:

Ns.config.add('ActionBar': {
    height: '10%',
    bottom: 0,
    layout: 'horizontal',
    items: {
        SaveButton: function() {
            return Ns.components.ui.Button({ title: 'Save' });
        },
        CancelButton: function() {
            return Ns.components.ui.Button({ title: 'Cancel' });
        }
    }
});

Ns.components.ui.ActionBar = function(configuration) {
    var config = Ns.config.merge(configuration, Ns.config.get('ActionBar'));
    return Ns.components.ui.Toolbar(config);
};


To make Controller part of MVC, View part has to do something. I defined that before in when I described "Events":

Events.subscribe(form.Toolbar.SaveButton, 'click', function(e) {
    Ti.alert('Username: ' + form.getValue().UserName);
             //do something with login data 
});


Besides interaction, Controller is responsible for component construction, parameter passing, gluing components together, passing data to View, getting data for View and so on. Controller is what gives life to View, View is stupid as it can be (it's just definition through configuration object).

"Model" part is up to you.

I use modified version of Joli ORM (https://github.com/xavierlacot/joli.js/) and Sqlite for database models.


Application structure

Application structure has three main parts: library, components and modules.

In library, there are all common things, like functionality for including components, creating namespaces, events system definition, database ORM and so on.

In components there are all general components that have no specific data in them. That components can be reused in multiple projects. For example, there are UI components (Button, Toolbar, FormView), filesystem component, XML component, ...

引用
/components/ui/button.js


In modules there are all project-specific components with data in them grouped into modules and their submodules where necessary (for big modules it's handy to group them into submodules). This components are general components with specific data in them. For example, if Button is general component, specific component would be LoginButton which is Button component with specific title.

引用
/modules/user/ui/login_button.js


View part is stored in "ui" namespace, while Controller is stored in module's root directory. Beside that two, I store models in module's directory also.

引用
/modules/user/models/db/users.js


Common practice that many people follow is not to separate this two kinds of components (with and without specific data), but I think it is better to separate them. If you mix project specific data with general components, then it will not be possible to reuse them in future projects. By separating them clearly, you can just copy components to new project (or link common library when that functionality become available in TiStudio) and start working on project specific modules.
 

Translation to CommonJS

I'll show you how I easily translated to CommonJS standard from this namespace-based application structure.

The biggest problem in CommonJS implementation in Titanium framework is that it does not have global variable implementation. As you might have noticed, namespace object in my application is stored in global variable which is accessible everywhere in the application.

Other problem is that CommonJS standard forces specific syntax.

exports.ModuleName = function() {};
//or
module.exports = {};


Other things are pretty much the same.

To translate from namespace syntax to CommonJS syntax I had to do two things. First was to use regular expression to replace all this kind of patterns:

引用
Ns.components.ui.ComponentName

to
引用
exports.ComponentName


Second, I had to modify my "Ns.require" method so it does not use "Ti.include", but "require" instead. The trick I used to do that coincidentally was basic idea that I used to solve problem of global variables.
"Ti.include" basically copy-pastes source code, while "require" returns what module exports. The trick here is to simulate what "Ti.include" was doing by using "require" function. That is, I just add module exports to global namespace object. The only thing that needed to be fixed was to "require" this global object on top of each module's file:

Ns = require('library/ns');


And that was it.

That is still not real CommonJS approach, but the thing here was following: on Android, "Ti.include" did not work inside functions (while "require" did when it was introduced), so everything had to be preloaded in the application (there was no way to include file on demand). For large project this was a disaster, I had to wait 10 seconds just to start application. Add 10 seconds of compilation time to that and you'll understand the secret of my high position in Appcelerator's Q/A.

I still don't use CommonJS in real sense, but I am slowly refactoring my application. For example, now I don't store configuration objects in global object, but in module's private variable.

var defaultConf = {
    ... 
};

exports.ComponentConstructor = function(configuration) {
    //merge
};



Final words

Although this approach is very nice in many cases, I found it limited in some ways. I miss some things in it, some things would be better if they were made in a different way.

Also, it's not the optimal. When building complex things for mobile devices, it is important to be aware of performance of your code from the beginning. Optimizing server is easy, you just buy more/better hardware. But you can't do that on mobile devices and with application size increasing, micro-optimizations are more and more noticeable (you will never, and not even then, do micro-optimization of server code).

After one year of programming with Titanium I have found two things.

To know how to program with Titanium, you must know JavaScript that is defined by ECMAScript specification (forget your jQuery skills). Basic JS skills are the best investment in your knowledge of Titanium.

After one year of intensive JS programming I have found that I don't know to think in JS (good thing is that now I am aware of that).
分享到:
评论

相关推荐

    Titanium Mobile API

    ### Titanium Mobile API 知识点详解 #### 一、Titanium Mobile API 概述 Titanium Mobile API 是一款由 Appcelerator 公司提供的用于跨平台移动应用开发的强大工具包。该工具允许开发者使用 JavaScript 编写应用...

    Titanium Mobile SDK 3.1.0 Apidoc 离线版

    Titanium Mobile SDK 3.1.0 是一个用于构建原生移动应用的开发工具,尤其针对iOS和Android平台。这个版本的Apidoc是开发者的重要参考资料,它包含了完整的API文档,帮助开发者理解并使用Titanium框架的各种功能。...

    TiInspector, 通过 Chrome DevTools调试 Titanium Mobile 应用程序.zip

    TiInspector, 通过 Chrome DevTools调试 Titanium Mobile 应用程序 #Ti 检查器Ti检查器允许在 Chrome DevTools web界面中调试 Appcelerator Titanium 应用程序。工具通过将命令和消息转换为 Chrome 调试协议和 ...

    [Titanium] Appcelerator Titanium 移动应用开发教程 (英文版)

    Develop fully-featured mobile applications using a hands-on approach, and get inspired to develop more Overview Walk through the development of ten different mobile applications by leveraging your ...

    Titanium Mobile API 1.8.2.chm

    Titanium Mobile API 1.8.2.chm 内容很好但是英文版的,E不好的童鞋慎下

    Titanium Mobile API的chm文件制作思路

    Titanium Mobile API是用于开发跨平台移动应用的框架,它基于JavaScript,允许开发者用一种语言创建iOS和Android应用。这个框架提供了丰富的API,使得开发者能够访问设备的各种功能,如GPS、摄像头、网络通信等。本...

    TiJSPDF, Titanium Mobile 应用程序的JSPDF插件.zip

    TiJSPDF, Titanium Mobile 应用程序的JSPDF插件 TiJSPDFTiJSPDF是用于使用JSPDF库生成pdf并在 Titanium Mobile 应用程序中操作它们的Titanium Mobile 应用程序的一种 JSPDF插件插件。这个插件独立于 Titanium 维护,...

    TitanTricks, Titanium Mobile的代码示例和组件.zip

    TitanTricks, Titanium Mobile的代码示例和组件 TitanTricks自述文件TitanTricks是 Titanium Mobile 项目,充满了可以重用的代码示例和技巧,面向begginers和中级级别用户。android和iOS兼容,但有些示例仅适用于 ...

    Android-titanium_mobile.zip

    Android-titanium_mobile.zip,带有javascript的本地ios、android和windows应用程序,安卓系统是谷歌在2008年设计和制造的。操作系统主要写在爪哇,C和C 的核心组件。它是在linux内核之上构建的,具有安全性优势。

    ti.mely, Titanium Mobile的Ti.mely 本机定时器.zip

    ti.mely, Titanium Mobile的Ti.mely 本机定时器 Ti.melyTi.mely 项目提供对Android和iOS计时器的访问。在开始之前* 是在使用这个模块之前,设计用于 Titanium SDK 3.1.1. GA *的iOS和Android本机模块。 如果你需要...

    Influence of Titanium Doping on the Structure and Morphology of MgO prepared by Coprecipitation Method

    Influence of Titanium Doping on the Structure and Morphology of MgO prepared by Coprecipitation Method,王维,乔学亮,Ti-doped magnesium oxide powders were prepared by a chemical coprecipitation ...

    sublimetext-tita, Tita Titanium Mobile/Alloy sublime text 2插件.zip

    sublimetext-tita, Tita Titanium Mobile/Alloy sublime text 2插件 这个插件不再主动地维护 ***因为我不再使用 st2/st3 。 有人对 fork 感兴趣并维护它?替代插件:...

    Building Mobile Applications with Titanium

    Building Mobile Applications with Titanium, 这是mobi版,适合knidle等电子书阅读。 详细介绍了titanium框架的使用

    titanium_mobile_tizen

    适用于 Tizen 的 Titanium Mobile 欢迎来到 Appcelerator Titanium Mobile 开源项目。 Titanium 为 Web 开发人员提供了一个使用 JavaScript 构建跨平台原生移动应用程序的平台。 有关 Titanium 的更多信息,请访问 ...

    titanium-typescript:TypeScript 的 Titanium Mobile 类型

    Titanium Appcelerator API v3.1.3.GA 的环境声明。 var ROWS: number = 10; var children: Array<Titanium> = button.getChildren(); var window: Titanium.UI.Window = Titanium.UI.createWindow({ title: '...

    ecm_titanium_1.61_www.dpfegr.ru.rar

    interface to create and manage projects for ECM Tuning V1.61 modified files. After reading the file stored in the ECU memory, all you have to do is upload the file and the software starts a search ...

    ASTM B861 ASME SB 861-2010 Titanium and Titanium Alloy

    ASTM B861 ASME SB 861-2010 Titanium and Titanium Alloy

    haxe_titanium_mobile:Appcelerator Titanium Mobile 的 Haxe Extern 类

    要安装到 haxelib,请运行haxelib git titanium_mobile_externs https://github.com/momer/haxe_titanium_mobile.git <version> haxelib 截至本次更新,最新为 3.5.0-rc1。 使用 externs 很简单,下面是test/Test...

Global site tag (gtag.js) - Google Analytics