アニメ風レンダリングが簡単にできるBlenderのFreestyleを試してみた。

Blender2.67から利用できるようになったFreestyleレンダリングが面白そうだったので、以前作ってみた動画で試してみました。

以前作った動画

今までにBlenderpythonを使って下記のような記事と動画を作りました。

Blenderの物理シミュレーションをpythonスクリプトで作成 - 株式会社CFlatの明後日スタイルのブログ

ミルククラウンをBlenderのpythonスクリプトで作成 - 株式会社CFlatの明後日スタイルのブログ

カオス的挙動をBlenderのpythonスクリプトで作成 - 株式会社CFlatの明後日スタイルのブログ

Freestyleでレンダリング

FreestyleでレンダリングするにはBlenderのバージョンを2.67以降に上げて、動画作成前に下記のコードを挿入するだけです。 bpy.data.scenes["Scene"].render.use_freestyle = True

ちなみに下記のコードを2回呼ばないと駄目だった不思議な現象はバージョンを上げると直っていました。 bpy.ops.object.forcefield_toggle()

実際の動画

下記がFreestyleでレンダリングした動画です。手作りアニメ風に仕上がっていて、物理シミュレーションなんて使っていないように見えるから不思議です。


Blenderの物理シミュレーション結果をFreestyleでレンダリング - YouTube


Blenderの物理シミュレーション結果をFreestyleでレンダリング2 - YouTube

流体シミュレーションのFreestyleレンダリング

ミルククラウンの動画についてはこんな問題があって修正に手間が掛かりそうだったので保留にしました。気が向けば作成するかもしれません。

Ansibleで自社Wordpressを移行を自動化

先日弊社Webサイト、http://www.cflat-inc.com のサーバーをAWSからさくらVPSに移行しました。元々t1.microを使っていたのですが、さすがに非力すぎて時折アクセスできないこともありました。かといってsmallを使うとスペックの割にコストがかかると考えさくらVPSを採用しました。

サーバーが決まったら次は環境の移行を行いますが、失敗することなく同一の環境を構築するためにまずはVirtualBox仮想マシンで動作を検証し、問題がなければさくらVPSでセットアップを行うという流れを取ることにしました。つまり2度セットアップを行うことになります。

2度同じセットアップを行うのはミスが入り込む余地だらけですし、そもそも面倒なのでいわゆるプロビジョニングツールを使うことにしました。PuppetとかChefに代表されるアレですね。今回はその中でも(機能的に)軽量であるという噂のAnsibleをトライアルするいい機会だと考え、Ansibleを使って移行を行うことにしました。

ちなみにAnsibleはhomebrewでインストールすることができます。↓でOK。

$ brew install ansible

Ansible 最終形

Ansibleでは、playbookというYAML形式の設定ファイルのようなものに処理を色々と書いておいて、playbookを指定して実行することで対象のサーバーを操作することができます。

今回最終的には次のようなコマンドを実行することになります。

$ ansible-playbook -i hosts all.yml

これはhostsというファイルに書いてあるサーバーに対してall.ymlに書いてある処理をしてね、という意味になります。

まずhostsですが、次のようになっています。

# hosts

[server]
# 192.168.33.11
cfalt2

コメントアウトしている一つ目のIPはVagrantで立ちあげたVirtualBoxIPアドレスです。テスト時はこのコメントを外します。

2つ目のcflat2とあるのは、~/.ssh/configに書いてある設定の中のホスト名です。本番サーバーを意味します。

次にplaybookのall.ymlはこうなっています。

# all.yml

- include: ssh-iptables.yml
- include: yum.yml
- include: lamp.yml
- include: mysql-secure-installation.yml
- include: mysql-initialize.yml
- include: prepare-contents.yml

playbookでは他のplaybookをincludeすることができるようになっています。all.ymlではもう少し細かい単位に分けたplaybookをincludeするだけにしてあります。書かれている内容は上から順番に実行されます。

ざっくり言うと、

  1. iptablesの設定して
  2. yumの設定して
  3. ApachePHPMySQL入れて
  4. MySQLのインストールして
  5. MySQLの初期設定して(ここは↑とまとめてもよかった)
  6. 元サイトのコンテンツを入れる

という流れになっています。

各playbook解説

playbookを載せると長くなるかもしれませんが、せっかくの事例紹介なので全部お見せします。

