2014年12月30日火曜日

プロキシ環境下でApache Subversionを使う

設定ファイル


~/.subversion/servers


グループ毎に個別指定する場合


アクセスするリポジトリが固定している場合はgroupsセクションで設定するのが望ましい
$ cat ~/.subversion/servers
(関連箇所のみ抜粋)
[groups]
targetgroup1 = somerepository.example.org

[targetgroup1]
http-proxy-host = proxy.example.com
http-proxy-port = 8080
http-proxy-username = foo.bar@example.com
http-proxy-password = hogepass


まとめて指定する場合


外部リポジトリにしかアクセスしない場合等はglobalセクションで一括設定すると楽
$ cat ~/.subversion/servers
(関連箇所のみ抜粋)
[global]
http-proxy-exceptions = *.internal.example.com, *.internal.example.net
http-proxy-host = proxy.example.com
http-proxy-port = 8080
http-proxy-username = foo.bar@example.com
http-proxy-password = hogepass

2014年11月26日水曜日

NSURLSessionを使ってHTTPリクエストする(Swift)

SwiftでNSURLSessionDataTaskを使って、HTTP-GET、JSONデータをHTTP-POSTする。

環境
  • Xcode 6.1
  • iOS 8.1
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


    // HTTP-GET
    @IBAction func getAsync(sender: AnyObject) {
        
        // create the url-request
        let urlString = "http://httpbin.org/get"
        var request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
        
        // set the method(HTTP-GET)
        request.HTTPMethod = "GET"
        
        // use NSURLSessionDataTask
        var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { data, response, error in
            if (error == nil) {
                var result = NSString(data: data, encoding: NSUTF8StringEncoding)!
                println(result)
            } else {
                println(error)
            }
        })
        task.resume()
        
    }


    // HTTP-POST
    @IBAction func postAsync(sender: AnyObject) {
        
        // create the url-request
        let urlString = "http://httpbin.org/post"
        var request = NSMutableURLRequest(URL: NSURL(string: urlString)!)
        
        // set the method(HTTP-POST)
        request.HTTPMethod = "POST"
        // set the header(s)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        // set the request-body(JSON)
        var params: [String: AnyObject] = [
            "foo": "bar",
            "baz": [
                "a": 1,
                "b": 20,
                "c": 300
            ]
        ]
        request.HTTPBody = NSJSONSerialization.dataWithJSONObject(params, options: nil, error: nil)
        
        // use NSURLSessionDataTask
        var task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error in
            if (error == nil) {
                var result = NSString(data: data, encoding: NSUTF8StringEncoding)!
                println(result)
            } else {
                println(error)
            }
        })
        task.resume()
        
    }
        
}

2014年10月5日日曜日

即時関数を使ってグローバル汚染を最小にする


関数単位でスコープが決まる性質を利用する。

  • 即時関数を使ってローカルスコープ化することにより、グローバル汚染を防ぐ。
  • strictモードでは関数内のthisがundefinedになるため、callメソッドを使用する。
  • 関数前の「;」はファイル連結時に不具合が出ないようにするための予防策。
<!DOCTYPE html>
<html>
<head lang="ja">
  <meta charset="UTF-8">
  <title>即時関数によるスコープ閉じ込め</title>
</head>
<body>
  <ul>
    <li id="first">1行目</li>
    <li id="second">2行目</li>
    <li id="third">3行目</li>
  <ul>
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
  <script src="./js/app.js"></script>
</body>
</html>
;(function ($) {
  'use strict';

  alert('outside ready method: this = ' + this);
  alert('outside ready method: $ = ' + $);
  alert('outside ready method: _.VERSION = ' + _.VERSION);

  $(function () {
    alert('inside ready method: this = ' + this);
    alert('inside ready method: $ = ' + $);
    alert('inside ready method: _.VERSION = ' + _.VERSION);
    $('#third').css('color', 'red');
  });

  alert('this = ' + this);
  alert('$ = ' + $);
  alert(_.VERSION);

}).call(this, jQuery);
//}.bind(this)(jQuery));

[参考URL]
知ってて当然?初級者のためのJavaScriptで使う即時関数(function(){...})()の全て - 三等兵
callで関数を即時実行すると何が嬉しいのか(と、ちょっとおまけ) - ただぱそこんしてるだけ
便利なjavascriptテクニック集 - Thujikun blog
"use strict" - blog.niw.at

2014年8月16日土曜日

NSURLSessionを使ってHTTPリクエストする

NSURLSessionDataTaskを使って、HTTP-GET、JSONデータをHTTP-POSTする。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextField *requestUrl;
@property (weak, nonatomic) IBOutlet UITextField *requestBody;
@property (weak, nonatomic) IBOutlet UITextView *requestResult;
- (IBAction)getAsync:(id)sender;
- (IBAction)postAsync:(id)sender;

@end
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

