文字っぽいの。

文字を書いています。写真も混ざります。

#esa の記事が古いものか一発で分かるChrome拡張「esa鮮度」をリリースしました。

様子

f:id:FromAtom:20190617150309p:plain

こんな感じで、よくある「最終更新日からn日経ってます」という表示が出ます。便利。 なお、README更新されてないやんみたいな意図は全く無いのであしからず。

インストール

こちらからどうぞ。

chrome.google.com

実装

github.com

簡単なコードですが、GitHubで公開しています。

まとめ

ドキュメントが古いとつらいことが多い世の中ですが、このChrome拡張を入れたらぱっと見で分かるようになって便利です。 Chrome拡張なので、ブラウザに入れてしまえばすべてのesaグループで使えるのも便利です。

そうそう、Yakitori, Kujaku はいい感じのネーミングができたんですが、esa鮮度は難しかったですね。 いろいろ考えたうえで諦めて、「英語でカッコつけるより、ダサくても分かりやすいほうがええわい!」と考えてesa鮮度という名前になりました。ちなみに、アイコンが腐ってそうなのは絵心がないからです。

それではesaユーザーのみなさま、esa鮮度をぜひご利用下さい。

UIScrollViewがスクロール中かどうか判定する

やりたいこと

  • UIScrollView, UITableView, UICollectionViewなどがスクロール中かどうか知りたい
  • タップはしてないけどまだ慣性スクロールが続いてるとか
  • 見た目上動いてないけどユーザーが指を置いていてスクロール状態だとか

解決コード

こんな感じのコードを書けば良い

override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    isScrolling = true
}

override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate {
        isScrolling = true
    } else {
        isScrolling = false
    }
}

override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    isScrolling = false
}

isScrollingtrueのときはスクロール中で false のときはスクロールしてないとして、他の機能を実装していくといい感じになる。

MTGで良い質問をしてもらうためのテクニック

結論まとめ

  • MTG後に「あの場では言い出せなかったんですけど〜」と個別に相談やDMが飛んできたら良くないことなのでやめるように指導する
  • 質問の仕方を工夫して、「さて、これを実行するにはどういう順序かな?」と考えてもらえるようにする

内容

MTGなど一通り議論や情報共有を終えたあとで「なにか質問ありますか?」と聞くタイミングがあると思う。この時に適切に質問を得て、実際に行動を移す際に「そういえばここ決まってないのでは?」とならないように、ちょっとしたテクニックをご紹介。なお、最近気づいたので、試している最中です。

もちろん、これがあればすべてのMTGが完璧に行われて、ヌケモレがゼロになるわけではないのであしからず。

  • はじめに、オーガナイザーはMTG後に個別でされる質問や提案をゼロ近づける意識を持つのが大事
  • まず、MTGに関係のある質問・意見・提案をMTGの場でせずに、後から個別に行うのは非常に邪悪なことを理解する
    • 会社によっては呼び出されて説教される
  • なぜ?
    • オーガナイザーの時間を追加で消費させているし、その場で議論も解決できない(MTGは解散しているため)
    • もしその質問や意見によって、再MTGや再意思決定が必要な場合、MTGに参加していた全員の時間を追加で消費させている
    • こういったことから、他人の時間を無碍に消費させ会社に損失をもたらす邪悪な行為だと認識されている
    • ちなみに外部の勉強会やカンファレンスなどでもこれをやるとキレられる
    • 「もう一点質問あるんですが、後で個別で質問しますね。」みたいな言動はコミュニティに疎まれる
    • 知識を広く発信するカンファレンスという場であるのに、わざわざ登壇者の時間を専有して知識も独り占めにしようとするのはどう考えても邪悪な行為
  • もちろん後から問題点に気がつくこともあるので一概に邪悪とは言えない
    • 「あの場では言い出せなかったんですけど〜」とDMなどが飛んできたら邪悪なので指導する
  • 「今気づいたんですけど、実はこの方法ダメなのでは?」はもちろん起きる
  • これを防ぎたい
  • なので聞き方「なにか質問ありますか?」から変える
  • 例えば
    • 「じゃあ明日から実装開始してほしいんですが、問題なく実装できそうですか?」
    • 「来週からこの仕事が増えるけど、問題なく開始できそうですか?」
  • MTGや情報共有では受け身モードになりがち
  • この質問で計画を実行する当事者サイドに脳みそをスイッチしてもらう
  • こういう質問だと「あれやって、これやって、こうすればいけるかな」と段取りを各自に考えてもらえる
  • 段取り途中でわからないポイントとか、不安点が質問としてあがってくる
  • やったね

Sora iOS SDKがちゃんと導入されているのに Module 'Sora' has no member named 'hoge' となる問題の解決法

import Sora

struct Test {
    init() {
        Sora.shared.audioEnabled = false
    }
}