ssh-iptables.yml

まずはSSHiptablesの設定をするplaybook。

- name: change ssh settings and iptables
  hosts: server
  sudo: yes
  tasks:
    - name: create iptables
      template: src=files/iptables.j2 dest=/etc/sysconfig/iptables

    - name: disallow root SSH access
      lineinfile: dest=/etc/ssh/sshd_config regexp="^#PermitRootLogin " line="PermitRootLogin no" state=present
    - name: disallow password authentication
      lineinfile: dest=/etc/ssh/sshd_config regexp="^#PasswordAuthentication " line="PasswordAuthentication no" state=present

    # NOTE: Combine two tasks to keep ansible access with port 22
    - name: restart sshd and restart iptables
      shell: service sshd restart && /etc/init.d/iptables restart

いきなりAnsible固有の色々がでてきたのでここで多少解説をいれておきます。

項目 意味
name その部分の名前、確かログに出るぐらいの意味
hosts 対象ホスト、hostsの[server]セクションにある全てのhostを対象にする
sudo rootで実行するかどうか
tasks 処理を書くところ
template テンプレートファイルをdestにコピーする。変数展開も使える。
lineinfile destファイルに指定した行があることを確約したり、置換したりする
shell shellコマンドを実行する

このplaybookの中でtemplateが参照しているfiles/iptables.j2iptablesの設定を書いておきます。

yum.yml

続いてyumリポジトリを追加します。OSに最初から設定されているリポジトリだけでは足りなかったので。書くのが遅れましたがOSはCentOS 6.3です。

commandyumとかありますが、なんとなく読めると思います。

- name: yum setting
  hosts: server
  sudo: yes
  tasks:
    - name: update yum
      command: yum -y update

    # repository
    - name: add repository 'rpmforge-repo'
      yum: name=http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm state=present
    - name: add repository 'remi-repo'
      yum: name=http://rpms.famillecollet.com/enterprise/remi-release-6.rpm state=present

lamp.yml

次にApacheMySQLPHPを入れましょう。

- name: Installing Apache, PHP, MySQL
  hosts: server
  sudo: yes
  vars:
    httpd_port: 80
    mysql_port: 3306

  tasks:
    # Apache
    - name: install apache
      yum: name=httpd enablerepo=remi,epel,rpmforge state=present
    - name: start httpd
      service: name=httpd state=started enabled=yes
    - name: add iptables rule for httpd
      lineinfile: dest=/etc/sysconfig/iptables regexp="{{ httpd_port }}" line="-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport {{ httpd_port }} -j ACCEPT" insertbefore="^# add end" state=present
      notify: restart iptables
    # PHP
    - name: install php
      yum: name={{ item }} enablerepo=remi,epel,rpmforge state=present
      with_items:
        - php
        - php-mysql
    # MySQL
    - name: install mysql
      yum: name={{ item }} enablerepo=remi,epel,rpmforge state=present
      with_items:
        - mysql
        - mysql-server
        - MySQL-python
    - name: add iptables rule for mysql
      lineinfile: dest=/etc/sysconfig/iptables regexp="{{ mysql_port }}" line="-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport {{ mysql_port }} -j ACCEPT" insertbefore="^# add end" state=present
      notify: restart iptables

  handlers:
    - name: restart iptables
      service: name=iptables state=restarted

長いですね。ここで見るべきはvarshandlersnotifywith_itemsあたりでしょうか。

項目 意味
vars 変数を使える。あとから参照するには{{ variable }} などとする
handlers notifyとセットで使う。他のタスクから呼び出せる共通の処理。
notify handlersに書いた処理の名前を指定することでその処理を呼び出せる。
with_items 複数の項目に同じコマンドを実行したいときに使う。項目は{{ item }}として参照できる。

mysql-secure-installation.yml

これもなんとなく読めると思います。

  • rootパスワードの設定
  • template.my.cnfファイルをコピー

などを行います。