// HTTP-GET
- (IBAction)getAsync:(id)sender {
    NSLog(@"%s", __func__);
    
    // create the url-request
    NSURL *url = [NSURL URLWithString:self.requestUrl.text];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    // set the method(HTTP-GET)
    [request setHTTPMethod:@"GET"];
    
    // use NSURLSessionDataTask
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        
        if (!error) {
            NSHTTPURLResponse *httpRes = (NSHTTPURLResponse *)response;
            NSLog(@"statusCode: %ld", (long)httpRes.statusCode);
            NSLog(@"allHeaderFields: %@", httpRes.allHeaderFields);
            
            NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"result: %@", result);
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.requestResult.text = result;
            });
            
        } else {
            NSLog(@"error: %@", [error localizedDescription]);
        }
        
    }];
    
    [task resume];
}

// HTTP-POST
- (IBAction)postAsync:(id)sender {
    NSLog(@"%s", __func__);
    
    // create the url-request
    NSURL *url = [NSURL URLWithString:self.requestUrl.text];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    // set the headers(s)
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    
    // set the method(HTTP-POST)
    [request setHTTPMethod:@"POST"];

    // set the request-body
    NSString *reqBody = self.requestBody.text;
    NSLog(@"%@", reqBody);

    [request setHTTPBody:[reqBody dataUsingEncoding:NSUTF8StringEncoding]];
    
    // use NSURLSessionDataTask
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        
        if (!error) {
            NSHTTPURLResponse *httpRes = (NSHTTPURLResponse *)response;
            NSLog(@"statusCode: %ld", (long)httpRes.statusCode);
            NSLog(@"allHeaderFields: %@", httpRes.allHeaderFields);
            
            NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"result: %@", result);
            
            dispatch_async(dispatch_get_main_queue(), ^{
                self.requestResult.text = result;
            });
            
        } else {
            NSLog(@"error: %@", [error localizedDescription]);
        }
        
    }];
    
    [task resume];
}

@end

2014年7月25日金曜日

WICの機能や無線LANの状態を確認する

Raspberry Piに無線LAN USBアダプタ(WLI-UC-GNM)を接続

$ lsusb
(関連箇所のみ抜粋)
Bus 001 Device 004: ID 0411:01a2 BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM Wireless LAN Adapter [Ralink RT8070]

$ lsmod
Module                  Size  Used by
(関連箇所のみ抜粋)
rt2800usb              17279  0 
rt2800lib              80619  1 rt2800usb
rt2x00usb              11669  1 rt2800usb
rt2x00lib              44799  3 rt2x00usb,rt2800lib,rt2800usb
mac80211              329373  3 rt2x00lib,rt2x00usb,rt2800lib
cfg80211              211002  2 mac80211,rt2x00lib
rfkill                 19567  2 cfg80211
-->
cfg80211、mac80211の記述からnl80211でカバーできそう

iwのインストール
$ sudo apt-get install iw

サポート機能の確認
$ iw list
Wiphy phy0
(関連箇所のみ抜粋)
 Supported Ciphers:
  * WEP40 (00-0f-ac:1)
  * WEP104 (00-0f-ac:5)
  * TKIP (00-0f-ac:2)
  * CCMP (00-0f-ac:4)
 Available Antennas: TX 0 RX 0
 Supported interface modes:
   * IBSS
   * managed
   * AP
   * AP/VLAN
   * WDS
   * monitor
   * mesh point
 software interface modes (can always be added):
   * AP/VLAN
   * monitor
 valid interface combinations:
   * #{ AP, mesh point } <= 8,
     total <= 8, #channels <= 1
-->
IBSS: アドホック
managed: クライアント
AP: アクセスポイント
VLAN: 無線VLAN
monitor: 無線LANネットワーク検出

無線LANのスキャン
$ sudo iw dev wlan0 scan

iwconfigはまだ使えるようだが、これはそのうちdeprecatedされるかもしれない
$ sudo ifdown wlan0
$ iwconfig
(関連箇所のみ抜粋)
wlan0     IEEE 802.11bgn  ESSID:off/any  
          Mode:Managed  Access Point: Not-Associated   Tx-Power=20 dBm   
          Retry  long limit:7   RTS thr:off   Fragment thr:off
          Power Management:on

$ sudo ifup wlan0
$ iwconfig
(関連箇所のみ抜粋)
wlan0     IEEE 802.11bgn  ESSID:"RaspiSSID"  
          Mode:Managed  Frequency:2.462 GHz  Access Point: 01:23:45:67:89:AB   
          Bit Rate=6.5 Mb/s   Tx-Power=20 dBm   
          Retry  long limit:7   RTS thr:off   Fragment thr:off
          Power Management:on
          Link Quality=70/70  Signal level=-39 dBm  
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:0  Invalid misc:0   Missed beacon:0


[参考URL]
cfg80211 - Linux Wireless
mac80211 - Linux Wireless
nl80211 - Linux Wireless
hostapd を使用した簡単な方法で WiFi に強力な暗号化を実装する

2014年7月21日月曜日

Raspberry Piのネットワークを設定する

有線LAN


有線LAN用固定IPの設定
$ sudo cp /etc/network/interfaces{,.orig}
$ sudo vim /etc/network/interfaces

/etc/network/interfaces
auto lo
iface lo inet loopback