と書くと使えるはずなんですが、Module 'Sora' has no member named 'shared' とエラーが出てしまいます。これを直すのは簡単で、

f:id:FromAtom:20190212135426p:plain

こういう感じにSimulatorではなくて

f:id:FromAtom:20190212135519p:plain

こうする。もしくは、実機に向けてビルドしてあげればよい。 

sora.shiguredo.jp

にはちゃんと (Sora iOS SDK はシミュレーターに対応していません) と書かれているが、見逃していた。アプリを作る場合、最初はSimulatorを使いながらあれこれ実装していくとおもうので、お気をつけください。

マネジメントとエンジニアリングの両立が難しいのはなぜか?のたとえ話

与太話です。

数ヶ月前、マネジメントをしながらメイン機能の開発をしていたら完全にタスクがオーバーフローして大変なことになったんですよ。 その時に「両立が難しいのは分かったが、なぜ両立が難しいんだろうか。」をひたすら考えていたら思いついた「たとえ話」です。

素潜り漁師と漁船で例えます。

エンジニアリングは素潜り漁

  • コードを書いているときは集中して潜りきらないといけない
    • エンジニアが割り込みを嫌うのはこのため
    • 途中で呼び止められたら潜水をやめて浮上しないといけない
    • 割り込みが終わったらまた潜り始める
  • 難しい機能を作る時ほど深く潜らないといけないというイメージ
  • 深さと釣果には相関がないので深ければ大漁というわけではない
  • ただ、深い漁場は素人の素潜り漁師が到達できないので、漁場が荒らされていなくて釣果が期待できる
  • 浅めの場所を複数箇所どんどん潜って数を稼ぐタイプがいる
  • 深い場所に潜り続けて「あの海産物なら奴だ」と言わせるタイプもいる

マネジメントは漁船上にいる船長

  • 漁船の上でレーダー・天候・海の様子をみたり、他の船や漁港に無線連絡したりする
  • 船上で釣果を見ながら「ここは獲れなさそうだから移動するぞ」などとする
  • 漁の終了などの管理もする
  • 急なシケや船の接近に備えて注意を払う
  • 失敗すると素潜り漁師が死ぬ or 釣果が振るわずに飯が食えなくなるので責任が大きい
  • とにかく色んな状態・状況・様子を俯瞰して見続けないといけない

  • 担当しなければならない視点・視座・視野がぜんぜん違う
  • なのでこれを同時にやるのは非常に難しい
  • 両立できる人は少ない
    • マネジメントは小さく始まるので、みんな最初は両立できると思いこんでいる
    • だんだんとエンジニアリングに割ける時間が減っていったり、逆にマネジメント系の仕事を受けないように整理していく
  • できる人はいるか
    • 複数サービスにまたがって開発している時に、コンテキストスイッチが速攻で切り替えられる人
    • 定時内はマネジメント、定時後はエンジニアリングのように力強いした働き方ができる人

iOS 12以降のAPIで "NSKeyedArchiver" と "NSKeyedUnarchiver" を使う

環境

  • Swift: version 4.2.1

やりたいこと

NSKeyedArchiverNSKeyedUnarchiver を使って、UserDefaultsやKeyChainに色々入れたり出したりすることがあると思います。iOS 11まではこうやって書いていました。

// ArchiveしてUserDefaultにセット
let rootObject: [Int] = [1, 2, 3, 4, 5]
let archivedData = NSKeyedArchiver.archivedData(withRootObject: rootObject)
UserDefaults.standard.set(archivedData, forKey: "key")

// UserDefaultからゲットしてUnarchiveする
let data = UserDefaults.standard.data(forKey: "key")!
let unarchivedObject = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Int]

print(unarchivedObject) // => Optional([1, 2, 3, 4, 5])

ここで使っている archivedData(withRootObject:)unarchiveObject(with:)iOS 12以降のAPIではDeprecatedになっているので、置き換えます。

// ArchiveしてUserDefaultにセット
let rootObject: [Int] = [1, 2, 3, 4, 5]
let archivedData = try! NSKeyedArchiver.archivedData(withRootObject: rootObject, requiringSecureCoding: false)
UserDefaults.standard.set(archivedData, forKey: "key")

// UserDefaultからゲットしてUnarchiveする
let data = UserDefaults.standard.data(forKey: "key")!
let unarchivedObject = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [Int]

print(unarchivedObject) // => Optional([1, 2, 3, 4, 5])

Xcodeからは

'unarchiveObject(with:)' was deprecated in iOS 12.0: Use +unarchivedObjectOfClass:fromData:error: instead

というWarningが出るんですが、素直に unarchivedObjectOfClass(fromData:, error:)を使うと

let unarchivedObject = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self], from: data) as? [Int]

というコードを書く必要があり(ArrayじゃなくてNSArrayです)難しさが増すので unarchiveTopLevelObjectWithData()を使うのをおすすめします。