-  name: MySQL setup
   hosts: server
   sudo: yes
   vars:
     root_db_password: XXXXXXXX
     mysql_root_password: XXXXXXXXX
   tasks:

     - name: ensure mysql is running and starts on boot
       service: name=mysqld state=started enabled=true

     # Need to do this for idempotency, see
     # http://ansible.cc/docs/modules.html#mysql-user
     - name: update mysql root password for all root accounts
       mysql_user: name=root host=localhost password={{ root_db_password }}

     - name: copy .my.cnf file with root password credentials
       template: src=files/.my.cnf dest=/root/.my.cnf owner=root mode=0600
  
     - name: update mysql root password for all root accounts
       mysql_user: name=root host={{ item }} password={{ root_db_password }}
       with_items:
         - 127.0.0.1
         - ::1
  
     - name: ensure anonymous users are not in the database
       mysql_user: name='' host={{ item }} state=absent
       with_items:
         - localhost
         - 192.168.33.11
         
     - name: remove the test database
       mysql_db: name=test state=absent

mysql-initialize.yml

ここではサイト用のMySQLユーザーやデータベースを作っています。

- name: Creating DB User
  hosts: server
  vars:
    dbname: dbname
    dbuser: dbuser
    loginuser: root
    loginpassword: XXXXXXXX

  tasks:
    - name: create database for wordpress
      mysql_db: db={{ dbname }} state=present encoding=utf8 login_user={{ loginuser }} login_password={{ loginpassword }}

    - name: create a user for wordpress
      mysql_user: name={{ dbuser }} password="YYYYYYYY" priv={{ dbname }}.*:ALL state=present login_user={{ loginuser }} login_password={{ loginpassword }}

prepare-contents.yml

このplaybookではコンテンツをサーバーにセットアップします。

ウェブサイトにはWordpressを用いています。このWordpressのファイル(テーマやプラグイン含む)はgitで管理しているのでgitから取得します。また、データベースに入っているデータ類はあらかじめmysqldumpなどでエクスポートしておきます。

  • gitをインストール
  • wordpressをgitからclone
  • apacheのvirtualhost設定をコピーする
  • apacheを再起動
  • sqlファイルをコピーし、DBにインポート
- name: instal Git and clone wordpress from git repo
  hosts: server
  sudo: yes
  tasks:
    # Git
    - name: install git
      yum: name=git enablerepo=remi,epel,rpmforge state=present
      
    - name: create contents dir
      file: path=/var/www/contents state=directory owner=apache group=apache

    - name: clone from repo
      git: repo=your_repo dest=/var/www/path/to/dir

    - name: copy vhost
      template: src=files/virtual.conf dest=/etc/httpd/conf.d/virtual.conf owner=root mode=0644
      notify: restart httpd

    # initial data
    - name: copy database.sql to server
      copy: src=files/database.sql dest=/tmp

    - name: import database.sql to db
      mysql_db: name=dbname state=import target=/tmp/database.sql
      
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted

以上のplaybookを全て実行するとサーバーのセットアップからコンテンツの移行までを一括して行ってくれます。

$ ansible-playbook -i hosts all.yml

この状態でサーバーにアクセスすると元サイトと同じ状態になっていることがわかると思います。あとはドメインの設定を変更して、新サーバーを指すようにしてあげれば完了です。

Ansibleのようなツールを使うことで仮想環境でテストしてから本番に適用というのがとても楽ですし、次に同じようなの移行が必要になっても、最後のコンテンツ部分(gitとかsql)の部分を新しくしておくだけで、あとの手順はコマンド一発になり非常に便利です。サーバーの状態がコードとして書かれているというのもよく言われるメリットですね。

Ansibleを使うことで学習コストがかかりましたが、手作業でやってしまおうという誘惑にまけなくてよかったと感じています。

Ansible、Chefに比べるとシンプルで使いやすいという印象をうけました。今回のような小さめのプロジェクトではAnsible優位な気がします。

CUDA6.0用にCPUとGPUの速度比較コードを修正

以前の記事でCUDAプログラミングする際に、CPUとGPUを切り替える方法を書いたのですが、 その2日後にCUDA6.0が正式リリースされ、Unified Memoryという 新しいメモリ確保の仕組みが提供されました。

メモリ確保が簡単に

CUDA5.5まではCPUとGPUの両方にメモリ領域を確保して、それぞれのメモリからメモリへデータを転送するという作業をユーザーがする必要がありました。 CUDA6.0からはCPUとGPUで使用するメモリを1変数で管理できるようになりました。当然データを転送する関数をユーザーが呼ぶ必要もありません。