iface eth0 inet static
address 192.168.21.41
netmask 255.255.255.0
gateway 192.168.21.1

該当IFの無効・有効化
$ sudo ifdown eth0 && sudo ifup eth0


無線LAN


無線LANクライアントの設定
$ sudo cp /etc/wpa_supplicant/wpa_supplicant.conf{,.orig}
$ wpa_passphrase "RaspiSSID" "RaspiSSIDPW" | sudo tee -a /etc/wpa_supplicant/wpa_supplicant.conf
network={
        ssid="RaspiSSID"
        #psk="RaspiSSIDPW"
        psk=d721085129ab22a68036c3c5cf7d28dab5f9b3de93a8866fe6626d59e811b7f7
}

$ sudo vim /etc/wpa_supplicant/wpa_supplicant.conf

/etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
        ssid="RaspiSSID"
        scan_ssid=0           # スキャン方法(0:パッシブ、1:アクティブ)
        priority=0            # 値が大きいほど優先度が高い(デフォルトは0)
        proto=RSN             # WPA2はRSNの別名
        key_mgmt=WPA-PSK
        pairwise=TKIP CCMP
        group=CCMP
        psk=d721085129ab22a68036c3c5cf7d28dab5f9b3de93a8866fe6626d59e811b7f7
        id_str="RaspiConf"    # 識別名
}

無線LAN用固定IPの設定
$ sudo cp /etc/network/interfaces{,.orig}
$ sudo vim /etc/network/interfaces

/etc/network/interfaces
auto lo
iface lo inet loopback

iface eth0 inet static
address 192.168.21.41
netmask 255.255.255.0
gateway 192.168.21.1

allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet dhcp

iface RaspiConf inet static
address 192.168.21.42
netmask 255.255.255.0
gateway 192.168.21.1

該当IFの無効・有効化
$ sudo ifdown wlan0 && sudo ifup wlan0


リゾルバの設定


$ sudo vim /etc/resolv.conf
$ cat /etc/resolv.conf
nameserver 192.168.21.1
nameserver 192.168.22.1
search example.com example.jp


ホスト名の設定


$ sudo vim /etc/hostname
$ cat /etc/hostname
raspi

$ sudo vim /etc/hosts
$ cat /etc/hosts
127.0.0.1      localhost.localdomain   localhost
127.0.1.1      raspi.example.com       raspi
192.168.11.41  raspi.example.com       raspi
192.168.11.42  raspi.example.com       raspi


再起動


$ sudo shutdown -r now


[参考URL]
How-To: WiFi roaming with wpa-supplicant | Debuntu
wpa_supplicant.conf(5)
Gentoo Linux ドキュメント -- 無線ネットワーク

2014年7月18日金曜日

Raspberry Piをバックアップ・リストアする

バックアップ


バックアップ元であるSDカードのディスク番号を確認してからバックアップを実行。
$ diskutil list
(関係箇所のみ抜粋)
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk1
   1:             Windows_FAT_32 boot                    58.7 MB    disk1s1
   2:                      Linux                         15.9 GB    disk1s2

$ df -h
Filesystem                          Size   Used  Avail Capacity  iused    ifree %iused  Mounted on
(関係箇所のみ抜粋)
/dev/disk1s1                        56Mi  9.5Mi   46Mi    17%      512        0  100%   /Volumes/boot

$ sudo dd bs=1m if=/dev/rdisk1 of=./raspi_YYYYMMDD.img

gzip圧縮してバックアップする際は
$ sudo dd bs=1m if=/dev/rdisk1 | gzip > ./raspi_YYYYMMDD.gz


リストア


リストア先であるSDカードのディスク番号を確認、アンマウントしてからリストアを実行(過去の内容と同様)。
$ diskutil list
(関係箇所のみ抜粋)
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk1
   1:                 DOS_FAT_32 UNTITLED                15.9 GB    disk1s1

$ df -h
Filesystem                          Size   Used  Avail Capacity  iused    ifree %iused  Mounted on
(関係箇所のみ抜粋)
/dev/disk1s1                        15Gi  2.5Mi   15Gi     1%        0        0  100%   /Volumes/UNTITLED

$ sudo diskutil unmount /dev/disk1s1
$ sudo dd bs=1m if=./raspi_YYYYMMDD.img of=/dev/rdisk1
$ sudo diskutil eject /dev/rdisk1

gzip圧縮したファイルからリストアする際は
$ sudo diskutil unmount /dev/disk1s1
$ gzip -dc ./raspi_YYYYMMDD.gz | sudo dd bs=1m of=/dev/rdisk1
$ sudo diskutil eject /dev/rdisk1


[参考URL]
Back-up a Raspberry Pi SD card using a Mac | TechTonic

2014年7月16日水曜日

Raspberry Piの初期設定を行う

apt-getの実行


$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo shutdown -r now


raspi-configの実行


$ sudo raspi-config

1 Expand Filesystem
    rootfs(ext4フォーマット)を拡張する。
    再起動後に拡張rootfsが有効になる。

2 Change User Password
    「$ sudo passwd pi」が実行される。
    ユーザpiのパスワードを変更する。

