2013年11月13日水曜日

Objective-C でシングルトンパターン (緩め)

Objective-C でシングルトンを実現するのは、ちゃんとやろうとすると、結構手間です。例えば、ARC (Automatic Reference Counting) 無効の場合、次のブログが示しているように、シングルトンインスタンスを取得するメソッドを追加するだけでは話は終わらず、あまり直感的ではない方法で複数のメソッドをオーバーライドしなければなりません。

Objective-C でシングルトンパターン
http://blog.syuhari.jp/archives/2178

ARC 有効となると、書き方も変わってきます。

言語の特性上、呼び出し側で無茶なことができてしまうケースが幾つかあるので、シングルトン実装側だけで完全性を達成しようとするのは難しいと思います。そのため、ある程度シングルトン的な構造にはしておくものの、呼び出し側がひねくれたことをやったら責任は負えないよ、という実装で落ち着くというのも、ケースによっては許容できると思います。

なので、例えば次のようなシングルトンの実装もありではないでしょうか。

// シングルトンインスタンスを保持する変数。
// static を付けているので、C 言語の仕様上、
// 他のソースコードからは参照できない。
static MyClass *_instance;

// +(void)initialize は Objective-C にとって特別。
// クラスの初期化時に一度だけ呼ばれる。
+ (void)initialize
{
    // あるクラスが initialize メソッドを実装していない場合、
    // そのスーパークラスの initialize メソッドが呼ばれて
    // しまうそうなので、同期をとっておく。(今回のケースでは
    // シングルトンクラスのサブクラスを作るというような変な
    // ことでもしない限り、当 initialize メソッドが複数回
    // 呼ばれることは無いが、一般的なコード例として同期)
    @synchronized (self)
    {
        // まだシングルトンインスタンスが作成されていなければ
        if (_instance == nil)
        {
            // シングルトンインスタンスを作る。初期化メソッドは
            // init ではなくて initInternal としておく。
            _instance = [[MyClass alloc] initInternal];
        }
    }
}

// シングルトンインスタンスを取得するためのクラスメソッド。
// sharedInstance というメソッド名はよく使われるので、
// その慣習に従うことにする。
+ (MyClass *)sharedInstance
{
    return _instance;
}

// デフォルトの初期化メソッド。間違って呼ばれないよう、
// 機能しないように実装しておく。
- (id)init
{
    // 呼び出し側が間違えて init メソッドを呼ぶことのないよう、
    // つまり _instance 以外のインスタンスを作ることがないよう、
    // init メソッドは機能しないようにしておく。ここでは、
    // 「そんなメソッド知らない」という例外を投げることにする。
    [self doesNotRecognizeSelector:_cmd];

    return nil;
}

// init のかわりの初期化メソッド。Objective-C の言語仕様上、
// 外部から initInternal を呼ぶことはできてしまうが、わざわざ
// 公開もしていない initInternal を呼んでシングルトンパターンを
// 壊すことをするようなら、それは呼び出し側が悪いので、何が
// 起こっても知ったことではない。
- (id)initInternal
{
    self = [super init];

    return self;
}

// 以下はヘッダファイル内の記述。init メソッドは使用できないと
// 宣言しておく。
- (id)init UNAVAILABLE_ATTRIBUTE;

2013年11月12日火曜日

Android Test プロジェクトを実行すると NoClassDefFoundError

Eclipse で Android プロジェクトを「ライブラリ」としてマークすると、project.properties というファイルに android.library=true と書かれるが、この状態で、このプロジェクトをターゲットプロジェクトとする Android Test プロジェクトを作って Android JUnit Test を実行すると、NoClassDefFoundError が出てしまう。この問題を回避するためには、ターゲットプロジェクトの android.library=true を消すか android.library=false にするかしないといけない。

android.library=true のままだと、{ターゲットプロジェクト}.apk がインストールされないため、{ターゲットプロジェクト}.apk 内のクラスがことごとく見つからない状態 (NoClassDefFoundError) になってしまうらしい。

なんか釈然としないけど、テストプロジェクトが動いたからいいや。

The accepted answer for "Android Eclipse - Could not find *.apk".
http://stackoverflow.com/a/6450971

2013年11月5日火曜日

Getting Started With Ruby/DBI + Oracle on Mac

1. Download Oracle Instant Client from the download page.

  • instantclient-basic-macos.x64-11.2.0.3.0.zip
  • instantclient-sdk-macros.x64-11.2.0.3.0.zip

2. Unzip the zip files.

$ cd /usr/local
$ unzip instantclient-basic-macro.x64-11.2.0.3.0.zip
$ unzip instantclient-sdk-macros.x64-11.2.0.3.0.zip

3. Create a symbolic link to the libclntsh library.

$ cd instantclient_11_2
$ ln -s libclntsh.dylib.11.1 libclntsh.dylib

4. Prepare a configuration file for Oracle clients.

$ vi /etc/tnsnames.ora

your-service-name =
  (DESCRIPTION =
    (ADDRESS =
      (PROTOCOL = TCP)
      (HOST = your-host-name)
      (PORT = 1521)
    )
    (CONNECT_DATA =
      (SERVICE_NAME = your-service-name)
    )
  )

5. Set up some environment variables.

$ export DYLD_LIBRARY_PATH=/usr/local/instantclient_11_2
$ export NLS_LANG=Japanese_Japan.AL32UTF8
$ export TNS_ADMIN=/etc/

6. Install DBI and the driver for Oracle.

$ gem install dbi
$ gem install ruby-oci8

7. Write a test script.

$ vi test.rb

#!/usr/bin/env ruby

require 'rubygems'
require 'dbi'

user = 'your-user-name'
password = 'your-password'
service_name = 'your-service-name'


DBI.connect("DBI:OCI8:#{service_name}", user, password) do |dbh|
  row = dbh.select_one("SELECT * FROM v$version")
  puts "Server version = #{row[0]}"
end

8. Run the test script and it'll show a result like below.

Server version = Oracle Database 11g Release 11.2.0.1.0 - 64bit Production