やるべきことはGPUで使用する変数を用意してcudaMallocManaged()で領域を確保し、使い終わったらcudaFree()で解放するだけです。 メモリ管理が簡単になったので、今回は下記のようなコンストラクタとデストラクタでメモリの確保と解放を行うCpuGpuDataクラスを作ってみました。

SwitchableCPUGPU.cuh

#pragma once
#include "Timer.cuh"

#define USE_GPU

#ifdef USE_GPU

#define SWITCHABLE_DEVICE __device__
#define SWITCHABLE_GLOBAL __global__
#define SWITCHABLE_TIMER CudaEventTimer

#else

#define SWITCHABLE_DEVICE  
#define SWITCHABLE_GLOBAL  
#define SWITCHABLE_TIMER Timer

#endif

template <class T>
class CpuGpuData {
public:
  CpuGpuData( const int iSize )
  {
    cudaMallocManaged( &m_data, sizeof(T)*iSize );
  }
  ~CpuGpuData()
  {
    cudaFree( m_data );
  }

  T* m_data;
};

Timerクラスは前回と同じです。

Timer.cuh

#pragma once
#include <string>
#include <time.h>

class CudaEventTimer
{
public :
  CudaEventTimer( const std::string& message ) : m_message( message )
  {
    cudaEventCreate(&m_start);
    cudaEventCreate(&m_end);
    cudaEventRecord( m_start, 0 );
  }
  ~CudaEventTimer()
  {
    cudaEventRecord( m_end, 0 );
    cudaEventSynchronize( m_end );

    float time;
    cudaEventElapsedTime( &time, m_start, m_end );
    printf("%s = %f sec.\n",m_message.c_str(), time*0.001);

    cudaEventDestroy( m_start );
    cudaEventDestroy( m_end );
  }

private:
  cudaEvent_t m_start;
  cudaEvent_t m_end;
  std::string m_message;
};

class Timer
{
public :
  Timer( const std::string& message ) : m_message( message )
  {
    m_start = clock();
  }
  ~Timer()
  {
    m_end = clock();
    printf("%s = %f sec.\n",m_message.c_str(), (double)(m_end - m_start)/CLOCKS_PER_SEC);
  }

private:
  clock_t m_start;
  clock_t m_end;
  std::string m_message;
};

コードもすっきり

下記が前回と同じ処理を行うテストコードです。メモリの確保と解放をクラスが行っているのですっきりしています。

#include <stdio.h>
#include "SwitchableCPUGPU.cuh"
#include <iostream>
#include <math.h>

SWITCHABLE_GLOBAL void Calculate( float* matrixA, float* matrixB, float* matrixC, int iLength, int col = 0, int row = 0 )
{
#ifdef USE_GPU
  row = blockIdx.x * blockDim.x + threadIdx.x;
  col = blockIdx.y * blockDim.y + threadIdx.y;

  if ( row > iLength || col > iLength ) return;
#endif

  float target = 0.0f;

  for ( int i = 0 ; i < iLength ; ++i ) {
    target += matrixA[row*iLength + i] * matrixB[i*iLength + col];
  }
  matrixC[row*iLength + col] = target;
}

int main()
{
  // 行列のサイズ決定
  const int iLength = 1024;
  const int iSize = iLength * iLength;

  CpuGpuData<float> matrixA( iSize );
  CpuGpuData<float> matrixB( iSize );
  CpuGpuData<float> matrixC( iSize );

  for ( int col = 0; col < iLength ; ++col ){
    for ( int row = 0; row < iLength ; ++row ){
      matrixA.m_data[col*iLength + row] = rand() % (1000);
      matrixB.m_data[col*iLength + row] = rand() % (1000);
      matrixC.m_data[col*iLength + row] = 0.0f;
    }
  }

  // ここから時間計測
  SWITCHABLE_TIMER t("time");

  // 行列計算
#ifdef USE_GPU
  const int iThread = 16;
  dim3 thread( iThread, iThread );
  const int iBlock = ( iLength + iThread - 1 )/iThread;
  dim3 block( iBlock, iBlock );

  Calculate<<<block, thread>>>( matrixA.m_data, matrixB.m_data, matrixC.m_data, iLength );
  cudaDeviceSynchronize();

#else
#pragma omp parallel for
  for ( int i = 0 ; i < iLength ; ++i ) {
    for ( int j = 0 ; j < iLength ; ++j ) {
      Calculate( matrixA.m_data, matrixB.m_data, matrixC.m_data, iLength, i, j );
    }
  }
#endif

  return 0;
}