4 Internationalisation Options
  • Change Locale
  • 「$ sudo dpkg-reconfigure locales」が実行される。
    「en_US.UTF-8 UTF-8」のみをチェックする。
    「Default local for the system environment」については「en_US.UTF-8」を選択する。
    設定反映のために、一旦ログアウトしてから再度ログインする。

  • Change Timezone
  • 「$ sudo dpkg-reconfigure tzdata」が実行される。
    「Asia -> Tokyo」と選択する。

  • Change Keyboard Layout
  • 「Generic 105-key (Intl) PC -> Other -> Japanese -> Japanese - Japanese (OADG 109A) -> The default for the keyboard layout -> No compose key -> Yes」と選択する。
    最後のYesは「Use Control+Alt+Backspace to terminate the X server?」に関する選択。


追加のロケール設定


$ sudo vi /etc/default/locale
$ cat /etc/default/locale 
#  File generated by update-locale
LANG=en_US.UTF-8
LC_ALL=en_US.UTF-8
LANGUAGE=en_US.UTF-8
設定反映のために、一旦ログアウトしてから再度ログインする。


HDMI出力の固定化


$ sudo vi /boot/config.txt
(関連箇所のみ抜粋)
hdmi_force_hotplug=1
framebuffer_width=1920
framebuffer_height=1080

$ sudo shutdown -r now


[参考URL]
“Americanizing” the Raspberry Pi | Rohan Kapoor
Fix locale problems in Raspbian | Programming and Technology

2014年7月14日月曜日

Raspberry Piで使うSDカードをセットアップする

OSイメージ(Raspbian)の準備


本家からOSイメージをダウンロードしようとすると時間がかかるので、JAISTのミラーサイトからダウンロードする。
$ wget http://ftp.jaist.ac.jp/pub/raspberrypi/raspbian/images/raspbian-2014-06-22/2014-06-20-wheezy-raspbian.zip

$ openssl sha1 2014-06-20-wheezy-raspbian.zip
SHA1(2014-06-20-wheezy-raspbian.zip)= b020908e3cba472a24f7a17498008eb69d86d1cb

$ unzip 2014-06-20-wheezy-raspbian.zip


ディスク番号の確認


SDカードを接続してディスク番号を確認する。
$ diskutil list
(関係箇所のみ抜粋)
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk1
   1:                 DOS_FAT_32 UNTITLED                15.9 GB    disk1s1

$ df -h
Filesystem                          Size   Used  Avail Capacity  iused    ifree %iused  Mounted on
(関係箇所のみ抜粋)
/dev/disk1s1                        15Gi  2.5Mi   15Gi     1%        0        0  100%   /Volumes/UNTITLED


OSイメージの書き込み


SDカードをアンマウントした上で、rawデバイス指定で書き込みを実行する(アンマウント実行後は、書き込みが完了するまでSDカードを取り外さないこと)。
なお、ddコマンド実行中にControl-Tを押下すると書き込み進捗状況を確認できる。
$ sudo diskutil unmount /dev/disk1s1
$ sudo dd bs=1m if=2014-06-20-wheezy-raspbian.img of=/dev/rdisk1
$ sudo diskutil eject /dev/rdisk1


[参考URL]
Raspberry Pi Documentation
RPi Easy SD Card Setup - eLinux.org

2014年6月14日土曜日

プロキシ環境下でChef Soloをインストールする

環境
  • Ubuntu Server 12.04 LTS x64

apt-get用にプロキシ設定を追加
$ sudo vi /etc/apt/apt.conf
$ cat /etc/apt/apt.conf
(関係箇所のみ抜粋)
Acquire::http::Proxy "http://proxy.example.com:8080";
Acquire::https::Proxy "http://proxy.example.com:8080";
Acquire::ftp::Proxy "http://proxy.example.com:8080";

vim、curlのインストール
$ sudo apt-get update -y
$ sudo apt-get install -y vim curl

プロキシ用の環境変数を追加
$ vim ~/.bashrc
$ cat ~/.bashrc
(関係箇所のみ抜粋)
export http_proxy="http://proxy.example.com:8080"
export https_proxy="http://proxy.example.com:8080"
export ftp_proxy="http://proxy.example.com:8080"

$ source ~/.bashrc


wget用にプロキシ設定を追加(後で実行するオムニバスインストーラスクリプト内でwgetが実行されるため)
$ sudo vim /etc/wgetrc

/etc/wgetrc(関係箇所のみ抜粋)
# You can set the default proxies for Wget to use for http, https, and ftp.
# They will override the value in the environment.
https_proxy = http://proxy.example.com:8080
http_proxy = http://proxy.example.com:8080
ftp_proxy = http://proxy.example.com:8080

# If you do not want to use proxy at all, set this to off.
use_proxy = on


オムニバスインストーラを使ってChefをインストール
$ curl -L https://www.opscode.com/chef/install.sh | sudo bash

Chef Soloのバージョン確認
$ chef-solo -v


補足

wgetについて個別にプロキシ設定しない場合は、sudoコマンドにEオプションをつけることにより、ログインアカウントの環境変数を引き継いでインストールスクリプトを実行する。

