mitsu's techlog

Customer Reliability Engineerになりました。備忘録も兼ねて技術ネタを適当に。技術以外はこっち→http://mitsu9.hatenablog.com/

Swiftで乱択クイックソートを実装してみた

はじめに

僕の好きな本の一つに「数学ガール」があります。 高校生のとき帰りに寄り道した本屋でたまたま出会って以来数学の楽しさを知り、 今情報系の道に進んでいる一つの理由だったりする思い出深い本です。

今回、勉強がてらアルゴリズムを実装したいなーと思ったので数学ガールの中からネタを探すことにしました。 そこで選ばれたのがこちら↓

数学ガール/乱択アルゴリズム (数学ガールシリーズ 4)

数学ガール/乱択アルゴリズム (数学ガールシリーズ 4)

この中から特にクイックソートに焦点をあて、最近勉強中のSwiftで実装してみました。

(ちなみにSwiftではary.sort { $0 < $1 }と書くことで簡単にソートできます。)

クイックソートとは

ソートとは、配列の要素をある規則にしたがって並べなおす操作です。

クイックソートはそのソートをおこなうアルゴリズムの一つなのですが、名前の通り比較的速い(少ないステップ数でソートできる)アルゴリズムです。 詳しくは述べませんが、計算量は平均で{ \displaystyle O(n \log n)}、最悪の場合は{ \displaystyle O(n^2)}になります(nはデータサイズ)。

クイックソートは分割統治法と呼ばれるアイデアを利用しています。 分割統治法とは、大きな問題を小さな問題に分けその小さな問題ごとに解くという手法のことです。 クイックソートでは、ある要素(ピボット)を基準とし、それより大きい数字のグループと小さな数字のグループに分け、それぞれのグループでソートをします(下図参照)。

クイックソートはピボットの選び方を変えることでソートの過程が変化します。 ピボットの選び方は単に配列の最初(最後)の要素を選ぶ方法や、数字からなる配列をソートする場合には平均値や中央値を用いることもあります。

今回は単に配列の最初の要素を選ぶ方法と、数学ガールの中で紹介されているピボットをランダムに選ぶ方法の2種類の方法で実装しました。 ランダムにピボットを選択する方法は乱択クイックソートと呼ばれ、最悪計算量を{ \displaystyle O(n \log n)}に抑えられる方法ということで本の中で紹介されていました。

(計算量の数学的な議論に興味のある方はぜひ数学ガールを読んでみてください!)

f:id:mtdtx9:20161219163820g:plain (引用: Wikipedia)

実装

クイックソートは大きく以下のステップに分かれます。

  1. pivotの選択。
  2. 配列を「pivotより小さい数字、pivot、pivotより大きな数字」の順に並び替える。
  3. pivotの左側(右側)で同様の操作を繰り返す。

クイックソートではSTEP3で再帰処理をするので、そのために関数を

func quicksort (ary: inout [Int], l: Int = 0, r: Int = ary.count - 1)

として実装を進めました。 aryはソートする配列、l, rはそれぞれソートする範囲を示す値(left, right)です。

僕が再帰処理をするときにいつも持っている悩みが、外部から呼び出す関数と内部で再帰的に呼び出す関数で引数を合わせることができず、それぞれ別の関数で実装しなければならないということです。 外部から呼び出すときはシンプルに保ちたいのですが、再帰的に呼び出す際には様々引数が必要になることが多く、一つの関数で実現できないという問題です。 Swiftではデフォルト引数を設定できるので、デフォルト引数を使うことで一つの関数として実現できました。

ここからはSwiftならではの引っかかった点を備忘録的に書いていきます。

関数内で配列を操作する(値渡しと参照渡し)

ソートでは配列の要素を入れ替える操作が必要になります。 しかし、Swiftの場合関数の引数は値渡しになります。 このままでは元の配列の要素を入れ替えることができないので、値渡しではなく参照渡しにする必要があります。

このためには関数定義の際にinoutを用います。 また関数を呼ぶ際にinoutで指定された引数は&argのように&マークを付けて渡します。

クイックソートの実装の際には具体的に以下のように書きました。

func quicksort (ary: inout [Int], l: Int = 0, r: Int = ary.count - 1) {
    // クイックソートの実装
}

// main function
var ary = [5, 6, 2, 3, 8, 1, 9, 7, 4]
quicksort(ary: &ary)

ちなみに、元の配列を維持したまま、ソートされた新しい配列が欲しい時もあると思います。 そのような場合、

func quicksort(ary: [Int]) -> [Int] {
    // ソートする
    return ary
}

のように書きたくなると思います。 しかし、Swiftでは引数はletで渡されるため配列を操作することができません。 このような場合には、

func quicksort(ary: [Int]) -> [Int] {
    var ary = ary
    // ソートする
    return ary
}

と書くことで実現可能になります。

(Swift 3から関数定義の際にvarをつけて変更可能にすることはできなくなりました。)

配列の要素の入れ替え (swap関数)

配列の要素を入れ替える際、Swiftで用意されているswap関数を利用すると簡単に実現できます。 前述の通りSwiftは値渡しなのでswap関数を呼ぶ際には&をつけて参照渡しにする必要があります。

swap関数での注意点は、同じ変数を渡すとエラーを吐き出すことです。 エラーはswapping a location with itself is not supportedと表示されていました。 今回の実装方法ではswapするときに用いる2変数が配列の同じindexを指す可能性があるので、swap関数を使うたびにif文でチェックしています。

(ここらへんをシンプルに書ける方法を模索中です。。)

乱数の生成

乱択クイックソートを実装する際に乱数を生成する必要があります。 乱数を生成する関数は何種類かあるのですが、arc4random_uniform()を用いると良いらしいです。 この関数は「0から引数で与えられた値の間から乱数を生成して返す」関数で、UInt32で値を与える必要があります。 また、戻り値もUInt32で返ってきます。 そのためにUInt32<->Intの型に注意して利用する必要がありました。

乱択クイックソートでピボットを選択する際には以下のように書きました。 arc4random_uniformに渡す際はUInt32にキャストし、戻ってきた値はIntにキャストして使っています。