実行結果

実行結果も載せておきます。このプログラムでは前回の結果とほぼ同じです。 Unified Memoryの実行速度についてはこちら で詳しく調査されています。メモリの転送が自動で行われてしまうので、注意しないと遅くなるようです。

GPU:0.149291s
CPU OpenMP:1.231000s
CPU 並列化無し:7.213000s

GPU計算の高速化

今回のプログラムでも全く行っていませんが、GPU計算には色々な高速化手法があります。 興味のある方は下記の書籍等を参考にして下さい。

setTimeout() vs ハッカー、仁義なき戦い

 早速ですが、以下のHTMLを見て下さい……。

<!doctype html>
<html>
    <head>
       <meta charset="UTF-8">
       <title>サンプル1</title>
       <style>
        #counter { font-size: 3em; font-family: monospace; color: blue; }
        </style>
       <script type="text/javascript" src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
       <script type="text/javascript">
       (function(){
           $(document).ready(function() {
               $('#start').on('click', function(){
                   var counter = 0;
                   $('#time').text(counter);
                   var timer = setInterval(function() {
                       ++counter;
                       $('#time').text(counter);
                       if (counter == 10) clearInterval(timer);
                   }, 1000);
               });
           });
       })();
       </script>
   </head>
    <body>
        <div>
            <p>
                <button id="start">計測開始!</button>
            </p>
            <p>
                <span id="counter"><span id="time">-</span>秒経過</span>
            </p>
        </div>
    </body>
</html>

 実行してみれば……というか実行しなくてもわかりますが、経過秒数を数えてゆくだけの単純なスクリプトです。  複数回押下への対策をしていないとか、本当はsetInterval()だと正確な秒数を数えられないとかありますが、今回はsetInterval()あるいはsetTimeout()(以下、setTimeout()系関数)の動作について述べたいだけなので気にしない方針で。

 兎に角、ゲームか何かのアニメーション処理を、これらの関数を使って行なおうとしている、とお考え下さい。  この時、もしもユーザーがsetTimeout()系関数が行なう処理を不正に飛ばそうとした時、ウェブサイトはこの事を検出できるでしょうか?  というのが、今回の話題です。

Round 1

 まず、上記のサンプル1を開いたら、JavaScriptコンソールなり、Firebugなり、兎に角デバッグ用コンソールを開きます。  そして、次のJavaScriptを実行してみましょう:

var setInterval2=window.setInterval;window.setInterval=function(a,b){ return setInterval2(a, b/100); }

 なんという事でしょう、「計測開始!」ボタンを押したら、もの凄い速度で処理が進むようになりました!  どうやらwindow.setIntervalは読み取り専用プロパティではないようで、ユーザーが上書きできてしまうようです。

 だがしかし!  デバッグ用コンソールを開いて上記のコードを打ち込むためには、手動でどんなに頑張ったところでたかが知れている!  ページを開いた瞬間に、兎に角window.setIntervalを変数に保存するようにしてはどうか。

(function(){
    var setInterval2 = window.setInterval; // <======
    $(document).ready(function() {
        $('#start').on('click', function(){
            var counter = 0;
            $('#time').text(counter);
            var timer = setInterval2(function() { // <======
                ++counter;
                $('#time').text(counter);
                if (counter == 10) clearInterval(timer);
            }, 1000);
        });
    });
})();

 ふっ……私の才能が怖いな。

Round 2

 ……などと安心していると当然足を掬われまして、世の中にはユーザースクリプトなるものが存在します。  代表的なものはFirefoxGreaseMonkeyですが、他のブラウザにも同じスクリプトを使えるようなアドオン(あるいはブラウザの組み込み機能)が用意されています。  そして、恐ろしい事に……metadataブロックに@run-at document-startを追加すると、HTMLを読み込んだ瞬間(!)にスクリプトが実行されてしまいます!

// ==UserScript==
// @name        VS サンプル2
// @namespace   http://localhost/test/blog/sample
// @include     http://localhost/test/blog/sample*.htm
// @run-at      document-start
// @version     1
// ==/UserScript==