$ curl -L https://www.opscode.com/chef/install.sh | sudo -E bash


[参考URL]
The little, that can make a difference...: Unable to add ppa behind proxy

2014年5月17日土曜日

RubyGemsをアップデートする

RubyGemsをアップデートする。rubygems-updateパッケージがない場合は自動的にインストールされる。
$ gem update --system
$ gem -v
2.2.2

なお、rubygems-updateパッケージを直接利用する場合は下記を実行すればOKのハズだが、私は使っていない。
$ update_rubygems 
$ gem -v
2.2.2

基本的にBundler(Gemfile)で各gemを管理しているので、Bundlerもアップデートする。
$ gem update bundler
$ bundle -v
Bundler version 1.6.2


必要に応じて下記も実行する。

Bundler管理のgemの更新
$ bundle update

Bundler管理の古いgemの削除
$ bundle clean

2014年4月30日水曜日

仮想ディスク(VHDXファイル)を作成する

diskpartを使って仮想ディスクであるVHDXファイルを作成する。
maximumにはMB単位でサイズを指定する。

> diskpart
DISKPART> create vdisk file=C:\Users\hogeuser\Documents\sample.vhdx maximum=10240 type=expandable
DISKPART> select vdisk file=C:\Users\hogeuser\Documents\sample.vhdx
DISKPART> attach vdisk
DISKPART> create partition primary
DISKPART> assign letter=P
DISKPART> format quick fs=ntfs label=somelabel
DISKPART> detach vdisk
DISKPART> exit
>

2014年3月2日日曜日

Railsをインストールしてアプリを動作させる

1. Railsをインストールする


Gemfileを作成する。
$ mkdir RailsSample
$ cd RailsSample
$ bundle init

Gemfileを編集する。
$ vim Gemfile

Gemfile
# A sample Gemfile
source "https://rubygems.org"

gem "rails"

Railsに必要なGemパッケージをインストールする。
$ bundle install --path vendor/bundle


2. Railsアプリを作成する


bundle installが自動実行されないように「--skip-bundle」オプションを付与した上でrails newする。Gemfileを上書きしてもいいか聞かれたら、Enterを押下して上書きする。
$ bundle exec rails new . --skip-bundle

.bundle/configファイルに「BUNDLE_PATH: vendor/bundle」の記載があることを確認した上で、必要なGemパッケージを追加する。
$ bundle install


3. Railsアプリを動作させる


付属のWebサーバ(WEBrick)を起動する。
$ bundle exec rails server

http://localhost:3000/にアクセスしてRailsアプリが表示されることを確認する。

2014年2月16日日曜日

Bundlerをインストールする

使用中のRuby及びgemのバージョンを確認する。
$ rbenv version
2.0.0-p353 (set by /Users/hogeuser/.rbenv/version)
$ gem -v
2.0.14

使用中のRubyにインストールされているGemを確認する。
$ gem list

*** LOCAL GEMS ***

bigdecimal (1.2.0)
io-console (0.4.2)
json (1.7.7)
minitest (4.3.2)
psych (2.0.0)
rake (0.9.6)
rdoc (4.0.0)
test-unit (2.0.0.0)


gemコマンドによるパッケージインストール及びアップグレード時に、ドキュメント生成しないようにする。
$ vim ~/.gemrc

~/.gemrc
install: --no-ri --no-rdoc
update: --no-ri --no-rdoc

上記.gemrcファイルについては下記表記も可能。
~/.gemrc
gem: --no-ri --no-rdoc


Bundlerをインストールする。
$ gem install bundler
$ rbenv rehash 

Bundlerがインストールされたことを確認する。
$ gem list

*** LOCAL GEMS ***

bigdecimal (1.2.0)
bundler (1.5.3)
io-console (0.4.2)
json (1.7.7)
minitest (4.3.2)
psych (2.0.0)
rake (0.9.6)
rdoc (4.0.0)
test-unit (2.0.0.0)

インストールされたBundlerの場所を確認する。
$ gem which bundler
/Users/hogeuser/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/bundler-1.5.3/lib/bundler.rb

2014年2月15日土曜日

Rubyバージョン管理ツールを導入する

OS Xで利用可能なRubyバージョン管理ツールは他にもあるが、ここでは個人的に最も使い勝手の良いと思われるrbenvの導入方法について説明する。

rbenvをインストールする。
$ brew install rbenv
$ rbenv -v
rbenv 0.4.0

ruby-buildをインストールする(PATH設定については、通常Homebrewを使ってインストールする場合は不要であるため、必要に応じて設定する)。
$ brew install ruby-build
$ echo 'export RBENV_ROOT="$HOME/.rbenv"' >> ~/.bashrc
$ echo 'export PATH="$RBENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
$ source ~/.bashrc

readlineとopensslをインストールする(opensslについては、通常ruby-buildパッケージインストール時に一緒にインストールされるため、必要に応じてインストールする)。
$ brew install readline
$ brew install openssl

インストール可能なRubyのバージョンを確認する。
$ rbenv install -l

