没有 nib 的 iphone 程序

Interface Builder 开始用还觉得酷炫浮华,越用越觉得不对劲,对于刚开始学写 iphone 程序,没有帮助理解代码,反而隐藏了需要知道的内容,然后用线连啊连,连着连着就晕了。另外,重点是,牛人都直接用代码写 GUI 的啊 XD。刚才试了一下不用 nib,只用敲 code,真的可以耶。步骤如下:

1. 新建一个 Window-Based Application 项目

2009-01-07_1714

2. 删掉

1
MainWindow.xib

文件

2009-01-07_1720

3. 删掉

1
Info.plist

里面的

1
Main nib file base name

属性

2009-01-07_1720_1

4. 把

1
main.m

文件中的

1
UIApplicationMain

函数最后一个参数由

1
nil

改为 

1
@"NiblessHelloWorldAppDelegate"

也就是我们的主要 delegate 的类名字

2009-01-07_1722

5. 把

1
NiblessHelloWorldAppAppDelegate.h

文件里的

1
IBOutlet

声明删掉(不删也行)

2009-01-07_1722_1

6. 在

1
NiblessHelloWorldAppDelegate.m

的程序启动回掉函数里加上我们自己创建 window 以及 view 的代码

2009-01-07_1723

7. Build And Go Go Go。。。

2009-01-07_1739

项目文件打包在这里,请自取。。。

Cairngorm In One File

最近有机会研究了一下传说中的 Cairngorm 框架,整体感觉还不错,其中的模块化概念可以使项目在变大变复杂之后依然可控。

具体优点就不说了,说说最大的缺点,就是 繁琐 。由于要分离用户触发事件以及对应的 Command 操作,导致几乎每个明显的用户操作都要写一个 Event 类(还真是累阿);另外由于 Cairngorm 本身是一个 design patten 的实践,不是 library,所以造成实际使用的时候需要死版的 implement 一些不着调的空 interface。

简单来说 Cairngorm 是一个婆婆妈妈的 framework,这从技术上来说也不是什么问题,但是重点是会影响写代码人的心情。比如我,一想到要每一个 action 都要写一个没什么特别之处的 Event class 就觉得头疼,一头疼就不想干,一不想干就完了。。。

所以,我就想能不能保留 Cairngorm 的思想,但是用一种更简洁的方式来实现?最后不小心想出了一招,经过一段时间的实验,感觉效果还不错,和大家分享一蛤。

global.as

我把 Cairngorm 的思想融入到一个名叫 global 的类中,使用的时候只需要这一个文件就包含了全部 Cairngorm 相关内容。我打算用一个通过 webservices 查询当前货币兑换比例的例子来具体的说一下 Cairngorm 各方面的特性,以及如何用 global 来实现这些特性。

全局的数据管理

Cairngorm 通过一个 singleton 的 ModelLocator 来连接数据与用户界面,基本思路是全部 ui 相关的数据都不是直接设置到组件上,而是通过一个全局的对象借由 binding 来实现。这样做分离了组件、数据以及命令之间的依靠。当有某个命令修改了某一个数据源之后,不用主动的再去更新组件显示,所有已经设置对应 binding 的组件将会自动更新。

global.as 将自身作为 ModelLocator 来使用,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// bindable properties, access from global.data model locator.
public var fromCurrency:String=“USD”;
public var toCurrency:String=“CNY”;
public var currencyResult:String=“”;
public var state:String=STATE_LOAD;
public var enabled:Boolean=true;
/**
 * The global singleton model locator.
 */

public static function get data():global
{
    return _data;
}

然后在对应需要响应数据更新的组件上通过绑定 global.data 中的对应属性来实现更新。比如实例中用来显示货币兑换比例结果的 Label 组件绑定了 global.data.currencyResult 属性。

1
<mx:Label text="{global.data.currencyResult}" color="0x00ff00"></mx:label>

全局的事件播报以及响应

在 Cairngorm 中,程序通过全局的 EventDispatcher 来播报全部程序相关的事件,然后对应处理事件的 Command 则通过 FrontController 在程序初始阶段配置好与对应事件的监听关系。副作用是:随着程序复杂程度的增加,会出现一堆只是名字不同的 Event class,以及只为了 execute 一下下却不得不实现的 ICommand 类。