let ran = Int(arc4random_uniform(UInt32(r - l))) + l

(ここの実装に関しては、配列のindexを示すl, r, ranなどをUInt32で実装すれば、いちいちキャストする必要が無くシンプルに書けたのかなと今思ってます。。)

コマンドラインからの実行

Playgroundを用いてもよかったのですが、興味があったこともありSwiftをコマンドラインから実行するようにして開発を進めました。 gccのようにswiftcを用いてコンパイル→実行というのも可能なのですが、正直面倒です。 そこで調べているとスクリプトのように実行できる方法を見つけたのでその方法を用いました。

そのために必要なことはファイルのはじめに#!/usr/bin/swiftと書くだけです。 これを書いておくとコマンドライン$ ./FILENAME.swiftと実行することができます。

コード

実際のコードを以下に貼っておきます。 一応動作確認しましたが、間違いなど見つけた際にはコメントしていただけると幸いです。

クイックソート

#!/usr/bin/swift

func quicksort (ary: inout [Int], l: Int = 0, r: Int = ary.count - 1)
{
    guard l < r else {
        return
    }
    
    // 配列の左端の値をピポットに選択
    let pivot = ary[l]
    var p = l
    for i in (l+1 ... r) {
        if(ary[i] < pivot) {
            p += 1
            // NOTE: swapping a location with itself is not supported
            if(p != i) {
                 swap(&ary[p], &ary[i])
            }
        }
    }
    // NOTE: swapping a location with itself is not supported
    if(p != l) {
        swap(&ary[l], &ary[p])
    }
    
    quicksort(ary: &ary, l: l, r: p-1)
    quicksort(ary: &ary, l: p+1, r: r)
}

var ary = [5, 6, 2, 3, 8, 1, 9, 7, 4]
quicksort(ary: &ary)
print(ary)

乱択クイックソート

#!/usr/bin/swift

// arc4random_uniform()を使うために必要
import Foundation

func random_quicksort (ary: inout [Int], l: Int = 0, r: Int = ary.count - 1)
{
    guard l < r else {
        return
    }
    
    // 乱数でピボットを選択->ピボットを左に持ってくる
    let ran = Int(arc4random_uniform(UInt32(r - l))) + l
    if(ran != l) {
        swap(&ary[l], &ary[ran])
    }
    
    // あとは通常のquicksortと同じ
    let pivot = ary[l]
    var p = l
    for i in (l+1 ... r) {
        if(ary[i] < pivot) {
            p += 1
            // NOTE: swapping a location with itself is not supported
            if(p != i) {
                swap(&ary[p], &ary[i])
            }
        }
    }
    // NOTE: swapping a location with itself is not supported
    if(p != l) {
        swap(&ary[l], &ary[p])
    }
    
    random_quicksort(ary: &ary, l: l, r: p-1)
    random_quicksort(ary: &ary, l: p+1, r: r)
}

var ary = [5, 6, 2, 3, 8, 1, 9, 7, 4]
random_quicksort(ary: &ary)
print(ary)

Hatena Engineer Seminar #7 @Tokyo に行ってきた!

こんにちは。id:mtdtx9です。

今回はHatena Engineer Seminar #7 @Tokyoに参加してきたので、
自分のためのまとめも兼ねて書いていきたいなーと思います。

参加までの経緯

夏のインターンに応募した際のご縁で人事の方からメールを頂きイベントの存在を知りました。
はてなさんが東京にオフィスを持っていることをここで初めて知り、イベント内容もエンジニアのキャリアに寄ったものだったので参加することにしました。
このようなイベントに参加するのは2年ぶりぐらいだったので当日はとてもわくわくして会場に向かいました。

いざ、はてな東京オフィスへ

東京オフィスは表参道駅から徒歩5分の場所にあるのですが、駅からオフィスまでがお洒落すぎてビビりました。
来るところ間違えたかなーなんて思いながらてくてく歩くとオフィスのあるビルを発見。
ビルはいたって普通なオフィスビルで安心しました笑

受付はこんな感じ。
全体的に木の温もりが感じられる非常に落ち着いたオフィスでした!

f:id:mtdtx9:20161207104608j:plain