(function(){
    var setInterval2 = window.setInterval;
    window.setInterval = function(a,b){
        return setInterval2(a, b/100);
    };
})();

 この時点では、DOMツリーの構築も完了していません。  DOMツリーが構築されていないという事は、scriptタグも未実行です。  すなわち、サンプル2でwindow.setIntervalを変数に保存した時には、既に書き換え済みのものになっています。console.log(window.setInterval.toString());とでもしてみれば一目瞭然。

 ……んん?  という事は、window.setInterval.toString()でネイティブコードかどうか判別すれば良いのでは!? 

// サンプル3
(function(){
    var setInterval2 = window.setInterval;
    if (!isNativeFunction(setInterval2, "setInterval")) {
        $(document).ready(function() {
            $('body').children().remove() ;
            $('body').append('<p class="error">setInterval()が不正です。</p>');
        });
        return;
    }
    $(document).ready(function() {
        $('#start').on('click', function(){
            var counter = 0;
            $('#time').text(counter);
            var timer = setInterval2(function() {
                ++counter;
                $('#time').text(counter);
                if (counter == 10) clearInterval(timer);
            }, 1000);
        });
    });

    function isNativeFunction(func, name)
    {
        var match = func.toString().match(/^function (\S+)\(\)\s*{\s*\[native code\]\s*}$/);
        return (match && match[1] === name);
    }
})();

Round 3

 ……だが甘い! ならば、window.setIntervalを書き換えると同時に、window.setInterval.toStringや、まで書き換えてしまえばどうなるのか!?

(function(){
    var setInterval2 = window.setInterval;
    window.setInterval = function(a,b){
        return setInterval2(a, b/100);
    };
    window.setInterval.toString = function() { return setInterval2.toString(); };
    window.setInterval.toSource = function() { return setInterval2.toSource(); }; // toSource未対応ブラウザへの対応などは省略
    window.setInterval.hasOwnProperty = function(x) { return setInterval2.hasOwnProperty(x); };
})();

 なんて事! hasOwnPropertyでのチェックまで封じられてしまった!

 ……いいや、我々にはまだ、for inなる力強い味方が残っている。組み込み型のtoStringはDontEnum属性がついているから、toStringfor inで列挙できたならtoStringが改竄されているということ。propertyIsEnumerableメソッドを使った場合にはそれ自身が上書き済みの可能性があるが、for inであればその心配もない!!

function isNativeFunction(func, name)
{
    for (var o in func) {
        if (o === "toString") return false;
    }
    var match = func.toString().match(/^function (\S+)\(\)\s*{\s*\[native code\]\s*}$/);
    return (match && match[1] === name);
}

 勝利!!

Round 4

 ……ところでさ。  たぶん、アドオンを自作したりしたら特定URLでだけwindow.setTimeout系関数が高速に動作するとかできますよね。  仮にアドオンでは無理だとしても、オープンソースのブラウザのソースコードを弄れば簡単ですよね。  他にも、上手いことプロキシを噛ませてチェックコードを取り除いてやったりするとか、とか、いくらでもやりようはありますよね。

 結論:そんなに時間を誤摩化されたくないWebアプリケーションなら、素直にサーバー側プログラムでどうにかしましょう。

下手な高速化で地獄を見たお話 ~ XCode編 ~

こういうエラーが起きました。

f:id:cflat-inc:20140715144324p:plain

「Could not insert new outlet connection: Could not find any information for the class named」というエラー。

第一手:IDEIndexDisableという犯人

スタックオーバーフローさん等でいろいろ解決方法が載っており、それらを片っ端から実行していったのですが、ダメ。 XCodeをアップデートしてもダメ。

そんな時この記事で一つ見逃していた箇所を発見。 ios6 - Could not insert new outlet connection - Stack Overflow

I recently came across this problem. I soon realized that the cause had been my own doing. I had previously disabled XCode indexing (which used to take forever & eat up my RAM), using the below code in a terminal window:

defaults write com.apple.dt.XCode IDEIndexDisable 1 To revert XCode to its default state, i used the following line in a terminal window:

defaults write com.apple.dt.XCode IDEIndexDisable 0 Voila! All's well again..

ほほう。。。。これは。。。。過去に

terminalを開いて defaults write com.apple.dt.XCode IDEIndexDisable 1 これで、おっけい。(・∀・)

