2017年振り返り
個人の日記なので2017年の振り返り。
お仕事
ご縁があって11月から渋谷にある事業会社でiOSアプリ開発者として働いている。
春先、前年から開発していたiOSアプリのリリース延期が決まりモチベーションが下がっていたところに、今の会社の人に声をかけてもらい、会社見学やコード課題などを経て働くことになった。
2ヶ月経過した感想としては、今のところ概ね楽しく働けている。
完璧な職場なんてあるはずもないけど、月の残業時間が10時間未満だったり、希望したREALFORCEが特に何も言われることもなく出社時に用意されている程度にはホワイトな職場。
子持ちの自分にとっては比較的自由な勤務体系なのも嬉しいところ。
コードレビューやCI環境なども整っていたり改善が進んでいたりしていて学ぶことが多い。
少人数なチームだけど優秀な人ばかりなので、自分もがんばらねばと思っている。
勉強会など
久しぶりにiOSDCという大きめなイベントに参加して多くの刺激をもらった。
子どもがいるとなかなか参加しにくいけど厳選して参加していこうと思う。
プライベート
"Write Code Every Day" を実践しようと思い立ってやってみた。
仕事のコード除くとこんな感じ。後半息切れ気味な感は否めない。
結果としては毎日というわけにはいかなかったけど、日々ちょっとでもコードを書く、エディタに向かうという習慣はできた気がする。
どうしても作りたいものがあってプログラミングしている人間ではないのでサンプルコードや検証コードがほとんどだけど、転職の際にも多少は役に立ってくれた。
やってみてわかったのは、時間がない中でやると圧倒的にインプットが減るということ。
子どもたちを寝かしつけた後の22時〜24時の最大2時間程度が貴重な勉強時間なので、バランス良くインプット・アウトプットができるように改善したい。
立てていた目標
- Qiita:Teamをディレクターさん、デザイナーさんも使うようになってきたのでそっち向けの記事も書く
- 前職で多職種向けの記事を書いたけど十分じゃなかった
- Swift環境での自動テストを導入する
- できなかった
- iOSアプリ開発に偏った1年だったので、Web開発もうちょっとがんばる
- できなかった
- 何かしらアウトプットする
- "Write Code Every Day"を不完全ながら実践
トータルで見ると、あんまり進捗よくなかった。無念。
来年の目標
たくさん目標立ててもできないのでこんなもんで。
Swiftでクロージャが入れ子の場合のキャプチャリスト
はじめに
Swiftのクロージャには循環参照を回避するための仕組みとしてキャプチャリストがあります。
最近、キャプチャリストがあるのにメモリリークするというバグに遭遇して、自分の認識が間違っていたことが分かったのでメモしておきます。
結論としては、クロージャが入れ子の場合には、直接使っていなくても外側にキャプチャリストを書いておかないと強参照になります。
バージョン
Swift 4.0
サンプルコード
class Hoge { deinit { print("deinit") } let n: Int init(n: Int) { self.n = n } // 内側だけweak lazy var closure1: () -> Int = { return {[weak self] in return (self?.n)! * 2}() } // 外側だけweak lazy var closure2: () -> Int = { [weak self] in return {return (self?.n)! * 2}() } // 両方weak lazy var closure3: () -> Int = { [weak self] in return {[weak self] in return (self?.n)! * 2}() } // 内側だけunowned lazy var closure4: () -> Int = { return {[unowned self] in return self.n * 2}() } // 外側だけunowned lazy var closure5: () -> Int = { [unowned self] in return {return self.n * 2}() } // 両方unowned lazy var closure6: () -> Int = { [unowned self] in return {[unowned self] in return self.n * 2}() } } do { // deinit呼ばれない print("closure1") let hoge = Hoge(n: 100) print(hoge.closure1()) } do { print("closure2") let hoge = Hoge(n: 100) print(hoge.closure2()) } do { print("closure3") let hoge = Hoge(n: 100) print(hoge.closure3()) } do { // deinit呼ばれない print("closure4") let hoge = Hoge(n: 100) print(hoge.closure4()) } do { print("closure5") let hoge = Hoge(n: 100) print(hoge.closure5()) } do { print("closure6") let hoge = Hoge(n: 100) print(hoge.closure6()) }
iOSで画像をリサイズして保存するときに気をつけること
はじめに
カメラロールからユーザーが選んだ画像をUIImageViewに表示してから、リサイズしてファイル保存する際に、ちょっとはまったのでメモ。
バージョン
- Xcode9.0.1
- Swift4.0
やりたかったこと
- ユーザーが選択した画像ファイルを一定の幅にリサイズして保存しておき、非同期でアップロードする
起こった事象
コード
最終的にこんな感じのリサイズ処理にしました。
サンプルコードなので単純に縦横半分にしています。
private func resize(image: UIImage) -> UIImage { // 縦横の画素数を半分にする let width = image.size.width * 0.5 let height = image.size.height * 0.5 // scale の設定が0だとオリジナル画像よりもサイズが大きくなるので1を設定 UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), false, 1.0) image.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) let resizeImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return resizeImage! }
原因
UIGraphicsBeginImageContextWithOptions(_ size: CGSize, _ opaque: Bool, _ scale: CGFloat)
の scale
パラメータに0.0を指定すると、デバイスにあわせて@2/@3の指定にしてくれます。
The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.
UIGraphicsBeginImageContextWithOptions(_:_:_:) - UIKit | Apple Developer Documentation
その結果、iPhon Xだと@3の画像となり、結果としてファイルサイズが大きくなってしまっていたのでした。
アプリ埋め込みのアイコンなどはデバイスにあわせた画像を用意しますが、ユーザーの写真などは原寸でいいと判断して、scale
を 1.0指定することで解決しました。
UIVIewController.viewの生成タイミング
Instantiateというライブラリがあります。
StoryboardやXibを使いつつコンストラクタインジェクションできる素敵ライブラリなのですが、コードを読んでてよくわからなかったところがありました。
Instantiate/Storyboard+UIViewController.swift at master · tarunon/Instantiate · GitHub
このコードの self.view
を参照しているところ。
左辺が _
となっているので何もしてないように見えます。
if self is ViewLoadBeforeInject { _ = self.view }
ViewLoadBeforeInject
というのはプロトコルで、定義はこんな感じ。
/// This protocol allow viewController force load view before call `inject` public protocol ViewLoadBeforeInject { }
コメントを読むと、 UIViewController.view
を inject
が呼ばれる前に強制的にロードするもののようです。
inject
はコンストラクタ内で呼ばれるメソッドです。
色々読んでるうちに UIViewContoroller.view
のコメントにたどり着き理解できました。
open var view: UIView! // The getter first invokes [self loadView] if the view hasn't been set yet. Subclasses must call super if they override the setter or getter.
先程のコードはコンストラクタ中のコードであり、最初のアクセスなのでviewを生成してるようです。
ゲッターにこんな副作用持たせてるとは思わなかった。
このコードのおかげで、inject
メソッドで UIViewContoroller
に置いてある UILabel
とかをセットするような場合に、このプロトコルを適用すればクラッシュしません。
内部の変数に保持して ViewDidLoad
とかでセットするなら、このプロトコルは不要です。
うまいことできてる。
祝日や会社の休日に起動しないSlack通知bot
はじめに
平日の18時に行っている夕会用に、開始時間になると曜日ごとのファシリテータをお知らせするbotを運用中。
土日祝日と会社指定の休日は通知飛ばないようにしてます。
祝日判定のためにGoogleAppsScriptとGoogleカレンダーを利用してます。
Googleカレンダーに用意されている日本の祝日カレンダーで祝日を、別に用意した会社の休日カレンダーで年末年始や夏季休暇に対応しています。
コード
function onOpen() { if (isHoliday()) { return } deleteAllTriggers(); setupTriggers(); } function deleteAllTriggers() { var allTriggers = ScriptApp.getProjectTriggers(); for(var i=0; i < allTriggers.length; i++) { ScriptApp.deleteTrigger(allTriggers[i]); } } function getFormatedDate(time){ var now = new Date(); var year = now.getFullYear(); var month = now.getMonth() + 1; var date = now.getDate(); month = ("0" + month).slice(-2); date = ("0" + date).slice(-2); var strDate = year + "/" + month + "/" + date + " " + time; return strDate; } function setupTriggers() { // 通知時間の設定 var trigger = new Date(getFormatedDate("18:00")); var onChangeTrigger = ScriptApp.newTrigger("notfy") .timeBased() .at(trigger) .create(); //全トリガー削除するので再設定 var onChangeTrigger = ScriptApp.newTrigger("onOpen") .timeBased() .atHour(10) .everyDays(1) .create(); } function notfy() { var facilitator = getTodayFacilitator(); postSlack(facilitator); } function isHoliday() { var japanHoliday = CalendarApp.getCalendarsByName('日本の祝日'); var companyHoliday = CalendarApp.getCalendarsByName('会社の休日'); var holidays = japanHoliday[0].getEventsForDay(new Date()) + companyHoliday[0].getEventsForDay(new Date()) ; return (holidays != ''); } function getMembers() { var values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('member').getRange(2, 1, 5, 2).getValues(); var members = {'日':'', '月':'', '火':'', '水':'', '木':'', '金':'', '土':''}; for (var i = 0; i < 5; i++) { var youbi = values[i][0]; members[youbi] = values[i][1]; } return members } function getTodayFacilitator() { var now = new Date(); var week = ['日', '月', '火', '水', '木', '金', '土']; var members = getMembers(); var facilitator = members[week[now.getDay()]]; return facilitator; } // send to slack function postSlack(name) { if (name == "") return; var payload = { "text" : "<!here> " + "今日の当番は" + name + "ですよ", "channel" : "#general" // 表示名やアイコンはお好みで } var options = { "method" : "POST", "payload" : JSON.stringify(payload) } var url = "https://hooks.slack.com/services/hoge/fuga/piyo"; // 各自のURL var response = UrlFetchApp.fetch(url, options); var content = response.getContentText("UTF-8"); }
ポイント
使うまで知らなかったけど、GoogleAppsScriptの時間起動トリガーだと毎日18時っていう指定ができませんでした。
近いのは、毎日18時〜19時の間に起動するというトリガーか特定日の18時というトリガーです。
仕方ないので、毎日10時〜11時の間に起動して、setupTriggers()
という関数でその日の18時のトリガーを設定してます。
さらに面倒なことにトリガーを区別するのが大変ぽかったので、一回全部消してから全部設定し直すようにしてます。
Linux上で特殊文字を含む名前のファイルの削除方法
はじめに
コマンドの操作ミスやスクリプト出力の文字化けなどで特殊文字を含む名前でファイルができてしまい、単にrmコマンドでは削除できない時の削除方法。
いざという時のためにメモ。
実験用のファイル
temp $ ls "ccc.txt -bbb.txt aaa.txt temp $
他にもありえますが用意するのがめんどくさかったのと、今回の削除方法はファイル名によらず使えるのでこれで。
実験
とりあえず用意したファイルを普通に消そうとするとどうなるか見てみます。
先頭がダブルクォートのファイル名
temp $ rm "ccc.txt > temp $
次の入力を待たれてしまいます。
先頭がハイフンのファイル名
temp $ rm -bbb.txt rm: illegal option -- b usage: rm [-f | -i] [-dPRrvW] file ... unlink file temp $
rmコマンドのオプションと解釈されてしまいます。
解決方法
いろいろなやり方があるのですが、汎用的な方法を。
手順1 ls -iでinode番号を確認
temp $ ls -i 2991451 "ccc.txt 2991471 -bbb.txt 2991475 aaa.txt temp $
ファイル名の前に表示されているのがinode番号です。
手順2 確認したinode番号を指定して削除
temp $ rm `find . -inum 2991451` temp $ ls -bbb.txt aaa.txt temp $ rm `find . -inum 2991471` temp $ ls aaa.txt temp $
削除できました。
実際にやる場合は、事前の削除対象の確認を忘れずに。
おまけ
実験用に使ったパターンのものは別の方法でも削除できます。
先頭がダブルクォートのファイル名
2つ方法があります。
先頭がハイフンのファイル名
こちらは、ダブルクォートの方法では消せないので以下の方法を使います。
- コマンドのオプションに
--
を指定するrm -- -bbb.txt
iOSDC Japan 2017に行ってきました
子どもができてからというもの、なかなか勉強会やイベントに参加できなくなっていましたが、久々に参加してきました。
かなり悩みつつセッションの選択をしたなかで、特に印象に残ったものをあげます。
両OSやるマンという選択
意外とAndroidも開発してるよって人が多かったのが印象的でした。
違いを意識してプロダクトに落とし込むことが一番重要っていう部分は同意しかなくて、ディレクターさんやデザイナーさんにこそ共有したいなーと思いました。
Android開発もやってみよっかなーという気分になりました。
アプリエンジニアはどのように事業に貢献すべきか
近々、初めてBtoCのサービス開発に関わる予定なのですが、同じようなことに悩むんだろうかと思いつつ聞いていました。
気になったキーワード
- アプリを使うことがユーザーにとってやりたいことではない
- ユーザーが喜ぶ体験を理解して、アプリ上で実現する
- アプリ以外も含めてサービスでありユーザー体験である
何度か読んで、自分なりの考えをまとめてみるつもり。
結婚式を支えた技術 Firebaseを活用したサーバレスiOSアプリケーション開発
結婚式の写真を共有してもらうために1ヶ月でアプリを作ってしまうという素敵なお話。
身近なところにある問題を解決するのは、ユーザーも近くてフィードバックもらいやすいし、なにより解決すれば喜んでもらえるし、いいことずくめですね。
我が家でも、妻からこんなアプリできないの?みたいなことをたまに言われるのですが、子どもと遊ぶのを言い訳にしてしまっていて反省。
とりあえずFirebase触ってみようと思いました。
全体通しての感想
めちゃめちゃ楽しかったです。
以下、楽しかったことを雑にまとめます。
- オープニングから力の入ったムービーでテンションあがる
- セッションはどれも面白く、同じ枠で聞きたいものがかぶりまくって泣く泣く選択するという贅沢な状況
- 懇親会でもないのにビールが配られて、飲みながら聞くLT最高
- 運営の方々が、ほんとによく動いていて、とにかくスムーズな運営が印象的
ただ、2日目の途中で体調を崩してしまい、途中で帰らざるを得なくなってしまったのが心残りです。
来年も開催されたら、最後まで参加するぞ。懇親会も参加できたらいいな。