廃墟

本ブログは更新を終了しました。 技術的な記事のみ、有用性を鑑みて残しておきます。

MiddlemanでBootstrap-sassを使うとGlyphiconsが出ないことへの対処法

最近Middlemanにハマってます。

ここのページには、Bootstrap-sassを使うには単に

# Gemfile
gem "bootstrap-sass", :require => false

で良いように書いていますが、これだけだとGlyphiconsが表示されません。

これに対処するには、config.rb中で以下のように読み込む必要があります。

# config.rb
require "bootstrap-sass/sass_functions.rb"

尚、自分のブログでぶつくさ言ってるだけでContributeしないのは良くないので、Pull Request として送っておきました。

最近のWeb開発技術フロントエンド周りのまとめ(2014年)

主に知人向けに、最近のWeb開発技術について、私が最近知る限りの事をまとめておきます。

VagrantとかChefみたいに、おおまかに知っているだけで使ってないものも紹介しています。間違いがあったらコメントを頂ければ訂正しますので、ご指摘下さい。

また、フロントエンドよりの記述が多く、インフラやバックエンドのことには触れていません。

大前提

パッケージマネジメントシステムの有用性

Linuxのサーバーサイドでは当然、パッケージマネジメントシステムが存在しますが、ソフトウェア開発においてもこれは非常に重要になります。

などなど。

こういったシステムを使うことの利点として以下があります。

  • ライブラリの公式の入手場所が辿りやすい
  • どこからどこまでがライブラリか分かりやすい
  • プロダクト特有のコードと、ライブラリのコードが分離される
  • ライブラリのバージョンアップがしやすい

相互に重複気味な表現もありますがご容赦。

Rails界隈の知識です

もしかするとTomcatなどから進化した界隈ではまた別の最適化があるかもしれません。 .NET界隈もVSでBootstrapが使える様になったりしてるらしいので、色々あるようです。 node界隈にも僕は詳しくないです。 PHPは知りませんが、Ruby界隈と割と業界が近いのでそれほど差異がないかRubyより劣る事が多いように思います。

ローカルで開発してリモートにデプロイ

基本的に、近年はローカルで開発して、リモートにデプロイするのが普通だと思います。 その方がGUIベースのIDEも使えるし、本番環境を壊す恐れが少ないからです。

但し、デプロイはセキュリティの観点や、考慮すべき事項の多さから往々にして属人的になりやすいです。詳しくは後述します。

コンパイルする簡易言語たち

アセンブリC言語の歴史をWebが同じように辿っているだけなのですが。
Webブラウザはその基盤となる言語を変更しにくいです。(CPUの命令セットが変更しにくいのと同様) HTML、CSSJavaScriptといったマークアップ言語およびプログラミング言語がそれにあたります。

Sass(SCSS) および LESS

SassとLESSは、CSSを書き出すための言語です。
私は数年前はLESSを使っていたのですが、 @extend が使える点などから、今ではSassに乗り換えています。

簡単にCSSが書けるだけであれば、大きな利便性はないのですが、@extend、@mixinなどの機能と、後述するCSSフレームワークとの相性が抜群です。

CoffeeScript

JSはプロトタイプベースだったり、this問題が起きたり、そもそも言語を問わず非同期処理は難しかったり、DOMが難しかったり、身につけるのが大変な言語ですが、CoffeeScriptは、それを少しだけ楽にしてくれます。少しだけです。

minifyとの相性

css,jsはminifyすることがありますが、セミコロンがないJSとかだと、minifyしたときにバグを生んでしまうことがあります。 上記の言語を使っていると、そういった問題が起きにくいように思います。

リソースをコンパイルするSprockets

Rails3.1に導入されたAsset pipeline(及びベースとなるSprockets)では、js,cssを簡単にコンパイルすることが出来る上、ハッシュ値を利用してキャッシュが有効にならないようにするなどの機能も有していて便利です。

また、上記のパッケージマネジャとの相性も考えられていて、パッケージマネジャで追加されたライブラリをJSやCSSの先頭で指定することで、それらを読み込むようにできます。(require, require_tree)
読み込んだファイルはSprocketsの設定にも依りますが、小規模なサイトであれば1つのJS,CSSに統合してしまうのが良いです。 そうすることで、コストの高い無駄なHTTPリクエストを減らすことができて、サイトの表示が非常に高速になります。