1.9.3-p484をインストールする。
$ CONFIGURE_OPTS="--with-readline-dir=`brew --prefix readline` --with-openssl-dir=`brew --prefix openssl` --with-gcc=clang" rbenv install 1.9.3-p484
$ rbenv rehash

2.0.0-p353をインストールする。
$ CONFIGURE_OPTS="--with-readline-dir=`brew --prefix readline` --with-openssl-dir=`brew --prefix openssl` --with-gcc=clang" rbenv install 2.0.0-p353
$ rbenv rehash

インストールされているRubyのバージョンを確認する。
$ rbenv versions
* system (set by /Users/hogeuser/.rbenv/version)
  1.9.3-p484
  2.0.0-p353

使用するRubyのバージョンを切り替える。
$ rbenv global 2.0.0-p353
$ rbenv versions
  system
  1.9.3-p484
* 2.0.0-p353 (set by /Users/hogeuser/.rbenv/version)

使用中のRubyのバージョンを確認する。
$ rbenv version
2.0.0-p353 (set by /Users/hogeuser/.rbenv/version)
$ ruby -v
ruby 2.0.0p353 (2013-11-22 revision 43784) [x86_64-darwin13.0.2]

2014年1月25日土曜日

iBeaconを受信する

uuidgenコマンドを使って、proximity IDとして使用するUUIDを事前準備しておく。

$ uuidgen


iBeacon受信処理を行うクラスにおいて、CoreLocationをインポートした上でCLLocationManagerDelegateプロトコルを実装(適合)する。各メソッドの意味等については下記コード内のコメントを要参照。
@implementation BLGViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    if ([CLLocationManager isMonitoringAvailableForClass:[CLBeaconRegion class]]) {
        self.locationManager = [[CLLocationManager alloc] init];
        self.locationManager.delegate = self;

        // identifierについては、アプリ内でどのリージョンであるかを判別するために使用
        NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:BLGViewControllerProximityUUID];
        self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid identifier:kIdentifier];
        
        // いずれもデフォルト設定値
        self.beaconRegion.notifyOnEntry = YES;
        self.beaconRegion.notifyOnExit = YES;
        self.beaconRegion.notifyEntryStateOnDisplay = NO;
        
        // 領域観測を開始する
        [self.locationManager startMonitoringForRegion:self.beaconRegion];
    }
}

#pragma mark - CLLocationManagerDelegate

// 領域観測が正常に開始されると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
    // 非同期に実行し、CLLocationManagerDelegateに結果を配送する
    // (locationManager:didDetermineState:forRegion:メソッド要実装)
    [self.locationManager requestStateForRegion:self.beaconRegion];
}

// 領域に関する状態を取得する
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
    switch (state) {
        case CLRegionStateInside:
            NSLog(@"state is CLRegionStateInside");
            self.stateLabel.text = @"CLRegionStateInside";
            // 領域内にいるので、測距を開始する
            if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
                [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
            }
            break;
        case CLRegionStateOutside:
            NSLog(@"state is CLRegionStateOutside");
            self.stateLabel.text = @"CLRegionStateOutside";
            break;
        case CLRegionStateUnknown:
            NSLog(@"state is CLRegionStateUnknown");
            self.stateLabel.text = @"CLRegionStateUnknown";
            break;
        default:
            NSLog(@"state is UNKNOWN");
            self.stateLabel.text = @"UNKNOWN";
            break;
    }
}

// 領域に入ると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region
{
    // 領域に入ったので、測距を開始する
    if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
        [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
    }
}

// 領域から出ると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region
{
    if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
        [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
    }
}

// 領域内でビーコンを受信する度に呼ばれる(実機で確認する限りでは約1秒毎)
// ビーコンの状態が変わった時も呼ばれる
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region
{
    if ([beacons count] > 0) {
        // 最も近くにあるビーコンが配列の先頭にあるように、デバイスからの距離によって整列されている
        CLBeacon *nearestBeacon = beacons.firstObject;
        NSString *proximityStr;
        
        switch (nearestBeacon.proximity) {
            case CLProximityImmediate:
                proximityStr = @"CLProximityImmediate";
                break;
            case CLProximityNear:
                proximityStr = @"CLProximityNear";
                break;
            case CLProximityFar:
                proximityStr = @"CLProximityFar";
                break;
            case CLProximityUnknown:
                proximityStr = @"CLProximityUnknown";
                break;
            default:
                proximityStr = @"UNKNOWN";
                break;
        }

        // ビーコン識別情報
        self.uuidLabel.text = nearestBeacon.proximityUUID.UUIDString;
        self.majorLabel.text = [NSString stringWithFormat:@"%@", nearestBeacon.major];
        self.minorLabel.text = [NSString stringWithFormat:@"%@", nearestBeacon.minor];
        
        // アドバタイズしているビーコンまでの距離に関する情報
        self.proximityLabel.text = proximityStr;
        self.rssiLabel.text = [NSString stringWithFormat:@"%ld [dB]", (long)nearestBeacon.rssi];
        self.accuracyLabel.text = [NSString stringWithFormat:@"%.0f [m]", nearestBeacon.accuracy];
    }
}