みたいな記事を参考に、インデックス機能をオフにしたことがあるという記憶がヨミガエってきたのでした。 犯人はこれだ!!

よっしゃ、

また、Indexingを戻したかったら defaults write com.apple.dt.XCode IDEIndexDisable 0 こうしたらできましたー バンザイ!(゚∀゚)アヒャヒャ

みたいな記事の言う通りに戻してみよう!!

えいっ!!

とやるもこれが戻らない。(゚∀゚)アヒャヒャ

第二手:Preferencesは弱かった

なんで戻らないんだ? ターミナルからでなく、ライブラリ/Preferences/com.apple.dt.XCode.plistを直接書き換えてやったら? これだと、0に書き換えた直後にまた1に戻る。保存もしているのに。 つまり、別のところにも同じ情報持ってるってことですね。Preferencesより強い何かがいるってことですね。

XCodeのアップデートしても効果なかったのだから、アプリケーションフォルダは犯人から除外してよさそうですね。 探してやると、ライブラリ/Cashces/com.apple.dt.Xcodeというやつと、ライブラリ/Saved Application State/com.apple.dt.Xcode.savedStateというやつがいた。 お前たちか、僕をOS再インストールまで追い込もうとしている可愛い子たちは、よしよし。 お前らを削除してこれで気分爽快、綺麗サッパリ、すっきりもっきりだ。

えいっ!!

とやるもこれが戻らない。(゚∀゚)アヒャヒャ

第三手:ゴミは捨てよう

なぜだ、なぜなんだ、どうしてなんだ。

どうして戻らないんだ、この設定は。

もはや打つ手がないぞ。

と思った最後の起死回生の一手、会心の一撃、それは「ゴミ箱を空にすること」。

ゴミ箱行ってもリンク切れてなかったなんて。。。

Xcodeで余計なCleanを避けるスクリプト

 XCodeでフォルダ内のファイルを全てプロジェクトに追加する方法には、二種類あります。

f:id:cflat-inc:20140623093944p:plain

 一つは「Create groups for any added folders」、もう一つは「Create folder references for any added folders」です。フォルダの色が黄色か青色か、という区別の仕方もアリ。

f:id:cflat-inc:20140623093921p:plain

 この二つの違いは以下の通りです:

  • Create groups ...(黄色)
    指定したフォルダと同様にグループ階層を作成し、その中に個別のファイルを追加します。
    フォルダ内に新たにファイルを追加しても、プロジェクトには一切影響がありません。
    リソースにこの方法でファイルを追加した場合、使用時にはフォルダ名を含まずファイル名のみで指定します。
  • Create folder references ...(青色)
    指定したフォルダそのものをプロジェクトに追加します。
    フォルダ内容が変更された場合、自動でXCode上にその変更が反映されます。
    リソースにこの方法でファイルを追加した場合、使用時にはフォルダ名を含んだファイル名で指定します。

 ですので、例えば「これから画像が増えて行くけれど、画像が出来る度にプロジェクトに登録してやるのは面倒くさいぞ」という場合には、「Create folder references ...」の方を選べばOKです。

……と思いきや

 ですが実は、ここに一つ落とし穴があるのでした。

 XCodeでは当然ながら、ビルド時に変更のないファイルをチェックし、不要なコンパイルを抑制する処理を行なっています。しかし、「Create folder references ...」で作った青フォルダの中のファイルを更新した場合、フォルダそのものには全く変更が加わっていないことに注意です。  つまり……ファイルをいくら更新しても、リソースが更新されません。  この状況で正しくリソースの変更を反映するためには、ビルド済みのモジュールから、該当のリソースを削除してやる必要があります。

 一番簡単なのは、一度Product > Cleanとする方法です……が、これでは折角のオブジェクトファイルまで削除してしまうため、巨大なプロジェクトでは再ビルドに恐ろしく時間がかかってしまいます。リソースだけを差し替えるためにソースを全てコンパイルし直すというのは、控えめに言っても愚かの一言です。

 そんなわけで、次のようなスクリプト、rmapp.shを作ってみました:

#!/bin/bash