類似: hamlとslim

railsではhtmlコードを吐き出すためにerbという、PHPと似たような形式の、HTMLのRubyのコードを埋め込む仕組みが使われてきましたが、htmlの閉じタグってムダですよね。 ...ということで、最近はhamlやslimといった記法が使われます。

自動リロード

少し前から、自動リロードがアツいように思います。

検証する端末を全てデスクに広げておいて、コードを変更すると全てでリロードされる、なんてのも可能です。

livereload (guard-livereload)とか、Yeomanとか。

特に、HTML/CSSを記述するときはかなり手軽になって良いです。 マルチディスプレイとの相性が良いです。 表示を確認する画面、コードを書く画面、デザインファイルを参照する画面で3画面欲しいですね。 表示の確認はiPadとかのタブレットデバイスでも良いですけど。

ローカル環境の構築とデプロイ

デプロイ

デプロイは一般に面倒です。 ミスったときのロールバックはどうするのとか、変更されたファイルを選んでそれだけアップロードしたいなーとか、上記のコンパイルする言語を先にコンパイルしなきゃなーとか・・・。 で、その状況を放置して行くと、プロジェクトリーダーしかデプロイ方法が分からないといったことになって、リーダーが疲れているときに酷いミスをやらかして大変なことになるわけです。

これを防ぐために、デプロイの一連の処理はスクリプトとして記述するのが最近のトレンドです。
先のパッケージマネジャでライブラリをインストールするような処理もサーバー側で出来るので、直接転送する量が少なくて済む、なんてメリットもあります。

Rails界隈でのデプロイツールはCapistranoデファクトスタンダードです。普通はgitと併用します。

ローカル環境の構築

でもさ、複数人で開発してるときってローカル環境を構築するのが面倒じゃん。だから本番環境でそのまま開発すれば、よくね?

・・・という声が聞こえてきそうですが、一発殴って良いですかね?
面倒なのは事実なんですが、ChefとかVagrantとかで解決するようです。 基本的にはローカルにプロジェクト固有の開発用の仮想マシンを立ち上げて、開発者全員が同じ設定の開発環境を自動セットアップ出来るような仕組みです。 Chefに関しては本番環境で使うことの方が多いかも知れません。

CSSフレームワーク、JSフレームワーク

CSSもJSも、毎回似たようなことばかり書くのに辟易した偉い人達が、さまざまな知見を集約してくれました。昔とは大違いなので、CSSを1から書くとか、もうやめましょう。

CSSフレームワーク

特に有名なのがTwitter Bootstrapでしょう。 バージョン3ではHTML5対応、レスポンシブデザイン対応が行われ、クラス名も以前のrow-fluidといった面倒なのが減って、デザインも含めて以前より洗練されました。
バージョン3になってからは「これBootstrap臭がするな・・・」といった、特有の画面になることが減ったように思います。 トレンドがシンプルな(いわゆるフラット)デザインになっているのも大きいでしょうね。

にわかCSS原理主義者は、Bootstrapを使うとHTMLに直接classを定義するからセマンティックではないといったことを言うかもしれませんが、上記のsassを使うとそういった事はなくせます。自分が独自に定義したclassでextendすれば良いのですね。 尚「にわかCSS原理主義者」というのは過去の私です。。。

短いファイルでもsassのコンパイルに10秒以上かかるのがネックでしょうか。 でも、1から書くよりはやいことが多いと思います。

JSフレームワーク

以前からjQueryデファクトスタンダードとなっていますが、より動的なシステムでは、JSフレームワークが流行っているようです。
特に、変数のバインディングの面倒を減らしてくれる、MVVMアーキテクチャを採用したものが主流です。 Angular.jsとかBackbone.js とかそのへん。

よく動くサイトを作るときに重宝するでしょうね。

bowerを使おう

JS、CSSフレームワークを導入するとき、ついついダウンロードして所定のフォルダに入れて満足してしまいがちですが、冒頭で述べたメリットの裏返しがデメリットとなってしまうわけですね。
なので、bowerを使ってパッケージ管理をしましょう。