// アプリのロケーションサービスに関するアクセス許可状態に変更があると呼ばれる
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
    NSLog(@"%s", __func__);
    switch (status) {
        case kCLAuthorizationStatusAuthorized:
            NSLog(@"kCLAuthorizationStatusAuthorized");
            break;
        case kCLAuthorizationStatusRestricted:
            NSLog(@"kCLAuthorizationStatusRestricted");
            break;
        case kCLAuthorizationStatusDenied:
            NSLog(@"kCLAuthorizationStatusDenied");
            break;
        case kCLAuthorizationStatusNotDetermined:
            NSLog(@"CLAuthorizationStatusNotDetermined");
            break;
        default:
            NSLog(@"UNKNOWN");
            break;
    }
}

@end

[参考URL]
[iOS 7] 新たな領域観測サービス iBeacon を使ってみる
iBeaconの解説

2014年1月13日月曜日

Objective-Cで定数を定義する

ヘッダーファイルにおいて、#define(マクロ)を使って、(公開/非公開に関わらず)全ての定数にプレフィックスkをつけて定義しているコードを見かけることがあるが、下記理由から個人的にはあまり良くないのではないかと思っている。
  • 型情報がない
  • ヘッダーファイルインポート条件によっては、名前衝突により開発者が意図しないファイルにおいても定数置換が行われてしまう危険性がある

実際のところ、私は下記のように定数定義を行っている。

外部に公開する定数

  • extern constを付与
  • 定数にはプレフィックスとしてクラス名をつける(名前衝突を避けるため)

内部でのみ使用する定数

  • static constを付与
  • 定数にはプレフィックスとしてkをつける(よく見かけるパターン)

コーディング例

以下は、BLGClassAクラスにおいて外部に公開する定数を定義、BLGClassBクラスにおいて内部で使用する定数を定義、して利用する場合の例。


動作確認

コンパイル/リンクして実行。


2014年1月11日土曜日

iOSのシステムサウンドを再生する

iOSにシステムとして予め用意されているサウンドを再生する。
プロジェクトにAudioToolbox.frameworkを追加した上で、SystemSoundIDを引数に持つAudioServicesPlaySystemSound関数を呼び出す。
#import "ViewController.h"
@import AudioToolbox;

@interface ViewController ()
@end

- (IBAction)playSystemSound:(id)sender {
    // ベル音(SystemSoundIDは1009)を鳴らす
    AudioServicesPlaySystemSound(1009); 
}

[参考URL]
iOSのシステムサウンドを確認する
AudioServices - iPhone Development Wiki

2014年1月5日日曜日

即座にローカル通知する

fireDateを指定する方法とpresentLocalNotificationNow:メソッドを使う方法がある。いずれの場合についても、前回と同様、AppDelegateクラスにおいてイベントメソッド処理できる。


1. fireDateを指定する
 fireDateにnilまたは過去の時間を指定すると、即座にローカル通知が送信される。
- (IBAction)sendNotification:(id)sender {
  
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.fireDate = nil;
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];

}

2. presentLocalNotificationNow:メソッドを使う
 fireDateの値に関わらず、即座にローカル通知が送信される。
- (IBAction)sendNotification:(id)sender {
  
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    notification.fireDate = [[NSDate date] dateByAddingTimeInterval:60];
    [[UIApplication sharedApplication] presentLocalNotificationNow:notification];

}

2014年1月4日土曜日

一定時間後にローカル通知する


UILocalNotificationを使って一定時間後(例では10秒後)にローカル通知する。

- (IBAction)sendNotification:(id)sender {
    NSLog(@"%s", __func__);
    
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    
    //    // iOS4未満用の処理?
    //    if (notification == nil)
    //        return;
    
    notification.fireDate = [[NSDate date] dateByAddingTimeInterval:10];
    notification.timeZone = [NSTimeZone defaultTimeZone];
    notification.alertBody = @"Hello, LocalNotification!";
    notification.hasAction = YES;
    notification.alertAction = @"Launch";
    notification.soundName = UILocalNotificationDefaultSoundName;
    notification.applicationIconBadgeNumber = 1;
    
    NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"Recevied." forKey:@"kExtra"];
    notification.userInfo = infoDict;
    
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ 
    // notificationの有無に関わらず、アプリ起動時に呼ばれる。
    NSLog(@"%s", __func__);
    
    UILocalNotification *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    
    if (notification) {
        NSDictionary *infoDict = [notification userInfo];
        NSString *extra = [infoDict objectForKey:@"kExtra"];
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSStringFromSelector(_cmd) message:extra delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alertView show];
    }
    
    return YES;
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    NSLog(@"%s", __func__);
    // アプリ強制終了の場合も考慮して、ここにバッジクリア処理を入れるのが良さそうか?
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    // アプリがフォアグランドまたはバックグラウンドにある場合に呼ばれる。
    NSLog(@"%s", __func__);
    NSDictionary *infoDict = [notification userInfo];
    NSString *extra = [infoDict objectForKey:@"kExtra"];
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSStringFromSelector(_cmd) message:extra delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
    [alertView show];
}