global.as 通过静态方法 fire/listen/unlisten 实现了全局的事件播报以及监听功能,其中为了避免过多 Event class 的出现,fire 方法会播报出 DynamicEvent,并允许设置任意参数到 event 对象上,在实例中当用户点击了 Calculate Conversion Rate 按钮后会这样触发事件

1
click="global.fire(global.EVENT_CONVERSION_RATE,{fromCurrency:fromField.text,toCurrency:toField.text})"

另一方面 command 响应事件,也做了相应的简化:直接用 function 作为 command 来进行实际的业务操作:

1
2
3
4
5
6
7
public function conversionRate(e:DynamicEvent):void
{
    //..
    var fromCurrency:String=String(e.fromCurrency);
    var toCurrency:String=String(e.toCurrency);
    //..
}

最后在程序初始化的时候将 command 和对应的事件建立联系(类似 Cairngorm 中 FrontController 的功能)

1
2
3
4
5
6
function registerDefaultCommands():void
{
    //register default commands here.
    global.listen(global.EVENT_LOAD_WSDL,loadWsdl);
    global.listen(global.EVENT_CONVERSION_RATE,conversionRate);
}

全局的网络服务资源

最后关于远程 service 的部分,Cairngorm 提供了另外一个 singleton 的 ServiceLocator 供程序使用,并通过 Responder 来响应服务返回的结果。

在 global.as 中通过一个 rpc 方法实现了注册以及使用 service 的功能。比如实例中 webservices 的注册如下:

1
2
3
4
5
6
7
function registerDefaultServices():void
{
    //register default services here.
    var s:WebService=new WebService();
    //..
    global.rpc(global.CONVERSION_DESTINATION,s);
}

然后代码通过以下方法访问上面注册的 service:

1
2
3
4
public static function get currencyConvertor():*
{
    return rpc(CONVERSION_DESTINATION);
}

实际上 rpc 方法返回的是一个 service 的 proxy 对象。和直接使用 service 不同的是,通过 proxy 简化了服务方法调用后结果响应部分的代码。比如实例中获取货币兑换比率的方法是这样写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
global.currencyConvertor.ConversionRate(fromCurrency,toCurrency)(
    function(data:Object):void
    {
        global.data.currencyResult=data.result.toString();
    },
    function(info:Object):void
    {
        Alert.show(info.fault.message,info.fault.faultString);
    },
    function(o:*):void
    {
        global.data.enabled=true;
    });

其中第一个函数处理成功,第二个函数处理失败,第三个函数无论成功失败都会先执行。

至此 Cairngorm 的全部功能都被 global.as 实现了。如果有盆友对我瞎掰的这些感兴趣,可以 下载 sample source 然后把你的想法告诉我。

Over :P

Spring MVC 中的中文编码问题小说一蛤

原来以为 Java 中文支持的会很好,没想到一用 Spring 就出乱码啦,看来人家默认情况下都不吊你第三世界国家的。。。

anyway 切入正题,我股沟了一下相关的内容,发现大多数解决方法都是加个 servlet filter 来转换编码。觉得这方法不好,好像虽然有用但是比较糊弄事,不彻底明白始终不爽。所以我就在源代码里游啊游,整理出大概的思路,如下:

页面显示中文

这部分以 jsp 为例, Spring MVC 在 resolve 页面显示的时候是这么一个流程。

InternalResourceViewResolver 利用 InternalResourceView 来 build view。 InternalResourceView 则会将最终页面生成的请求转交给对应的 jsp 来通过 container 实现。

如果页面显示有乱码,问题貌似会出在 InternalResourceView 上,实际上 InternalResourceViewResolver 的父类中有一个叫 UrlBasedViewResolver 的东东,里面有个属性叫做 contentType,并且会在 buid view 的时候最终把这个属性赋值给 InternalResourceView 。貌似找到了问题的原因。我试着这样设置并窃喜:

1
2
3
4
5
<bean id="jspViewResolver"
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="contentType" value="utf-8"></property>
    ...
</bean>