rails界隈では、Sprockets周りのサポートなども自動でやってくれるrails-jqueryとかbootstrap-sassとかいうgemが数多く用意されてるんですが、若干ブラックボックスが多くて、一長一短だったり。 Railsアプリを書いてる時はgemベースのものでも良いですが、bowerは他の環境でも使えるので良いです。

IDEもご検討下さい

ぼくはRubyMineちゃん!

vimとかemacsとかで出来るのは分かるのですが、個人的にはGUIによる開発の方が好みです。 環境構築に時間をかける必要が少ない気がします。 まぁ、お金払ってるし。。

おわりに

珍しく早起きしたので、脳内にあったWeb知識を一気に解放してみました。

7年ぐらい前はYahooのYUIが強かったと思うんですが、最近はGoogle,Twitterあたりが便利なものを出してるみたいです。
加えて、Web自体がここ数年で経済的にもかなり発達したので、上記のようなWeb開発をサポートすること自体をビジネスにしている会社も増えた様に思います。 herokuとかその辺ですね。

これからも、Web業界は発展を続けるでしょう。 どんな風になってるか、楽しみですね。

Middlemanでグローバルナビゲーションの今見ているページへのリンクにcurrentクラスをつける

Middleman便利ですね。

Webサイトといえばナビゲーションとかメニューが普通だとありますよね。 もちろんpartialなりlayoutなりに書いていると思うのですが、こうやって共通化してしまうと、一個だけ困る事があります。
まぁ、タイトルから推測がつくでしょう。 今みているページを示すナビゲーション要素を装飾したいときに困るんですよね。

f:id:akn_ep:20140409171205p:plain

こんな感じ。今Homeにいるから下にバーが出てる、という例。

ここで「しょうがない、メニューぜんぶ個別のページに書くか・・・」なんて考えるのはちょっと早い! 何とかするのが技術者、と思うわけです。

というわけで以下ハウツー。

本題

ヘルパーを追加する

まず、以下のヘルパーを追加しておきます。
"current_page?" はRailsなんかだとUrlHelperに含まれているのですが、Middlemanにはないので、適当に作ってしまいます。

# config.rb
require "lib/helpers"
helpers Helpers

# lib/helpers.rb
module Helpers

  def current_page?(path)
    current_page.url == path
  end

  def nav_link(link_text, link_path)
    class_name = current_page?(link_path) ? 'current' : ''

    content_tag(:li, :class => class_name) do
      link_to link_text, link_path
    end
  end
end

directory_indexes をオンに

以下のコードも適当な位置に追加しておいてください。

#config.rb

activate :directory_indexes

ナビゲーションのパーシャルを定義

僕はslim派ですが、別に何でも良いと思います。

/_nav.slim
ul
  = nav_link "Home", '/'
  = nav_link "Profile", '/profile/'
  = nav_link "Works", '/works/'
  = nav_link "Links", '/links/'
  = nav_link "Contact", '/contact/'

ここで、先のcurrent_page? の実装が甘いので、最後のスラッシュが必要になります。
directory_indexes がオフな環境でどうするかは調べていないのですが、適当にcurrent_page.url なり current_page.path なりを見て検査すれば何とかなると思います。

viewDidUnloadが使えなくなった今、ViewControllerでどうやってNSNotificationをobserveするか

表題の通りですが、ViewControllerでNSNotificationを受け取る事は非常に典型的なパターンです。
ここでは2通りの想定パターンについて簡単に紹介します。 (以下のコードはARCを利用している事を前提としています)

画面が表示されてないときはNotificationを受け取らなくて良い場合

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeHoge:) name:AKNHogeDidChangeNotification object:nil];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

画面が表示されてないときにもNotificationを受け取りたい場合

こっちが本題です。

viewDidUnoadがあった頃は、viewDidUnloadとdeallocでやれば良かったのですが、そういうわけにはいかなくなりました。

また、アプリケーションの状態遷移によっては以下の様なケースがあるようで、これらに対処しなきゃなりません。

  • ビューが最初にロードされる時にviewDidLoadが呼ばれる
  • 他の画面に遷移
  • メモリ警告発生(didReceiveMemoryWarningが呼ばれる)
  • 元の画面に遷移
  • viewDidLoadがまた呼ばれる