if [ $# -ne 1 ]; then
  echo "Usage: ./$0 projectname"
  exit 1
fi

for path in ~/Library/Developer/Xcode/DerivedData/$1-*/Build/Products/*
do
  if test -e "${path}/$1.app" ; then
    echo "rm -rf \"${path}/$1.app\""
    rm -rf "${path}/$1.app"
  fi
done

exit 0

 使い方は、次の通りです:

./rmapp TestProject

 やっている事としては、関連するっぽいappを兎に角片っ端から探して削除しているだけです。  同名の別プロジェクトがあればそちらも削除してしまいますが、Cleanするのと比べればappの作成処理にかかる時間など微々たるものなので、気にする必要はないでしょう。

Parse.comのREST APIを使ったPush通知

複数のアプリに対してParse.comを介してPush通知を送りたい場合、Webインターフェースでポチポチやるのは効率が悪く、とても辛い作業になってしまいます。Parse.comのサイトはレスポンスが少し遅いなと感じる程度である上に、1回1回が退屈な作業を何度も繰り返すことになるのでミスも入り込み易いのです。当然ですが、自動化・半自動化をするのが望ましいでしょう。

複数種類のiPhoneアプリを同時開発するときのTips - 株式会社CFlatの明後日スタイルのブログ複数種類のiPhoneアプリを同時開発するときのTips - 株式会社CFlatの明後日スタイルのブログはてなブックマーク - 複数種類のiPhoneアプリを同時開発するときのTips - 株式会社CFlatの明後日スタイルのブログ

REST API用のKEY

REST APIを使うためには、アプリケーションIDとREST API用のアプリケーションキーが必要です。これらのキーを自動化スクリプトなり、専用のファイルなり、どこかに転載しておきます。

なお、iOS用のアプリケーションキーREST API用のアプリケーションキーは異なります。

Rubyクライアント

REST APIcurlのようなコマンドから呼び出すことも可能ですが(Push Developer Guide | Parse)、スクリプト言語から呼び出した方が楽だと個人的には思います。

私が常用しているRubyにはparse-ruby-clientというgemがあったのでこちらを使うことにしました。

data = { :alert => "This is a notification from Parse" } push = Parse::Push.new(data, "user_1") push.type = "ios" push.save

使い方

user_1というチャンネルに該当するiOSバイスにデータを送る、という処理を次のコードで実現できます。

require "parse-ruby-client"

Parse.init(application_id: id, application_key: key)

data = { :alert => "This is a notification from Parse" }
push = Parse::Push.new(data, "user_1")
push.type = "ios"
push.save

全デバイスに送る

先ほどの例はとあるチャンネルに該当するユーザーにのみ通知を出す方法なんですが、今回はチャンネルではなくユーザー全体を対象にしようとしていました。

そこでチャンネルを空にしてやればいいだろうとParse::Push.new(data, "")Parse::Push.new(data, nil)などを試したところ、これでは0人に対してのpush通知を発行するという意味になるらしく失敗に終わりました。

どうやらREST API本体に存在しているwhereというパラメータに空のjsonを渡せばいいということでした。このラッパーの場合はこのように書く必要があります。

require "parse-ruby-client"

Parse.init(application_id: id, application_key: key)

data = { :alert => "This is a notification from Parse" }
push = Parse::Push.new(data) 
push.where = {}
push.save

データファイルのサンプル

複数のアプリケーションにプッシュ通知を送るスクリプトですが、実際にはRakeタスクとして定義しました。前提としては、アプリケーションIDやREST用のキーはyamlファイルに書いてあります。

app1:
  parse_id: APP_ID_1
  parse_rest_key: APP_REST_KEY_1
  ...
app2:
  parse_id: APP_ID_2
  parse_rest_key: APP_REST_KEY_2
  ...
app3:
  ...

タスク本体はこんな感じです。

require "yaml"
require "parse-ruby-client"

task :push do
  config = YAML.load_file( File.expand_path("../../data.yml", __FILE__) )
  config.keys.each_with_index do |key|
    parse_id  = config[key]["parse_id"]
    rest_key = config[key]["parse_rest_key"]

    data = {
      alert: "alert title",
      message: "alert message"
    }

    # ParseのREST APIを使うコード
    Parse.init(application_id: parse_id, api_key: rest_key)
    push = Parse::Push.new(data)
    push.where = {}
    push.save
  end
end

これで半自動化できるようになりました。通知内容がハードコードされているので何回も使うのには適しません。そのような場合は通知内容の部分を分離し、引数のjsonファイルから読み込むなどの工夫が必要となります。