页面刷新后乱码依旧。。。回到源文件中,游啊游,发现其实 InternalResourceViewResolver 虽然有给 InternalResourceView 赋值 contentType,但是 InternalResourceView 忽视了这个属性。。。然后直接转交给对应的 jsp 处理。看来问题实际上出在 jsp 上。到对应的 jsp 页面,加上下面的代码:

1
<%@ page pageEncoding="UTF-8" contentType="text/html;charset=UTF-8"%>

然后在编辑器里确保源文件也存成 utf-8,然后刷新页面,好了。

h3. 提交中文参数

原以为这样就 OK 了,后来发现还没完。。。在表单提交的时候,中文依然乱码,sun。再游! 发现要改的地方有两点,第一个是针对表单的 POST,因为默认的 request body 处理的编码不是 utf-8 所以在 decode body 的时候会解出乱码来。这需要写个Interceptor 来设置 request 的 encoding ,比如:

1
2
3
4
5
6
7
8
9
10
11
public class RequestEncodingInterceptor implements HandlerInterceptor
{
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception
    {
        request.setCharacterEncoding("utf-8");
        return true;
    }
    ...
}

然后挂在对应的 handlerMapping 上面,比如:

1
2
3
4
5
6
7
8
9
<bean id="handlerMapping">
        class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <bean class="RequestEncodingInterceptor">
        </list>
    </property>
    ...
</bean>

另一方面,如果 url get 中有 encode 之后的中文参数,还需要确保 url decode 也用 utf-8 来解。由于 BeanNameUrlHandlerMapping 通过 UrlPathHelper 来处理 url,所以需要这样配置:

1
2
3
4
5
6
7
8
9
10
<bean id="handlerMapping"
    class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
    ....
    <property name="urlPathHelper">
        <bean class="org.springframework.web.util.UrlPathHelper">
            <property name="urlDecode" value="true"></property>
            <property name="defaultEncoding" value="utf-8"></property>
        </bean>
    </property>
</bean>

国际化 i18n

国际化的配置文件如果用 java 的 properties 文件是不支持 utf-8 滴,要换成 Spring 的 ReloadableResourceBundleMessageSource ,酱紫配置:

1
2
3
4
5
<bean id="messageSource">
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basename" value="WEB-INF/i18n/messages"></property>
    <property name="defaultEncoding" value="utf-8"></property>
</bean>

搞定!(貌似?)

如何去掉 Flash Player 8 中那个烦人的安全设置窗口

Flash Player 8 最让人郁闷的就是那个新的安全机制。所有访问网络资源的程序如果通过在桌面上双击swf来运行,都会无一幸免的弹出这个对话框。

实际上新的安全机制没有错,错就错在那个安全设置对话框太招人烦。如果你不想看繁琐的文档,又想让整个世界清静,以下是几种解决办法(任意一种既可)。

  • 通过 Settings Manager 设置。这个就是方法就是通过点击弹出的安全对话框中的设置按钮来添加Local-trusted位置。这个方法我个人感觉严重影响用户体验,而且只能在本机使用。

  • 如果你有Flash 8程序,可以在输出设置里把local playback选项设置为access network only。

  • 如果你没有Flash 8程序,可以下载 Flash Local Content Updater ,来防止弹出安全对话框。(Danger在此基础上作了个允许在Windows下直接右键修改的版本,有兴趣可以去看看。”SWF 文件安全策略修改器”:http://www.dengjie.com/weblog/comments.asp?post_id=960 )

原理

当通过本地打开swf文件时,Flash Player8执行三种不同的安全机制:

  • Local-with-filesystem 只允许访问本地文件。
  • Local-with-networking 只允许访问网络。
  • Local-trusted 允许指定的位置进行本地和网络访问。

第三种的实现是通过网上的Settings Manager来设置来自macromedia的本地shared object来实现。第一二种则是通过向swf文件中写入一个flash player8所能识别的tag标签来设置是否允许本地访问或网络访问。这个新tag的具体格式如下(个人推断,仅供参考):

  • tagCode : 69
  • tagLength: 4 (不包含tagCodeAndLength的大小,只表示tagContent的大小)
  • tagContent: 0×00000000 (表示Local-with-filesystem) , 0×00000001(表示Local-with-networking)