2017年1月の振り返り
はやいもので1月ももう最後の日になりました。
今年は毎月振り返りをすることでその月に何を学んだのかを気にするようになりたいなーと思ってます。
というわけで1月の振り返りをしていきます〜。
研究
研究企画書の締め切りが20日にあったため、論文の執筆に主に時間を使っていました。
院に入って新しいテーマに変え、システム系の論文をしっかり書くのは初めてだったので結構時間を割いて書いていました。
技術
- Scalaの勉強
- Skinny framewotkの勉強
ScalaでWebアプリを作るのでその前段階の勉強をしていました。
一応Scalaは昔ちょっと触れたことがあったけど、今回は真面目に一から勉強しました。
dwangoさんの新卒向け資料がすごいわかりやすくて実践的だったのでオススメ。
一応基本的なところは勉強できたので2月以降はコツコツ作っていきます。
今年は絶対にサービス作る٩( 'ω' )و
読書
インフラエンジニア系の本は、インフラエンジニアの選考にエントリーしてるけど、インフラエンジニアって何してるのかとかどういう能力が必要とかがまだ曖昧なのでお勉強という感じです。
数学ガールは最近数学にあまり触れていなかったので楽しく数学したいなということで気晴らしに読んでます。
イベント参加
就活の一環ではあるけど、個人的に興味深い2社で俺得なイベントだったので普通に楽しんじゃいました。
就活
就活についてはこういう場で公にするのはどうかと思うので簡単に。
一応あとで振り返った時にこの頃こういう感じだったなと思い出せればいいかなとは思うので。
業界としてはWeb系の企業を見ています。
インフラに興味あってそっちでエントリーはしてるけど、将来的にはサービス側の開発もしたいし、いろいろできる企業であれば嬉しいなという感じです。
ただ広く浅い器用貧乏になるのも嫌なので自分の武器はしっかり作りたい。
いくつかの企業を見ていて技術に関する考え方に違いがあるなと感じているので、自分の考えに合うところ、自分が得たい経験を積ませてくれるような企業に採用してもらえるといいなと思ってます。
Web系は企業によって色が違うのと、その色は正直採用ページからは見えてこないので、実際に人に会っていかないとなと感じました。
大規模な合説みたいに1回のイベントで複数社と話せる機会がほぼ無いので、地道に足動かしていかないといけないのかなと思いました。
まとめ
主に時間を割いていたのは研究と就活だったけど、その中で技術の勉強ができたのは良かったです。
今年こそWebサービスをリリースするという具体的な目標があるからこそ勉強に身が入っている気がします。
また、こうやって振り返ってみると定量的に勉強量を把握できるので良いなーと。
勉強した気になっているだけでできていないときとかあると思うので今後も続けていきたいと思います。
2月は就活の時間がさらに増えると思うけど、しっかり技術のインプット・アウトプットをしていきたいと思います。
アウトプットについては1月はできなかったので特に。
Swiftで乱択クイックソートを実装してみた
はじめに
僕の好きな本の一つに「数学ガール」があります。 高校生のとき帰りに寄り道した本屋でたまたま出会って以来数学の楽しさを知り、 今情報系の道に進んでいる一つの理由だったりする思い出深い本です。
今回、勉強がてらアルゴリズムを実装したいなーと思ったので数学ガールの中からネタを探すことにしました。 そこで選ばれたのがこちら↓
- 作者: 結城浩
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2011/03/02
- メディア: 単行本
- 購入: 19人 クリック: 779回
- この商品を含むブログ (102件) を見る
この中から特にクイックソートに焦点をあて、最近勉強中のSwiftで実装してみました。
(ちなみにSwiftではary.sort { $0 < $1 }
と書くことで簡単にソートできます。)
クイックソートとは
ソートとは、配列の要素をある規則にしたがって並べなおす操作です。
クイックソートはそのソートをおこなうアルゴリズムの一つなのですが、名前の通り比較的速い(少ないステップ数でソートできる)アルゴリズムです。 詳しくは述べませんが、計算量は平均で、最悪の場合はになります(nはデータサイズ)。
クイックソートは分割統治法と呼ばれるアイデアを利用しています。 分割統治法とは、大きな問題を小さな問題に分けその小さな問題ごとに解くという手法のことです。 クイックソートでは、ある要素(ピボット)を基準とし、それより大きい数字のグループと小さな数字のグループに分け、それぞれのグループでソートをします(下図参照)。
クイックソートはピボットの選び方を変えることでソートの過程が変化します。 ピボットの選び方は単に配列の最初(最後)の要素を選ぶ方法や、数字からなる配列をソートする場合には平均値や中央値を用いることもあります。
今回は単に配列の最初の要素を選ぶ方法と、数学ガールの中で紹介されているピボットをランダムに選ぶ方法の2種類の方法で実装しました。 ランダムにピボットを選択する方法は乱択クイックソートと呼ばれ、最悪計算量をに抑えられる方法ということで本の中で紹介されていました。
(計算量の数学的な議論に興味のある方はぜひ数学ガールを読んでみてください!)
(引用: Wikipedia)
実装
クイックソートは大きく以下のステップに分かれます。
- pivotの選択。
- 配列を「pivotより小さい数字、pivot、pivotより大きな数字」の順に並び替える。
- 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分の場所にあるのですが、駅からオフィスまでがお洒落すぎてビビりました。
来るところ間違えたかなーなんて思いながらてくてく歩くとオフィスのあるビルを発見。
ビルはいたって普通なオフィスビルで安心しました笑
受付はこんな感じ。
全体的に木の温もりが感じられる非常に落ち着いたオフィスでした!
会場は少し奥に入ったところにある芝生が敷かれた広場でした。
50~60人座るとそこそこいっぱいになるこぢんまりしたスペースでした。
(このころは記事書こうなんて思ってなかったので写真撮ってません。。唯一の後悔(´・ω・`))
1人目 | id:hakobe932さん「はてなで一人前のエンジニアになる方法」
新卒7年目のid:hakobe932さんのキャリアを振り返りつつ、はてなのエンジニア制度や教育制度などについて説明してくれました。
聞けば聞くほどはてなさんが技術を大事にしていることが伝わってきました。
エンジニアが多い訳ではないため、一人一人がある程度の幅の技術を知らないと仕事ができないといった背景もあるかもしれませんが、
みんな技術が好きで会社としてもその気持ちを全力でサポートしているという印象でした。
勉強会やSlackを用いた技術共有など割と普通な取り組みの中で一つ特徴的だったのは、勉強会の打ち上げ費用のサポートをしてくれる点です。
勉強会の打ち上げをサポートしてくれるとか最高ですよね。
(ちょっと重い本だと一冊全部勉強するのって結構辛い。。)
それでは印象に残ったお話をいくつか。
一人前のエンジニアなのか?→たぶん常に違う。でも新卒のころに自分に対してはそうだと言ってもいいかな。
エンジニアとしての向上心と、成長できている実感を得られていることがわかるお話でした。
僕も将来こう言えるようになりたいなーと思いました。
基礎を大事にする組織。寿命が長い基礎技術を大事にする。
基礎技術を大事にしようという文化が素晴らしいと感じました。
このような基礎技術の勉強は目の前のプロジェクトに直接関係ない時もありますが、長く安定したプロダクトを作るためには必須だと思います。
はてなブックマークやはてなブログなどの長寿命のプロダクトをリリースされてきた会社だからこその価値観なのかもしれないなと思いました。
また、別のところで話されていたのですが、技術選定の際に"長く使える技術なのか"を意識して選ばれているのも印象的で、はてなさんのエンジニア文化を垣間見ることができました。
2人目 | id:Songmuさん。「エンジニアがはてなでマネージャーをやるということ」
チーフエンジニア/Meckerelディレクターとして働かれているid:Songmuさんからのマネージャーに焦点を当てたお話でした。
マネージャーの仕事をイマイチわかってないので大事なポイントとかよくわからないのですが、研究室で後輩の指導をする先輩として投影して聞いてみました。
(id:Songmuさんの話された内容は後日ブログにまとめてくださるそうなので詳しく知りたい方はそちらを見てください。)
オーナーシップ中心マネジメント。メンバーにオーナーシップを持ってもらう。
とにかくオーナーシップという言葉が多く使われていて、確かにオーナーシップ大事だなーと思って聞いてました。
僕はこのオーナーシップを愛着心と責任感という風に受け取ったのですが、メンバーがそのプロダクトに愛着心と責任感を持って取り組んでいればその組織って多分強いですよね。
懇親会などで話を聞いていても、はてなのエンジニアはオーナーシップを持っている方ばかりで、とても楽しそうな姿が印象的でした。
メンバーのパフォーマンスが出ないのはマネージャーの問題
id:hakobe932さんのお話の中でも感じたのですが、はてなさんはエンジニアのモチベーションをどう保つか、どうすれば楽しく働けるのかということをすごく考えていました。
マネージャーはエンジニアが楽しく働ける環境を作ってあげることが大事で、パフォーマンスが出ない時はその環境が整ってないからだからマネージャーの問題、って考え方なのかなと思いました。
評価者は優劣をつける立場ではなく、上にアピールする味方
こんなマネージャーの下で働きたいなぁと素直に感じました。
3人目 | id:y_uukiさん。「ウェブオペレーションエンジニアのになるまでの思い出」
ここからはLTでした。
id:y_uukiさんの技術の思い出を振り返りながらのウェブオペレーションエンジニアに関するお話でした。
大学でlinuxやC言語に触れ、コマンドライン・UNIXかっけーって話など共感できる話ばかりで終始頷きながら聞いてました笑
4人目 | id:a-knowさん。「ぼくがセールスエンジニアというキャリアを選んだ理由(わけ)」
イベント最後の発表はid:a-knowさんからのセールスエンジニアについてのお話でした。
これまでの知識を活かして更にエンジニアとして成長したい!という気持ちがとても伝わってくるお話でした。
www.slideshare.net
ここまで4人の方のお話を聞いてきましたが、みなさん向上心がとても強くて、さすがはてなという感じで刺激的でした。
パネルディスカッション
4人のお話のあとはパネルディスカッションがありました。
このパネルディスカッションを通して普段のはてなのエンジニアの生活を知れたり、そこからエンジニア文化が垣間見れたりしたので、僕としては一番収穫があった時間でした。
一方で、話に夢中であまりメモをとっていないのが後悔です。
(頭には入っているんですけど、話題が多くて忘れてるのがありそう。。)
それでは印象に残ったところをいくつか。
リリースタイミングを昼1にしたり、金曜日を避けたりなどしている
エンジニアの生活に関する話をしているときに、プルリクがたまるとbotがリリースしようと教えてくれるといった話をしていたときの話です。(話ばっかで読みづらいな。。)
変な時間にリリースすると障害が起きた時に障害対応班が帰れない・土日出社などになってしまうのでタイミングを考えていると言われていました。
最初は良いチームだなぁと思って聞いていたのですが、id:y_uukiさんいわくインフラ側からお願いしているのでは無く自然とそうなっているらしいので、更に良いチームだなぁと思いました。
他にも"無理はせずに定時で帰る"といったことも言われていて、エンジニアが楽しく働けるような工夫が様々なところでされていると感じました。
隣の部署にすごい人がいる、ではなくすぐ隣にすごい人がいる。
これはそこまで規模の大きくない会社ならではの特徴だなと思いました。
エンジニアとして成長するためには、すごい人が近くにいて切磋琢磨しあうというのは必須条件だと思っているのですが、大企業になると隣の部署という距離感になるのかーと思って聞いていました。
個人的には就活前にこの言葉を聞けたのはラッキーだったなと思います。
最後に!
懇親会含め約3時間のイベントだったのですが、様々なお話を聞けてよかったです。
そして何より楽しかったです!
その理由は登壇された方がみなさん楽しそうに話されていたからなのかなーと後になって感じました。
はてなさんの雰囲気を肌で感じることができたので良かったです。
エンジニアとしてはてなさんは働きやすいだろうなと感じたし、成長もできるだろうなと思いました。
アウトプットを重視する文化もエンジニアとしての価値を高めていけると思うので良いなーと。
もはや良かったとしか言ってないのですが、本当に行って良かったと思える3時間でした!
またこういうイベントあったら参加したいと思います!
最後に、今回のイベントの中で一番いいなと思ったところを。
時折でる関西弁に癒される #hatenatech
— Mitsunobu (@mtdtx9) December 6, 2016
登壇された皆様、楽しい時間をありがとうございました!
運営の方々、お疲れ様でした!
さくらVPSにUbuntu LTS 14.04を入れてRedmineを導入するまで Part.1 サーバの基本的な設定
サーバーをいじってみたいなーということで値段も手頃なさくらVPSを借りてみることにしました。 インターネットにさらされたサーバーを持つということで、知らぬ間に攻撃に加担しないように、とりあえず基本的な最初にすべき設定をまとめました。 何か不足している点があれば教えていただけると幸いです!
Ubuntuのインストール
さくらVPSのデフォルトOSはCentOSなのでUbuntuをインストールします。 これは公式のインストールガイドが丁寧なのでこれに沿っていけば問題なくできます。 今回ユーザーはdeveloperで作成したので、以下のコードは適当に読み替えてください。
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永遠の初心者の備忘録
SwiftでFacebookのユーザー認証からデータ取得まで(Facebook SDK)
SNSとの連携ということで代表的なFacebookの認証からデータ取得まで実装しました。 FacebookはSocialFrameworkとFacebook SDKを使う2種類の方法があるのですが、今回はFacebook SDKを使って実装してみました。
SDKの導入
以下のリンクの内容に沿ってSDKを導入します。 行ったことは以下の5項目です。
- SDKのダウンロード
- Facebook Appの作成 (App ID等の取得)
- ProjectにFacebookのSDKを追加
- plistファイルにApp IDなどの情報を追加
- Application Delegateに必要なコードを追加
Facebook SDK for iOS - Getting Started
ユーザー認証
- ログインボタンの設置
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にボタンを追加 } }
- 認証後の処理
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
Swiftで作るかっこいいwalkthrough(Airbnb風)
Airbnbのwalkthroughがかっこいいなーとおもったので同じようなものをつくってみました。
Airbnbの良いところは、
- 横にスクロールすると今の画像がフェードアウトし、次の画像がフェードインする
- 画像が常にアニメーションしている
- 横スクロールに合わせて下のボタンの色がなめらかに変わる
です。
今回はこれらの項目を実装しました。
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
これで今の色と次の色の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(¤tColor.red, green: ¤tColor.green, blue: ¤tColor.blue, alpha: ¤tColor.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) }
最後に
全体像は以下のようになってます。
UIViewAnimationOptionsの定義から同時に指定できるオプションを理解する。
はじめに
UIViewAnimationOptionsについて
現在アニメーションについて勉強中なのですが、iOSにはUIViewAnimationOptionsという便利なものがあります。
これはOptionを指定するだけでかっこいいアニメーションを自動でしてくれるものです。
例えばTransitionCurlDownを指定するとremoveFromSuperview()に対して以下のようなアニメーションを付けてくれます。
これは以下のように実装することで使えます。
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を設定したので、同様のアニメーションが永遠にリピートされます。
本題
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で同じようなことをするときには、実装する側も使う側も、オプションが同時に適用できるのか、片方しか適用できないのかを理解し実装に反映させることで思わぬバグを防ぐことができそうです。