[参考URL]
LocalおよびPush Notification プログラミングガイド

2014年1月3日金曜日

ストーリーボードも使わずに実現する

前回の内容をストリーボードも使わずに実現する。ただし、AppDelegateクラスについてはUINavigationControllerをプロパティとして使い、ViewControllerクラスについてはUITableViewControllerを継承する形式で実現してみる。

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UINavigationController *navigationController;

@end
#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    ViewController *viewController = [[ViewController alloc] init];
    self.navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
    self.window.rootViewController = self.navigationController;

    [self.window makeKeyAndVisible];
    return YES;
}

@end
#import <UIKit/UIKit.h>

@interface ViewController : UITableViewController

@end
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSArray *teams = _leagues[indexPath.section];
    NSArray *teamPrefixes = _leaguePrefixes[indexPath.section];
    
    DetailViewController *detailViewController = [[DetailViewController alloc] init];
    [detailViewController setTitle:teams[indexPath.row]];
    [detailViewController setDetailItem:teamPrefixes[indexPath.row]];
    [self.navigationController pushViewController:detailViewController animated:YES];
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    // I don't care orientation this time!
    UILabel *detailLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    detailLabel.textAlignment = NSTextAlignmentCenter;
    detailLabel.backgroundColor = [UIColor clearColor];
    detailLabel.text = self.detailItem;
    [self.view addSubview:detailLabel];
    NSLog(@"self.detailItem is %@", self.detailItem);
}

2014年1月2日木曜日

セグエを使わずに実現する

前回の内容をセグエを使わずに実現する。 また、ビューコントローラについてはView Controllerのみを使い、ストーリボードについては2つ使ってみる。つまり、iOS View Controllerプログラミングガイドのリスト 2-3のような形式をとってみる。


新しいストーリーボードからビューコントローラのインスタンスを生成する。
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIViewController *viewController;

@end
#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:NSStringFromClass([ViewController class]) bundle:nil];
    ViewController *viewController = [storyboard instantiateInitialViewController];
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
    self.viewController = navigationController;
    self.window.rootViewController = navigationController;
    
    [self.window makeKeyAndVisible];
    return YES;
}

@end


選択行をスムーズに消すために、viewWillAppear:イベントメソッド内でdeselectRowAtIndexPath: animated:メソッドを呼び出す。
セルを再利用のためにdequeueReusableCellWithIdentifier:メソッドを使う。
画面遷移については、didSelectRowAtIndexPath:イベントメソッド内で実行する。
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>

@end
#import "ViewController.h"
#import "DetailViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController {
    @private
    NSArray *_leagueKinds;
    NSArray *_leagues;
    NSArray *_leaguePrefixes;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view.
    _leagueKinds = @[@"セントラル・リーグ", @"パシフィック・リーグ"];
    
    _leagues = @[
                 @[@"ジャイアンツ", @"タイガース", @"ドラゴンズ", @"ベイスターズ", @"カープ", @"スワローズ"],
                 @[@"バッファローズ", @"ホークス", @"ファイターズ", @"マリーンズ", @"ライオンズ", @"ゴールデンイーグルス"]
                 ];
    
    _leaguePrefixes = @[
                        @[@"読売", @"阪神", @"中日", @"横浜DeNA", @"広島東洋", @"東京ヤクルト"],
                        @[@"オリックス", @"福岡ソフトバンク", @"北海道日本ハム", @"千葉ロッテ", @"埼玉西武", @"東北楽天"]
                        ];
    
    self.title = @"日本プロ野球チーム";
    self.tableView.dataSource = self;
    self.tableView.delegate = self;
}

- (void)viewWillAppear:(BOOL)animated
{
    [self.tableView deselectRowAtIndexPath:self.tableView.indexPathForSelectedRow animated:YES];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return [_leagueKinds count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return [_leagues[section] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }
    
    NSArray *teams = _leagues[indexPath.section];
    NSArray *teamPrefixes = _leaguePrefixes[indexPath.section];
    cell.textLabel.text = teams[indexPath.row];
    cell.detailTextLabel.text = teamPrefixes[indexPath.row];
    
    return cell;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString *HeaderIdentifier = @"Header";
    UITableViewHeaderFooterView *view = [tableview dequeueReusableHeaderFooterViewWithIdentifier:HeaderIdentifier];
    
    if (!view) {
        view = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:HeaderIdentifier];
    }
    
    view.textLabel.text = _leagueKinds[section];
    
    return view;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 40;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSArray *teams = _leagues[indexPath.section];
    NSArray *teamPrefixes = _leaguePrefixes[indexPath.section];
    
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:NSStringFromClass([DetailViewController class]) bundle:nil];
    DetailViewController *detailViewController = [storyboard instantiateInitialViewController];
    [detailViewController setTitle:teams[indexPath.row]];
    [detailViewController setDetailItem:teamPrefixes[indexPath.row]];
    [self.navigationController pushViewController:detailViewController animated:YES];
}

@end

[参考URL]
iOS View Controllerプログラミングガイド