参考記事でも言及されているように、何も考慮せずにviewDidLoadでaddObserverしてdeallocでremoveObserverするだけだとNotificationを二回受け取る事になってしまうので、都合が悪いです。

なので、didReceiveMemoryWarningでremoveObserverしても良いんですけど、今後のiOSアップデートでちょっとでもイベントループが変化すると破綻するような感じで、あんまりやりたくない方針。

また、init系メソッドでaddObserverするという手もありますが、ビューのないビューコントローラでNotificationを受け取ったらクラッシュしそうだし、それを防ぐ制御コードを書くのも何だか馬鹿らしい。

などと考えた結果、以下の様にしちゃえば良いという事に気づきました。

- (void)viewDidLoad
{
    [self addNotificationObserver];
}
- (void)dealloc
{
    [self removeNotificationObserver];
}

- (void)addNotificationObserver
{
    // 念のため先に削除する。
    [self removeNotificationObserver];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeHoge:) name:AKNHogeDidChangeNotification object:nil];
}
- (void)removeNotificationObserver
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

流石に、これが期待通りに動かなくなるようなイベントループの変更はこないでしょう。たぶん。。

大いに参考にした記事

もとい、元ネタです。 iOS5まではこの方法で良かったんだよね。*1

*1:僕が本格的にコード書き始めたのはiOS6からだから、詳しくは知らないんですけどね。

Mac OSX mountain Lion でレインボーカーソルが出る問題とその対処(要約:Sophosをアンインストールしよう)

僕のMacBook Air 11-inch, Mid 2013 (OSX 10.9.1)での話ですが、多くのバージョンで同じ様な問題が発生するのではないかと。

経緯とか

最近どうもネットを見るときもワンテンポ遅れる。それだけならまだしも、レインボーカーソルが出てOSが反応しなくなってしまう事が増えたなぁと思っていて、
原因を調べてみると、bluetoothからのスリープ解除を無効にしろとかPRAMリセットしろとか、いろんな情報がひっかかります。
僕はbluetoothからのスリープ解除は使ってるし、それ以外の方法もあまり気乗りがしないなーと思いつつ、この状況にいつ頃からなったんだっけ、と考えていました。

そういえば、先日Macのセキュリティが不安になって無料のアンチウイルスソフト、Sophos Anti Virus を入れたんでした。
インストール直後からDNSをフックしてるのかブラウジングは重かったし、ちょうどその頃からだなーと思って
「Sophos rainbow cursor」などと検索してみると、こんな記事がヒット。

はい。どう見てもSophosが原因ですね。

Sophos Anti Virus のアンインストール方法

ちなみに、アンインストール手段が分かりにくいのですが、以下の手段で削除できるはずです。 (人によっては "Macintosh HD"が違う名前になってる事もあります。)
ここでは現時点(2014年2月6日)で最新のSophosのバージョン9系を想定しています。

  1. 「ターミナル.app」を開く
  2. cd "/Volumes/Macintosh HD/Library/Sophos Anti-Virus" と入力してEnter
  3. sudo ./remove_v9.sh
  4. パスワードを聞かれるので入力してEnter
  5. 5秒ぐらい待つと削除処理が動くので、止まったのを確認してからターミナルを閉じる

今のところおすすめはESET Cyver Securityかなぁ。

さて、削除も済んだところで、軽さに定評のある僕はESET Cyber Securityを導入しました。まだ体験版ですが、このまま何事もなければ購入すると思います。
Kasperskyとも迷ったんですが、ずいぶん昔に未知のウイルスをヒューリスティクスで検出してて印象よかったし、評判も良かったのでESETにしてみました。
別に他の製品でも良いと思いますけれど、どうしても有償ソフトの方が信頼出来ますね。以外と安いし。

Amazonでも買えるみたいです。


※ あまり無責任な事を言うとSophosの中の人からおこられるなーと思ったので、ESETに変えてから数日たってから記事を公開しています。 残念ながらSophosアンチウイルスが原因である事は明白でした。こういうソフトを作るのは大変ですね。

iPhone5S(arm64)でBlocksKitが使えるようになってた

libffiのバグが原因で、arm64向けにビルドしているとBlocksKitが使えませんでしたが、つい5日前ぐらいに直ったようです。