会場は少し奥に入ったところにある芝生が敷かれた広場でした。
50~60人座るとそこそこいっぱいになるこぢんまりしたスペースでした。
(このころは記事書こうなんて思ってなかったので写真撮ってません。。唯一の後悔(´・ω・`))

1人目 | id:hakobe932さん「はてなで一人前のエンジニアになる方法」

新卒7年目のid:hakobe932さんのキャリアを振り返りつつ、はてなのエンジニア制度や教育制度などについて説明してくれました。

聞けば聞くほどはてなさんが技術を大事にしていることが伝わってきました。
エンジニアが多い訳ではないため、一人一人がある程度の幅の技術を知らないと仕事ができないといった背景もあるかもしれませんが、
みんな技術が好きで会社としてもその気持ちを全力でサポートしているという印象でした。
勉強会やSlackを用いた技術共有など割と普通な取り組みの中で一つ特徴的だったのは、勉強会の打ち上げ費用のサポートをしてくれる点です。
勉強会の打ち上げをサポートしてくれるとか最高ですよね。
(ちょっと重い本だと一冊全部勉強するのって結構辛い。。)

それでは印象に残ったお話をいくつか。

一人前のエンジニアなのか?→たぶん常に違う。でも新卒のころに自分に対してはそうだと言ってもいいかな。

エンジニアとしての向上心と、成長できている実感を得られていることがわかるお話でした。
僕も将来こう言えるようになりたいなーと思いました。

基礎を大事にする組織。寿命が長い基礎技術を大事にする。

基礎技術を大事にしようという文化が素晴らしいと感じました。
このような基礎技術の勉強は目の前のプロジェクトに直接関係ない時もありますが、長く安定したプロダクトを作るためには必須だと思います。
はてなブックマークはてなブログなどの長寿命のプロダクトをリリースされてきた会社だからこその価値観なのかもしれないなと思いました。
また、別のところで話されていたのですが、技術選定の際に"長く使える技術なのか"を意識して選ばれているのも印象的で、はてなさんのエンジニア文化を垣間見ることができました。

speakerdeck.com

2人目 | id:Songmuさん。「エンジニアがはてなでマネージャーをやるということ」

チーフエンジニア/Meckerelディレクターとして働かれているid:Songmuさんからのマネージャーに焦点を当てたお話でした。

マネージャーの仕事をイマイチわかってないので大事なポイントとかよくわからないのですが、研究室で後輩の指導をする先輩として投影して聞いてみました。

(id:Songmuさんの話された内容は後日ブログにまとめてくださるそうなので詳しく知りたい方はそちらを見てください。)

オーナーシップ中心マネジメント。メンバーにオーナーシップを持ってもらう。

とにかくオーナーシップという言葉が多く使われていて、確かにオーナーシップ大事だなーと思って聞いてました。
僕はこのオーナーシップを愛着心と責任感という風に受け取ったのですが、メンバーがそのプロダクトに愛着心と責任感を持って取り組んでいればその組織って多分強いですよね。
懇親会などで話を聞いていても、はてなのエンジニアはオーナーシップを持っている方ばかりで、とても楽しそうな姿が印象的でした。

メンバーのパフォーマンスが出ないのはマネージャーの問題

id:hakobe932さんのお話の中でも感じたのですが、はてなさんはエンジニアのモチベーションをどう保つか、どうすれば楽しく働けるのかということをすごく考えていました。
マネージャーはエンジニアが楽しく働ける環境を作ってあげることが大事で、パフォーマンスが出ない時はその環境が整ってないからだからマネージャーの問題、って考え方なのかなと思いました。

評価者は優劣をつける立場ではなく、上にアピールする味方

こんなマネージャーの下で働きたいなぁと素直に感じました。

3人目 | id:y_uukiさん。「ウェブオペレーションエンジニアのになるまでの思い出」

ここからはLTでした。
id:y_uukiさんの技術の思い出を振り返りながらのウェブオペレーションエンジニアに関するお話でした。

大学でlinuxC言語に触れ、コマンドラインUNIXかっけーって話など共感できる話ばかりで終始頷きながら聞いてました笑

speakerdeck.com

4人目 | id:a-knowさん。「ぼくがセールスエンジニアというキャリアを選んだ理由(わけ)」

イベント最後の発表はid:a-knowさんからのセールスエンジニアについてのお話でした。

これまでの知識を活かして更にエンジニアとして成長したい!という気持ちがとても伝わってくるお話でした。

www.slideshare.net

ここまで4人の方のお話を聞いてきましたが、みなさん向上心がとても強くて、さすがはてなという感じで刺激的でした。

パネルディスカッション

4人のお話のあとはパネルディスカッションがありました。
このパネルディスカッションを通して普段のはてなのエンジニアの生活を知れたり、そこからエンジニア文化が垣間見れたりしたので、僕としては一番収穫があった時間でした。
一方で、話に夢中であまりメモをとっていないのが後悔です。
(頭には入っているんですけど、話題が多くて忘れてるのがありそう。。)

それでは印象に残ったところをいくつか。

リリースタイミングを昼1にしたり、金曜日を避けたりなどしている

エンジニアの生活に関する話をしているときに、プルリクがたまるとbotがリリースしようと教えてくれるといった話をしていたときの話です。(話ばっかで読みづらいな。。)
変な時間にリリースすると障害が起きた時に障害対応班が帰れない・土日出社などになってしまうのでタイミングを考えていると言われていました。
最初は良いチームだなぁと思って聞いていたのですが、id:y_uukiさんいわくインフラ側からお願いしているのでは無く自然とそうなっているらしいので、更に良いチームだなぁと思いました。
他にも"無理はせずに定時で帰る"といったことも言われていて、エンジニアが楽しく働けるような工夫が様々なところでされていると感じました。

隣の部署にすごい人がいる、ではなくすぐ隣にすごい人がいる。

これはそこまで規模の大きくない会社ならではの特徴だなと思いました。
エンジニアとして成長するためには、すごい人が近くにいて切磋琢磨しあうというのは必須条件だと思っているのですが、大企業になると隣の部署という距離感になるのかーと思って聞いていました。
個人的には就活前にこの言葉を聞けたのはラッキーだったなと思います。

最後に!

懇親会含め約3時間のイベントだったのですが、様々なお話を聞けてよかったです。
そして何より楽しかったです!
その理由は登壇された方がみなさん楽しそうに話されていたからなのかなーと後になって感じました。
はてなさんの雰囲気を肌で感じることができたので良かったです。

エンジニアとしてはてなさんは働きやすいだろうなと感じたし、成長もできるだろうなと思いました。
アウトプットを重視する文化もエンジニアとしての価値を高めていけると思うので良いなーと。

もはや良かったとしか言ってないのですが、本当に行って良かったと思える3時間でした!
またこういうイベントあったら参加したいと思います!

最後に、今回のイベントの中で一番いいなと思ったところを。

登壇された皆様、楽しい時間をありがとうございました!
運営の方々、お疲れ様でした!

さくらVPSにUbuntu LTS 14.04を入れてRedmineを導入するまで Part.1 サーバの基本的な設定

サーバーをいじってみたいなーということで値段も手頃なさくらVPSを借りてみることにしました。 インターネットにさらされたサーバーを持つということで、知らぬ間に攻撃に加担しないように、とりあえず基本的な最初にすべき設定をまとめました。 何か不足している点があれば教えていただけると幸いです!

Ubuntuのインストール

さくらVPSのデフォルトOSはCentOSなのでUbuntuをインストールします。 これは公式のインストールガイドが丁寧なのでこれに沿っていけば問題なくできます。 今回ユーザーはdeveloperで作成したので、以下のコードは適当に読み替えてください。

https://help.sakura.ad.jp/app/answers/detail/a_id/2403/~/%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0os%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%82%AC%E3%82%A4%E3%83%89---ubuntu-12.04%2F14.04

sshの設定

セキュリティを高めるために鍵認証方式に変更します。

ディレクトリの作成。

mkdir ~/.ssh
chmod 700 ~/.ssh

ローカルPCに戻って鍵をサーバーに送信。

scp ~/.ssh/id_rsa.pub developer@IP_ADDRESS:~/.ssh/authorized_keys

別タブで鍵認証でログインできるかを確認。

ssh -i ~/.ssh/id_rsa  developer@IP_ADDRESS

オリジナルのコピーを保存

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.org

設定の変更

vi /etc/ssh/sshd_config

=>
Port 22 -> 好きな番号に
PermitRootLogin no # rootのログインを禁止
X11Forwarding no # ??
PasswordAuthentication no # パスワード認証を禁止

設定のリロード

service ssh reload

Firewallの設定

httpとssh以外は禁止するように変えます。

sudo ufw status
sudo ufw default deny
sudo ufw enable
sudo ufw allow http
sudo ufw allow https
sudo ufw allow NEW_PORT_NUMBER
sudo ufw status

(出力例)=>
Status: active

To                         Action      From
--                         ------      ----
80                         ALLOW       Anywhere
443                        ALLOW       Anywhere
NEW_PORT_NUMBER                      ALLOW       Anywhere
80 (v6)                    ALLOW       Anywhere (v6)
443 (v6)                   ALLOW       Anywhere (v6)
NEW_PORT_NUMBER (v6)                 ALLOW       Anywhere (v6)

最後に

Redmine導入までは終わっているので、Part2は書き次第更新します。。

参考サイト

さくらVPSにUbuntu LTS 14.04をインストールした話(1) - いぬれおがんばれお

さくらVPS設定の手順の備忘録 - rails永遠の初心者の備忘録

さくらのVPS入門 (全21回) - プログラミングならドットインストール

そこそこセキュアなlinuxサーバーを作る - Qiita

SwiftでFacebookのユーザー認証からデータ取得まで(Facebook SDK)

SNSとの連携ということで代表的なFacebookの認証からデータ取得まで実装しました。 FacebookはSocialFrameworkとFacebook SDKを使う2種類の方法があるのですが、今回はFacebook SDKを使って実装してみました。

SDKの導入

以下のリンクの内容に沿ってSDKを導入します。 行ったことは以下の5項目です。

  1. SDKのダウンロード
  2. Facebook Appの作成 (App ID等の取得)
  3. ProjectにFacebookSDKを追加
  4. plistファイルにApp IDなどの情報を追加
  5. Application Delegateに必要なコードを追加

Facebook SDK for iOS - Getting Started

ユーザー認証

  1. ログインボタンの設置

SDKの中にログインボタンがあるので、そちらを利用してボタンを設置します。 ボタンをViewに追加するだけで認証に関する処理は全てSDK側でしてくれるので非常に楽です。

カスタムで作りたい方はボタンタップ後の処理を数行書く必要があります。

// 認証に必要なSDKをimportする
import FBSDKCoreKit
import FBSDKLoginKit

class ViewController: UIViewController, FBSDKLoginButtonDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let loginButton = FBSDKLoginButton() // ボタンの作成
        loginButton.center = self.view.center // 位置をcenterに設定
        loginButton.delegate = self // 認証後の処理のためにdelegateを設定
        loginButton.readPermissions = ["public_profile", "email", "user_friends"] // 欲しいデータに合わせてpermissionを設定
        self.view.addSubview(loginButton) // viewにボタンを追加
    }
}
  1. 認証後の処理

delegateを設定しておくことで認証後にメソッドが呼び出されます。 FBSDKLoginButtonDelegateプロトコルで定義されているメソッドは、

  • func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!)
  • func loginButtonDidLogOut(loginButton: FBSDKLoginButton!)

になります。 ログイン時とログアウト時に呼ばれる2種類のメソッドです。

これらはサンプルを利用して以下のように実装しました。

    func loginButton(loginButton: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!){
        println("User Logged In")
        
        if ((error) != nil)
        {
            // Process error
        }
        else if result.isCancelled {
            // Handle cancellations
        }
        else
        {
            // If you ask for multiple permissions at once, you
            // should check if specific permissions missing
            if result.grantedPermissions.contains("email")
            {
                // Do work
            }

            // ログイン後の処理はここに書く
        }
    }
    
    func loginButtonDidLogOut(loginButton: FBSDKLoginButton!) {
        println("User Logged Out")
    }

Facebook SDKを用いたとき認証に必要なコードはこれだけになります。 ほとんどサンプルのコピーなので、認証に関しては問題無く実装できると思います。

ユーザー情報の取得

ユーザーの情報を取得するにはFBSDKGraphRequestのインスタンスを作成し、データ取得後の処理を引数にstartWithCompletionHandlerを呼び出します。

FBSDKGraphRequest初期化時にparametersを指定することで必要な情報のみを取得することができます。 以下のコードでは(おそらく)全ての情報を取得しています。

取得できる情報は認証時にreadPermissionsに設定した項目によって変化します。 今回はreadPermissionsを["public_profile", "email", "user_friends"]に設定しているので、(おそらく)全ての情報がとれる権限になっています。

func returnUserData() {
    
    let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me",
        parameters: ["fields": "id,email,gender,link,locale,name,timezone,updated_time,verified,last_name,first_name,middle_name"])
    graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
        
        if ((error) != nil)
        {
            // Process error
            println("Error: \(error)")
        }
        else
        {
            println("fetched user: \(result)")

            // 個々の情報を取得したいときはこんな感じ
            let userName : NSString = result.valueForKey("name") as! NSString
            println("User Name is: \(userName)")
        }
    })
}

出力は以下のようになります。

fetched user: {
    email = "mail@mail";
    "first_name" = firstname;
    gender = male;
    id = randomnumber;
    "last_name" = lastname;
    link = "https://www.facebook.com/app_scoped_user_id/user_id/";
    locale = "ja";
    name = "firstname lastname";
    timezone = "-5";
    "updated_time" = "2015-07-18T03:34:22+0000";
    verified = 1;
}
User Name is: firstname lastname

友達のリストの取得

現在Facebookの友達のリストをとるAPIはないのですが、taggable_friendsというAPIを利用して友達の情報を取得することができます。

taggable_friendsを利用するとデフォルトでは25人分の情報と次の友達の情報を取得するための情報が返ってきます。(以下に例があります)

実際にアプリを開発する際はスクロールなどに合わせてAPIから情報を取得していくことになると思うのですが、今回は面倒なので全てのfriendsを一回で取得しようと思います。 そのためにパラメーターにlimitを追加して、友達の数をlimitに設定します。

この友達の数はfriendsというAPIから取得できるので、friendsで友達の数を取得し、その後全ての友達を取得するように実装しました。

ちなみに、実際に実行してみたところfriendsで取得した友達の数とtaggable_friendsで取得した友達の数に若干の差がありました(僕の場合877と859)。 taggable_friendsという名前からわかるようにtaggableでない友達は取得できないので、全ての友達は取得できません。 しかし、taggableでない友達は少ないと思うのであまり困ることはないかと思います。 (それにしてもtaggableでない友達ってどういう人?わかる方いたら教えてください。)

taggable_friendsのレスポンス↓

{
  "data": [
    {
      "id": "XXXXXXX",
      "name": "hoge hoge",
      "picture": {
        "data": {
          "is_silhouette": false,
          "url": "url"
        }
      }
    },
    {
      "id": "XXXXX",
      "name": "huga huga",
      "picture": {
        "data": {
          "is_silhouette": false,
          "url": "url"
        }
      }
    }
],
  "paging": {
    "cursors": {
      "before": "XXXXXXXXXX",
      "after": "XXXXXXXXXX"
    },
    "next": "https://graph.facebook.com/v2.4/854530921331223/taggable_friends?access_token=ACCESS_TOKEN&pretty=0&limit=25&after=XXXXXXXXXXXX"
  }
}

友達のリストを取得するメソッド

func friendsList() {
    
    let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me/friends", parameters: nil)
    graphRequest.startWithCompletionHandler( { (connection, result, error) -> Void in
        
        if ((error) != nil)
        {
            // Process error
            println("Error: \(error)")
            return
        }
        
        //  友達の数を取得し次のリクエストのlimitに利用
        let summary = result.valueForKey("summary") as! NSDictionary
        let counts = summary.valueForKey("total_count") as! NSNumber
        
        let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me/taggable_friends", parameters: ["fields": "id,name,picture", "limit": "\(counts)"])
        graphRequest.startWithCompletionHandler( { (connection, result, error) -> Void in
            
            if ((error) != nil)
            {
                // Process error
                println("Error: \(error)")
                return
            }
            else
            {
                // 友達を一人ずつ出力
                let friends = result.valueForKey("data") as! NSArray
                var count = 1
                if let array = friends as? [NSDictionary] {
                    for friend : NSDictionary in array {
                        let name = friend.valueForKey("name") as! NSString
                        println("\(count) \(name)")
                        count++
                    }
                }
            }  
        })  
    })
}

補足

Facebookの認証の権限はユーザー側で管理することができます。 具体的にはreadPermissionsを["public_profile", "email", "user_friends"]と設定していても、ユーザーによって実際は["public_profile"]だけの権限になっている場合があります。 このことによって、NSDictionaryで返ってくるデータに対してKeyが存在しないことがあり得るので、実際にアプリを開発する際はそのことを意識してOptional型を適切に利用する必要があります。 権限を持っているつもりでOptional Valueに対してforce wrappingしてしまうとアプリが落ちることになるので気をつけましょう。

(※上記のコードは一切このことについて気にしていません。)

最後に

認証は簡単にできたのですが、ドキュメントがよくわからずデータ取得に手こずりました。 今回はデータを取得することしかしていないので、次はポストやシェアができるようにしたいと思います。

全体のコードはGistにあるので参考にしたい方はどうぞ。

The demonstration of how to use Facebook SDK

参考サイト

Facebook SDK for iOS - Getting Started

Facebook API v2.0で、フレンド数やフレンド一覧を取得する方法 | Sunday In The Park

https://developers.facebook.com/tools/explorer/

Swiftで作るかっこいいwalkthrough(Airbnb風)

Airbnbのwalkthroughがかっこいいなーとおもったので同じようなものをつくってみました。

Airbnbの良いところは、

  • 横にスクロールすると今の画像がフェードアウトし、次の画像がフェードインする
  • 画像が常にアニメーションしている
  • 横スクロールに合わせて下のボタンの色がなめらかに変わる

です。

今回はこれらの項目を実装しました。

f:id:rayc5:20150622004404g:plain

1つめ | 横にスクロールすると今の画像がフェードアウトし、次の画像がフェードインする

今回はImageViewを乗せるためのimageViewContainerを用意し、そちらにImageViewを乗せるという方法で実装しました。

walkthroughの長さによってImageViewの数が変わってくるので、常に全てを表示するのではなく、今表示しているものと左右にスクロールしたときに出てくるものの計3枚のImageViewが一度にこのimageViewContainerにのっています。 左右にスクロールした時に出てくるビューはスクロールの開始時に追加され、終了時に取り除かれるので、スクロールしていないときはそのとき表示されているImageViewが表示されるようにしました。

ViewDidLoad

まずViewDidLoad内で下準備をします。

今回scrollViewはstoryBoardで実装し、imageViewContainerはコードで実装するという変なことをしたので、余計な部分がありますが気にしないでください。

基本的には、ImageViewを用意し配列に保存し、一枚目のImageViewだけimageViewContainerに追加し表示する処理をおこなっています。

class ViewController: UIViewController, UIScrollViewDelegate {

var imageViewContainer: UIView!
var imageViewArray = [UIImageView]()

override func viewDidLoad() {
    super.viewDidLoad()

    /** 関係ない部分は省略 */
        
    // imageViewContainer. scrollViewの後ろ側に追加
    imageViewContainer = UIView(frame: CGRectMake(0, 0, view.frame.width, view.frame.width))
    view.insertSubview(imageViewContainer, belowSubview: scrollView)
        
    // ImageViewの設定
    let frame = CGRectMake(0.0, 0.0, view.frame.size.width, view.frame.size.height)
     
    var imageView1 = UIImageView(image: UIImage(named: "image1.jpg"))
    imageView1.frame = frame
    imageViewArray.append(imageView1)
        
    var imageView2 = UIImageView(image: UIImage(named: "image2.jpg"))
    imageView2.frame = frame
    imageViewArray.append(imageView2)
        
    var imageView3 = UIImageView(image: UIImage(named: "image3.jpg"))
    imageView3.frame = frame
    imageViewArray.append(imageView3)
        
    // 最初は1枚目だけを追加. 後のビューはスクロールに合わせて追加・削除する.
    imageViewContainer.addSubview(imageViewArray[0])
}

UIScrollViewDelegate

アニメーションはscrollViewのdelegateメソッドを利用します。

まず初めにscrollViewWillBeginDraggingにて、左右のImageViewをimageViewContainerに追加します。

次にscrollViewDidScrollにて、ImageViewのalpha値を変更しフェードイン・アウトを実装します。

alpha値は現在のcontentOffsetと今のページのx座標の差分からスクロール量を計算し、それを元に計算します。progressがスクロールの進み具合を示す変数で、0~1の値をとるようになっているので、この変数を使ってalpha値を決めていきます。

func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    addBothSidesImageView()
}

func scrollViewDidScroll(scrollView: UIScrollView) {
    let pageWidth = scrollView.frame.size.width
    if Int(fmod(scrollView.contentOffset.x , pageWidth)) == 0 {
        pageControl.currentPage = Int(scrollView.contentOffset.x / pageWidth)
        imageViewArray[pageControl.currentPage].alpha = 1.0
        removeBothSidesImageView()
    } else {
        // ここで画像と色の変化を実装
        let currentView = imageViewArray[pageControl.currentPage]
        if let index = nextIndex {
            let def = pageWidth * CGFloat(pageControl.currentPage) - scrollView.contentOffset.x
            let progress = CGFloat(abs(def)) / pageWidth
            
            let nextView = imageViewArray[index]
            currentView.alpha = 1 - progress
            nextView.alpha = progress
            
            /** 関係ない部分は省略 */
        }
    }
}

private var nextIndex: Int? {
    get {
        var nextIndex: Int
        let currentPageX = CGFloat(pageControl.currentPage) * view.frame.width
        if scrollView.contentOffset.x < currentPageX { // 右スワイプ
            nextIndex = pageControl.currentPage - 1
        } else {
            nextIndex = pageControl.currentPage + 1
        }
            
        if nextIndex < 0 || pageCount <= nextIndex {
            return nil
        }
        return nextIndex
    }
}

2つめ | 画像が常にアニメーションしている

今回はimageViewContainerを作っているので、こちらを常にアニメーションすれば、ImageViewが常に変化するようになります。

アニメーションを永遠に繰り返すためにrepeatCountをHUGEにすることと、拡大縮小を繰り返すためにautoreversesをtrueにすることでこのようなアニメーションを実現できます。

override func viewDidLoad() {
    var animation = CABasicAnimation(keyPath: "transform.scale")
    animation.duration = 10.0
    animation.repeatCount = HUGE
    animation.autoreverses = true
    animation.fromValue = 1.0
    animation.toValue = 1.1
    imageViewContainer.layer.addAnimation(animation, forKey: "scale-layer")
}

3つめ | 横スクロールに合わせて下のボタンの色がなめらかに変わる

基本的には1つめの画像のフェードインと同じです。色の情報をarrayに格納しておき、その情報を元になめらかに色を変化させていきます。

画像はalpha値を変更したことに対し、今回は色のRGB値を変更することでなめらかに色を変化させていきます。

UIColorからRGB値を取得するためにはgetRed(red:UnsafeMutablePointer, green:UnsafeMutablePointer, blue:UnsafeMutablePointer, alpha:UnsafeMutablePointer)メソッドを使います。

これで今の色と次の色のRGB値を算出し、その差分をスクロールの量に合わせて追加することでなめらかに色を変化させることができます。

func scrollViewDidScroll(scrollView: UIScrollView) {
    let pageWidth = scrollView.frame.size.width
    if Int(fmod(scrollView.contentOffset.x , pageWidth)) == 0 {
        /** 関係ないところは省略 */
    } else {
        // ここで画像と色の変化を実装
        let currentView = imageViewArray[pageControl.currentPage]
        if let index = nextIndex {
            let def = pageWidth * CGFloat(pageControl.currentPage) - scrollView.contentOffset.x
            let progress = CGFloat(abs(def)) / pageWidth
            
            /** ImageViewの変更 - 略 - */
            
            let nextColor = colorArray[index]
            var currentColor = colorArray[pageControl.currentPage]
            var currentRGBColor = getRGB(currentColor)
            var defColors = defTwoColors(currentColor, second: nextColor)
            footerView.backgroundColor = UIColor(
                red: currentRGBColor.red + defColors.red * progress,
                green: currentRGBColor.green + defColors.green * progress,
                blue: currentRGBColor.blue + defColors.blue * progress,
                alpha: currentRGBColor.alpha + defColors.alpha * progress)
        }
    }
}

private func getRGB(color: UIColor) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
    var rgbValue = (red: CGFloat(0.0), green: CGFloat(0.0), blue: CGFloat(0.0), alpha: CGFloat(0.0))
    color.getRed(&rgbValue.red, green: &rgbValue.green, blue: &rgbValue.blue, alpha: &rgbValue.alpha)
    return (rgbValue.red, rgbValue.green, rgbValue.blue, rgbValue.alpha)
}

private func defTwoColors(first: UIColor, second: UIColor) -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
    var currentColor = (red: CGFloat(0.0), green: CGFloat(0.0), blue: CGFloat(0.0), alpha: CGFloat(0.0))
    var nextColor = (red: CGFloat(0.0), green: CGFloat(0.0), blue: CGFloat(0.0), alpha: CGFloat(0.0))
    
    first.getRed(&currentColor.red, green: &currentColor.green, blue: &currentColor.blue, alpha: &currentColor.alpha)
    second.getRed(&nextColor.red, green: &nextColor.green, blue: &nextColor.blue, alpha: &nextColor.alpha)
    
    return (nextColor.red - currentColor.red, nextColor.green - currentColor.green, nextColor.blue - currentColor.blue, nextColor.alpha - currentColor.alpha)
}

最後に

全体像は以下のようになってます。

AirbnbWalkThrough

UIViewAnimationOptionsの定義から同時に指定できるオプションを理解する。

はじめに

UIViewAnimationOptionsについて

現在アニメーションについて勉強中なのですが、iOSにはUIViewAnimationOptionsという便利なものがあります。

これはOptionを指定するだけでかっこいいアニメーションを自動でしてくれるものです。

例えばTransitionCurlDownを指定するとremoveFromSuperview()に対して以下のようなアニメーションを付けてくれます。

f:id:rayc5:20150620062110g:plain

これは以下のように実装することで使えます。

import UIKit

class ViewController: UIViewController {
    
    var container: UIView!
    var redView: UIView!
    var blueView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // set container frame and add to the screen
        let frame = CGRect(x: 50, y: 50, width: 300, height: 300)
        container = UIView(frame: frame)
        container.backgroundColor = UIColor.grayColor()
        self.view.addSubview(container)
        
        // set redView and blueView; because blueView is added in animation, don't addSubview
        redView = UIView(frame: CGRectMake(0, 0, container.frame.width, container.frame.height))
        redView.backgroundColor = UIColor.redColor()
        container.addSubview(redView)
        
        blueView = UIView(frame: redView.frame)
        blueView.backgroundColor = UIColor.blueColor()
        
        // set button as a trigger of animation
        let button = UIButton(frame: CGRectMake(50, 400, 300, 30))
        button.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
        button.setTitle("start animation", forState: .Normal)
        self.view.addSubview(button)
        
        button.addTarget(self, action: "tapButton:", forControlEvents: .TouchUpInside)
    }
    
    func tapButton(sender: AnyObject) {
        var opt = UIViewAnimationOptions.TransitionCurlDown 
        
        var views = (frontView: redView, backView: blueView)
        
        UIView.transitionWithView(container, duration: 1.0, options: opt, animations: {
            // remove the front object...
            views.frontView.removeFromSuperview()
            
            // ... and add the other object
            self.container.addSubview(views.backView)
            
            }, completion: { finished in
                // any code...
        })
    }
}

UIViewAnimationOptionsの複数指定

このUIViewAnimationOptionsなのですが、以下のように複数指定することもできます。

var opt = UIViewAnimationOptions.TransitionCurlDown | .Repeat

ちなみにこの場合Repeatを設定したので、同様のアニメーションが永遠にリピートされます。

f:id:rayc5:20150620062848g:plain

本題

UIViewAnimationOptionsには横にflipする.TransitionFlipFromLeftと縦にflipする.TransitionFlipFromBottomのオプションがあります。

そこで僕は、この2つのオプションを指定すれば、縦と横が混ざって斜めにflipするようなかっこいいアニメーションが作れるのではと思ったんですねー。

そして少しの期待に胸をふくまらせながら動かしてみると、TransitionFlipFromBottomと全く同じ動きをしました。わかってはいましたがそう上手くいくわけはありません。この2つのオプションを同時に行うのは無理なのでどちらか一方ということでTransitionFlipFromBottomが選ばれたのでしょう。

そして次にしてみたことはTransitionCurlUpとTransitionCurlDownです。全く正反対のオプションを指定するとどうなるのか気になったんですね。

その結果は不思議なことにTransitionFlipFromBottomと同じ動きになったんです。TransitionCurlUpとTransitionCurlDownの2つのオプションから誰がTransitionFlipFromBottomを予想できるでしょうか。

この不思議な結果を受けて、詳細にUIViewAnimationOptionsについて調べて見ることにしました。

内容

UIView Class Referenceで詳細を調べました。

そこでは、UIViewAnimationOptionsはObjective-Cで以下のように実装されていることが書いてあります。

enum {
   UIViewAnimationOptionLayoutSubviews            = 1 <<  0,
   UIViewAnimationOptionAllowUserInteraction      = 1 <<  1,
   UIViewAnimationOptionBeginFromCurrentState     = 1 <<  2,
   UIViewAnimationOptionRepeat                    = 1 <<  3,
   UIViewAnimationOptionAutoreverse               = 1 <<  4,
   UIViewAnimationOptionOverrideInheritedDuration = 1 <<  5,
   UIViewAnimationOptionOverrideInheritedCurve    = 1 <<  6,
   UIViewAnimationOptionAllowAnimatedContent      = 1 <<  7,
   UIViewAnimationOptionShowHideTransitionViews   = 1 <<  8,
   UIViewAnimationOptionOverrideInheritedOptions  = 1 <<  9,
   
   UIViewAnimationOptionCurveEaseInOut            = 0 << 16,
   UIViewAnimationOptionCurveEaseIn               = 1 << 16,
   UIViewAnimationOptionCurveEaseOut              = 2 << 16,
   UIViewAnimationOptionCurveLinear               = 3 << 16,
   
   UIViewAnimationOptionTransitionNone            = 0 << 20,
   UIViewAnimationOptionTransitionFlipFromLeft    = 1 << 20,
   UIViewAnimationOptionTransitionFlipFromRight   = 2 << 20,
   UIViewAnimationOptionTransitionCurlUp          = 3 << 20,
   UIViewAnimationOptionTransitionCurlDown        = 4 << 20,
   UIViewAnimationOptionTransitionCrossDissolve   = 5 << 20,
   UIViewAnimationOptionTransitionFlipFromTop     = 6 << 20,
   UIViewAnimationOptionTransitionFlipFromBottom  = 7 << 20,
};
typedef NSUInteger UIViewAnimationOptions;

複数指定の際に " | " を使っていたあたりから予想できる通り、ビット演算を利用して様々なオプションが管理されていますね。

UIViewAnimationOptionTransition~に関するものだけに焦点を当てて考えます。

これらのオプションは0~7までの数字を20ビット分左シフトしています。シフトのことは一旦おいておいて、0~7までの数字を2進数表示すると、以下のようになります。

オプション名 10進数 2進数
TransitionNone 0 000
TransitionFlipFromLeft 1 001
TransitionFlipFromRight 2 010
TransitionCurlUp 3 011
TransitionCurlDown 4 100
TransitionCrossDissolve 5 101
TransitionFlipFromTop 6 110
TransitionFlipFromBottom 7 111

これを見ると先ほどのアニメーションが納得いきますね。確かにTransitionCurlUp | TransitionCurlDown は 011 | 100 = 111 なので TransitionFlipFromBottomになっています。

また、これを見ると、TransitionFlipFromLeft | TransitionFlipFromRight = TransitionCurlUpになることなんかもわかります。この場合はFlipFromLeft,Rightという横の動きを足すことでCurlUpという縦の動きに変わるので、文字通りに考えると意味不明の動きをすることになりますね。

この実装からわかるように基本的にTransition系のオプションを2つ以上指定することは推奨されていません。オプションの複数指定は、RepeatとTransition系のオプションを指定するという様に、違う種類のオプションを指定するときに使いましょう。

複数指定して良いオプションについて

UIViewAnimationOptionsの詳細を理解したため、どのオプションが同時に指定できるのかが定義からわかります。

まず最初に書かれている10個のオプション(UIViewAnimationOptionLayoutSubviews ~ UIViewAnimationOptionOverrideInheritedOptions)は、2進数の1桁目から10桁目までのそれぞれの桁がそれぞれのオプションのフラグになっているので、それぞれ独立していることがわかります。したがって、複数のオプションを指定しても大丈夫です。

次に書かれているUIViewAnimationOptionCurveEaseInOut~UIViewAnimationOptionCurveLinearのオプションはTransition系のときと同様に複数指定すると想定外の動作をしてしまいます。

例えば、EaseInとEaseOutを指定するとLinearになってしまいますね。

(これに関してはLinearとEaseInOutを交換すると、EaseIn | EaseOut = EaseInOutになり、もし複数指定しても文字通りに動作するのでそうしたら良いのになと思いましたが、デフォルトがEaseInOutなので仕方ないみたいです。)

最後に

フラグが多いときはそれぞれのフラグを変数にするとフラグまみれになります。これは初心者のコードに多いパターンなのではないでしょうか。

このようなときは、UIViewAnimationOptionsのようにenumにしてビットで管理すると、一つの変数で管理できるのでコードをシンプルになりいいですね。

しかし、ビット演算は使いこなすと強力な武器になりますが、わからずに使うと今回取り上げたようなことが起こり、混乱のもとになります。

自作enumで同じようなことをするときには、実装する側も使う側も、オプションが同時に適用できるのか、片方しか適用できないのかを理解し実装に反映させることで思わぬバグを防ぐことができそうです。

PlaygroundでUIViewのアニメーションを簡単に確認

みなさんPlayground使ってますか? APIの確認などサクッと調べたいときに少ないコード量で調べられるので、活用すれば開発スピードがぐっと上がると思います。 そんな僕も今はまだ全然使いこなせていないので、これから便利な使い方を発見したときはまとめていきたいと思います。

今回はUIViewのアニメーションの確認です!

意外につまづきなかなかアニメーションしてくれなかったので、同じような状態の人は参考にしてもらえると良いと思います。

下準備

その1

View>Utilities>Show File Inspectorをクリック

右側にUtilityの画面が出てくるので、Playground SettingのRun in Full Simulatorにチェックを入れる。

チェックを入れた後は邪魔なので消しておく。View>Utilities>Hide Utilities

f:id:rayc5:20150617020919p:plain

その2

Assistant Editorを表示します。もし表示されていなかったら以下から表示できます。

View>Assistant Editor>Show Assistant Editor

実際にコードを書いてみる

今回僕はautoresizingMaskの動作がよくわからず調べてみたかったので、それを調べるようなコードを書いています。 ちなみにautoresizingMaskは親ビューのサイズが変更されたときに自分がどのように振る舞うかを決めるプロパティです。

ポイントはXCPlaygroundを使うところです。詳しくは以下で書きます。

import UIKit
import XCPlayground

var containerView = UIView(frame: CGRectMake(0, 0, 300, 300))
containerView.backgroundColor = UIColor.grayColor()
XCPShowView("Container View", containerView)

var subView = UIView(frame: CGRectMake(50, 50, 200, 200))
subView.backgroundColor = UIColor.blackColor()
containerView.addSubview(subView)

subView.autoresizingMask =
    .FlexibleHeight |
    .FlexibleBottomMargin |
    .FlexibleTopMargin |
    .FlexibleWidth |
    .FlexibleLeftMargin |
    .FlexibleRightMargin

UIView.animateWithDuration(2.0, animations: {
    containerView.frame = CGRectMake(0, 0, 400, 400)
})

XCPlaygroundについて

XCPlaygroundとは最初から用意されているユーティリティフレームワークです。 こちらをうまく利用することでPlaygroundで様々なことができるらしいです。

詳しくは公式ドキュメントを参照してください。(XCPlayground Module Reference)

使い方

XCPShowView(identifier: String, view: UIView)を利用することでViewを表示することができます。

こちらを利用することで、コードの変更時に自動でアニメーションをしてくれたり、タイムバーを利用してアニメーションの経過を詳細に見ることができます。

もしアニメーションしてくれないときは、Editor>Execute Playgroundからアニメーションさせることができると思います。

全体像はこんな感じ。コードと結果を一度に見ることができます。

f:id:rayc5:20150617074018p:plain

アニメーションはこんな感じでした。

f:id:rayc5:20150617063026g:plain

.FlexibleTopMarginと.FlexibleRightMarginだけにしたとき↓

f:id:rayc5:20150617074505g:plain

感想

変更をすぐに見ることができるので、アニメーションをテストしたり、細かく色を調節するときなんかに便利だと感じました。Viewの形を整えるときなんかにも良さそうですね。半径がどうだとか影がどうだとかいう処理をリアルタイムで確認できるのはメリットだと思います。

Playgroundで動作したら、そのコードをアプリに移植するように進めていくと開発スピードもあがりそうです。