これは以前のBlocksKit1系ではなく、2系に対して行われています。
詳しく調べていないのですが、2系で廃止されたメソッドがある場合には対処できないかもしれません。 その場合は、速度を諦めてarmv7のみでビルドするなどで対処する方が良さそうです。

ただ、少し見た限り、2系はメソッドが洗練されていて、1系より良くなっているようでした。

現時点で対応するための Podfile の書き方

とりあえず以下のように書いておけばOKです。 そのうちstableとしてリリースされるでしょう。

pod 'BlocksKit', :git => 'https://github.com/pandamonia/BlocksKit.git', :commit => 'aa99a68674'

Issueに記述されている以下の指定方法だと、時折nextブランチにバグが含まれたりして、頻繁にバグを含んでしまいます。(実際ひっかかった…) まぁリリース前なのでしょうがないですね。

pod 'BlocksKit', :git => 'https://github.com/pandamonia/BlocksKit.git', :branch => 'next'


追記

こんな感じで紹介してもらったのですが、libffiを使わずに似たような機能を提供してくれるライブラリもあるみたいです。かなりしっかり作られているようなので、こちらの導入も検討してみたいですね。

iOSアプリ開発における正しいMVC

この記事は iPhone Advent Calendar 2013 - Adventar の12月24日分の記事になります。

今年もアドベントカレンダーが熱いわけですが、今年は僕は年中iOSアプリのコードを書いていたので、その中で手に入れた or 手に入れつつある様々な知見を皆さんに紹介していきたいと思います。

MVCって何だっけ? 〜UITableViewCellってあれで良いのか?〜

この記事を読んでいる方で、こんなコードを書いている人はいませんか?

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    
    UIImageView *myImageView = (UIImageView *)[cell viewWithTag:10];
    
    myImageView.image = [UIImage imageNamed:self.imageName];
    
    return cell;
}

はい。少し前までの僕です。。
しかし、ここはあえて言わせて頂きたい。 糞コードだ! …と。

そもそもこれって、Viewの分離ができていないわけで、"M"と"VC"でしか分かれてないわけです。 それってPHPSymfony使って書いてるのと変わらない概念なわけで、そんな前時代的な(以下省略

正しいUITableViewCellの扱い方

こんな時、どうすればいいのかっていうと、最低限やりたいのが「Viewの分離」なわけです。Controllerのもつ役割とViewの持つ役割を分離しましょう。

具体的には、こんな感じ。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    AKNGoodTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    
    AKNEntry *entry = [self.fetchedResultsController objectAtIndexPath:indexPath];
    cell.titleLabel.text = entry.title;
    cell.bodyLabel.text  = entry.body;
    
    return cell;
}

// 途中省略

@interface AKNGoodTableViewCell : UITableViewCell

@property (nonatomic) IBOutlet UILabel *titleLabel;
@property (nonatomic) IBOutlet UILabel *bodyLabel;

@end

@implementation AKNGoodTableViewCell

@end

こんなふうにして、StoryBoardからアウトレットを引っ張ってあげましょう。 ちゃんとAutomatic の所に出てきますよ。

と、ここまで書いたのですが、コードで説明したほうが早いのでコードを書いていたら深夜になったので、一旦この記事を公開してしまいます。
後で説明を追記しようと思うので、良かったら左上、「読者になる」ボタンを押してやって下さい!

AknEp/AKNAdventar · GitHub

その他ありそうな疑問など

これじゃCellを複数のViewControllerで使いまわせないけどどうしたらいいの?

セルに対応したNibファイルを作っておいて、 registerNib:forCellReuseIdentifier: というメソッドを使うと解決します。
但し、UITableViewControllerで直接的に行うのではなく、対応するセルのクラス側にクラスメソッドを容易すると、よりエレガントになりそうです。 (ファイル名を再定義する必要がなくなるので。)

これって普通のViewController でも同じようなことをした方が良いの?

yesです。

特に、複雑な画面では、部分ごとにUIViewのサブクラスを作ってあげると、効果的でしょうね。

フリーランスでやってます。

いろいろ引き受けてます。興味があったら以下のメールアドレスまでどうぞ。 ご相談お待ちしております。

master(at-mark)silph.jp