koogawa blog

iOS、Android、foursquareに関する話題

プログラミングに関する情報を英語でググるときのコツ

あけましておめでとうございます!今年も本ブログをよろしくお願い致しやす🙏

さて、私はプログラミングに関する情報を調べる際、基本的に英語でググるようにしています。理由は単純で、英語のほうが圧倒的に情報量が多いからです。このエントリではいつも私が英語でググる際に使っているキーワードなどをメモしておきます。

実装した機能がうまく動かない場合

  • 〜 not work

でググります。「動く」だからといって move で検索しても期待した検索結果にはなりません。

さらにOSバージョン等を指定する際は

  • 〜 not work in iOS 12

のように指定するとヒット率が上がります。*1

〜が表示されない

  • 〜 not show (up)

メソッドなどが呼ばれない/実行されない

  • 〜 not called
  • 〜 not execute

プログラムで〜したい

programmatically というキーワードを使う。

例えばiOSの開発で「Autolayoutの制約(constraints)をStoryboardからではなくプログラムコードで追加したい」場合は

  • autolayout constraints programmatically

なんかでググるとそれっぽいのがヒットする。

2つの違いを知りたい

日本でもよく使う vs というキーワードが便利。

例えば REST における PUT と POST の違いについて知りたい場合は

  • PUT vs POST

のようにググるとそれっぽいのがヒットする。または普通に

  • difference PUT POST

のようにググるのも良い。

〜かどうかをチェックしたい

普通に check というキーワードが使える。例えばある文字列が〜を含んでいるかどうかをチェックする方法を知りたい場合は

  • check if string contains 〜

のようにググってやるといい感じにヒットする。

〜を検知したい

例えば通信が切断されたタイミングを検知する方法を知りたい場合は

  • how to detect disconnection

のようにググると良い。

ユーザーが使用している OS のバージョンを検知する方法も

  • how to detect user os version

のようにググるとそれっぽいのがヒットする。

〜の実装方法を知りたい

  • How can I implement 〜

〜についての最良の方法を知りたい

  • best practices for 〜

あわせて読みたい

blog.koogawa.com

*1:in ではなく on iOS X って書く人も多いので、どちらが正しいのかはわかっていない

2018年を振り返る

このエントリは、今年一年の自己の振り返り Advent Calendar 2018 - Adventar の記事です。


どうも、koogawa です。今年も残りわずかですね。

宮崎の空
宮崎の空

今年のはじめに目標を立てていたので、ひとつずつ振り返ってみたいと思います。

blog.koogawa.com

目標振り返り

1. Stack Overflowで5000 reputationを目指す

達成しました🎉

blog.koogawa.com

実を言うと5月の時点ではまだ 4,000 reputation 程度でした。

しかし後半になると怒涛の追い上げを見せ、終わってみれば10月には目標の 5,000 に達していました。人間、追い込まれるとなんとかなるもんですな。

この調子で次は 10,000 reputation 目指します💪

2. 公開しているOSSのメンテナンスを続けていく

いくつかオープンソースのライブラリを公開していますが、しっかりメンテナンスも続けております。

今年公開された Swift 4.2 にももちろん対しております。

何れもニッチなライブラリですが、ちょっとでも使ってくれる人がいる限りメンテナンスは続けていきたいと思っています。

3. リモートでも参加できる勉強会に参加する

リモート・オフライン問わずいろんな勉強会に参加しました。

昨年はまだ子供が小さかったこともあり、iOSDC への参加は見送っていたのですが、今年は一年ぶりに参加できました!楽しかったー😆

子育てとバランスを取りつつ、来年も可能な限りイベントに参加していく所存です。

おまけ

その他、今年あったイベントをちょっとだけ

息子が2歳に

移住当時は0歳だった息子が先日2歳になりました👦

f:id:koogawa:20181201223649p:plain
みやざき臨海公園はいつも空いている

宮崎の自然に囲まれ、 元気に育っております。

副業から本業へ

東京から宮崎へ移住後、しばらく宮崎を本拠地とする企業に正社員として勤めていたのですが、今年10月に退職しました。

退職理由としては、今年の夏ぐらいから初めた副業*1の方が楽しくなってしまったというのが正直な理由です。今はその副業が本業になっており、よりエキサイティングな毎日を送っております。

また、地元の会社文化に適応できなかったというのも理由の一つです。詳しい内容については割愛しますが、やはり地方には地方独特の文化や仕事の進め方があることを肌で感じました。もし地方への移住に興味があり、詳しい話を聞いてみたい方がおりましたら適当に声をかけてください。

そんなわけで、来年もどうぞよろしくお願い致します!

*1:東京の案件です

【iOS/Swift】Firebase Cloud Firestoreで簡単なGPSロガーを作ってみる

この記事は Firebase #2 Advent Calendar 2018 4日目の記事です。


Firebase を理解するには何か作ってみるのが一番!ってことで、今回は簡単なGPSロガーを作ってみました。

f:id:koogawa:20150802150835p:plain

次のような機能があります。

  • Startボタンを押すと位置情報を記録開始
  • アプリをバックグラウンドに落としても記録し続ける
  • 位置情報が取得されると地図にもピンが立つ
  • 1日経過したデータは起動時に自動削除
  • Stopボタンを押すと位置情報の取得終了

***

以下、実装メモです。すべての実装は説明できないので、完全なソースコードは最後の方に貼ってある GitHub リポジトリを参照してください。また、今回は学習を目的としたサンプルプログラムという位置付けなので、料金については詳しく触れません。Cloud Firestore はデータの読み取り、書き込み、削除の回数によっても課金されますので、下記のリンクもよくお読みください。

Cloud Firestore の料金  |  Firebase


動作環境

  • Xcode 10.1
  • Swift 4.2
  • FirebaseCore 5.1.8
  • Firebase/Firestore 5.13.0
  • CocoaPods 1.5.3

手順

プロジェクト作成

https://console.firebase.google.com/ から新規プロジェクトを作成します。

f:id:koogawa:20181203231855p:plain

チェック項目については各自おまかせします。

テータベースの作成

左のメニューから「Database」を選択し、「データベースの作成」ボタンをクリックします。

f:id:koogawa:20181203233644p:plain

Cloud Firestore データモデルについて

公式ドキュメントから引用します。

Cloud Firestore は NoSQL ドキュメント指向データベースです。SQL データベースとは違い、テーブルや行はありません。代わりに、データは「ドキュメント」に格納し、それが「コレクション」にまとめられます。

これは図に表すと理解しやすいと思います。

f:id:koogawa:20181203232958p:plain

また、各「ドキュメント」には、一連のキーと値のペア(フィールド)が含まれています。今回は locations コレクションにGPSログ(ドキュメント)を追加していく想定で進めていきます。

f:id:koogawa:20181203233919p:plain

iOSアプリに Firebase 追加

iOS」ボタンをクリックします。

f:id:koogawa:20181203233955p:plain

バンドル名は com.example.GPSLogger、アプリ名は GPSLogger としました。

f:id:koogawa:20181203234015p:plain

GoogleService-Info.plist をプロジェクトに追加

画面に従っていくと GoogleService-Info.plist をダウンロードするように促されるので、これをプロジェクトに追加します。

f:id:koogawa:20181203234303p:plain

このファイルには Firebase を使うためのIDなどがセットされています。このファイルを追加しないとアプリ起動時にクラッシュします。

Cloud Firestore インストール

次のような Podfile を用意して pod install します。

use_frameworks!

target 'GPSLogger' do
  pod 'Firebase/Core'
  pod 'Firebase/Firestore'
end

アプリで Firebase を初期化する

AppDelegate で FirebaseApp.configure() を実行し、アプリを初期化します。

import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?)
    -> Bool {
    FirebaseApp.configure()
    return true
  }
}

モデル作成

位置情報を格納するためのモデルを作ります。緯度、経度、作成日時のみを保持するようにしました。

struct Location {
    let latitude: Double
    let longitude: Double
    let createdAt: Date

    init(document: [String: Any]) {
        latitude = document["latitude"] as? Double ?? 0
        longitude = document["longitude"] as? Double ?? 0
        createdAt = document["createdAt"] as? Date ?? Date()
    }
}

緯度経度を格納するための型(地理的座標)も用意されていますが、今回はわかりやすくするためにあえて Double 型を採用しています。

後述しますが、Firebase からはデータが Dictionary 型で返ってくるので、init(document: [String: Any]) { のようなメソッドを定義し、そこから Location インスタンスを生成できるようにしています。

Firebase では次の型が使用できます:配列、ブール型、バイト、日時、浮動小数点数、地理的座標、整数、マップ、Null、参照、テキスト文字列。

Startボタンを押したときの処理

位置情報の取得を開始します。*1

self.locationManager.startUpdatingLocation()

位置情報を追加

取得できた位置情報(CLLocation)を Firestore に追加します。

let db = Firestore.firestore()
var ref: DocumentReference? = nil
ref = db.collection(kLocationsCollectionName).addDocument(data: [
    "latitude": location.coordinate.latitude,
    "longitude": location.coordinate.longitude,
    "createdAt": FieldValue.serverTimestamp()
]) { err in
    if let err = err {
        print("Error adding document: \(err)")
    } else {
        print("Document added with ID: \(ref!.documentID)")
    }
}

追加日時は FieldValue.serverTimestamp() メソッドで時間軸を全てサーバーに預けてしまうことによってモバイルの個体差によるずれを解消しています。

リアルタイムアップデート

addSnapshotListener メソッドを使用すると、ドキュメントが更新されたときにイベントを受け取ることができます。

let db = Firestore.firestore()
self.listener = db.collection("locations")
    .addSnapshotListener(includeMetadataChanges: true) { [weak self] documentSnapshot, error in
        guard let document = documentSnapshot else {
            print("Error fetching document: \(error!)")
            return
        }
        print("Current data: \(document.description)")
        self?.loadStoredLocations()
}

ここではイベントを受け取るたび(つまり位置情報が追加されるたび)にデータをロードし、テーブルビューを更新しています。includeMetadataChangestrue にしているのは、データが削除された際にもイベントを受け取るためです。

Stopボタンを押したときの処理

位置情報の取得を停止します。

self.locationManager.stopUpdatingLocation()

また、リアルタイムアップデートも停止します。先ほど実行した addSnapshotListener メソッドの戻り値 ListenerRegistrationremove メソッドを呼ぶことで、イベントの受け取りを停止します。

self.listener.remove()

アプリを起動したときの処理

getDocuments メソッドを実行して Firestore に保存されている位置情報をロードします。

let db = Firestore.firestore()
db.collection(”locations”)
    .order(by: "createdAt", descending: false)
    .getDocuments { [weak self] snapshot, error in
        if let error = error {
            print("Error getting documents: \(error)")
        } else {
            self?.locations = snapshot?.documents.map { Location(document: $0.data()) } ?? []
        }
}

createdAt でソートをかけて全データを取得し、Location 型の配列としてローカルに持ちます。

古いデータの削除

古いデータがいつまでも残ってしまうのを防ぐため、1日経過した位置情報ログを削除します。

まずは whereField を利用して1日(86400秒)より古いデータを抽出します。

let db = Firestore.firestore()
db.collection("locations")
    .whereField("createdAt", isLessThanOrEqualTo: Date().addingTimeInterval(-86400))
    .getDocuments { snapshot, error in
        if let error = error {
            print("Error getting documents: \(error)")
            return
        }
        for document in snapshot?.documents ?? [] {
            print("Deleting document", document)
            self.delete(documentID: document.documentID)
        }
}

そして一件ずつ delete メソッドで削除していきます。

fileprivate func delete(documentID: String) {
    let db = Firestore.firestore()
    db.collection("locations")
        .document(documentID)
        .delete() { err in
            if let err = err {
                print("Error removing document: \(err)")
            } else {
                print("Document successfully removed!")
            }
    }
}

大量にデータを削除する場合はメモリ不足エラーを避けるため、小さなバッチに分けてドキュメントを削除することが推奨されています。

所感

割と簡単にGPSロガーが作れてしまいました。ドキュメントが充実しているのも安心できますね。

とくに便利なのがオフラインデータ機能です。この機能により、アプリが使用している Cloud Firestore データのコピーがキャッシュに保存されるため、端末がオフラインの場合でもアプリはデータにアクセスできます。端末がオンラインに戻ると、アプリがローカルで行った変更とリモートの Cloud Firestore に保存されたデータが同期されます。なんて素敵な機能なんでしょう!

ソースコード

こちらに全てアップしてあります。Firebase を使い慣れている方にとってはツッコミどころ満載だと思いますので、ご指摘など歓迎です:-)

github.com

リンク

*1:位置情報の利用をユーザーに許可して貰う必要があります。詳しい実装は全体のソースコードを参照してください

Xcode で Breakpoint を一括で削除する方法

割と知らない方が多かったのでメモ。

  1. 左のペインに Breakpoint Navigator を表示
  2. Workspace 右クリックして「Delete Breakpoints」選択
  3. これですべての Breakpoint が消える

image.png

頂いたフィードバック🙏

近況

こんにちは koogawa です。たまには近況報告なんかを書いてみます。

宮崎におります

普段からTwitterで宮崎のことをツイートをしまくってるので今さら感はありますが、「自然の多いところで子育てをしたい」という夢を叶えるために昨年、東京から宮崎に移住しました。

f:id:koogawa:20181025231134p:plain

長い電車通勤生活からも開放され、今は自転車・車中心の生活にシフトしております。

そんなわけで、現在は戸建ての物件を借りてのんびり暮らしております。騒音を気にしなくてよい、というのは良いことですね。

会社員 兼 個人事業主

今年の5月に Marimosoft という屋号を取得し、個人事業主になりました。

とは言っても完全な自由業というわけではなく、昼間は本業に集中し、夜は副業としてコードを書くというスタイルを取っています。睡眠時間を削る必要があるため、昼間は若干眠いですが、なんとか両立しています。

最近のお仕事

まだ詳しくは書けないのですが、わりと大きめのプロジェクトをお手伝いさせていただいております。自分はiOSをメインで担当しておりますが、非常に強いメンバーが集っており、刺激の多い日々を送っております💪早く公開したいな〜

副業の難しさ

会社員 兼 個人事業主として副業をやってますが、割とツラい面もあります。

日中は副業できない辛み

本業があるため、副業先で日中開催されるMTGに参加することが難しかったり、Slackに飛んできたリプライへの回答が遅れたりと、メンバーの皆さんにはいろいろと迷惑をかけてしまっていると思います🙇🏻

ただ、ちゃんとMTGの議事録は残してもらえますし、本業の休み時間を利用してSlackに返信したりもできますので、今のところなんとか続けられております。

眠み😪

帰宅後は、子供をお風呂に入れ寝かしつける、というタスクがあるため、副業を開始できるのはどうしても22時以降になってしまいます。そこから作業を始めると、眠りにつけるのはどんなに早くても午前0時過ぎになります。時には朝5時頃まで頑張ることもありました。そのため、昼間(特にランチ後)はちょっと眠くなるので、コーヒー飲んで頑張ってました☕

まとめ

いろんな課題はありつつも、意外と何とかなるもんです。しかし、最近はさすがに体力的にもキツくなってきたので、近い内に本業か副業のどちらか一本に絞りたいと考えています。

追記:その後、副業一本に絞りました。 現在はフリーな感じでやらせていただいております。めちゃめちゃ楽しいです💪

【Stack Overflow活動記】reputationが5,000に!Tag Wiki 編集の承認権限が付与されました

Stack Overflow活動中 の koogawa です、こんにちは。

昨日ついに reputation(Stack Overflowにおける信頼度)が 5,000 に到達しました🎉

f:id:koogawa:20181007020821p:plain

今年の初めに設定した目標 が「Stack Overflowで5000 reputationを目指す」だったので、無事達成できたことになります。

ちなみに、エンジニアアウトプットランキング Stargzr によると、日本で reputation 5,000 以上のユーザーは現在3人しかいないようです *1。ちなみに、1位の id:KishikawaKatsumi さんは 8,000 reputation を超えていらっしゃるので本当にすごいですね!

そして、同時に Approve Tag Wiki Edits 権限が付与されました。これは Tag Wiki の編集申請を承認できる権限になります。

Tag Wiki とは

Stack Overflow で質問する際には ios, swift, xcode などの Tag を設定することができます。

各々の Tag には専用のページが用意されており、Tag の概要文やその Tag における上位回答ユーザー、最近のベストな回答などが掲載されます。

f:id:koogawa:20181007022029p:plain

すべてのユーザーは Tag 概要文を編集することができますが、Approve Tag Wiki Edits 権限(今回私に付与された権限)を持つユーザーに承認されるまでサイトには反映されません。

Tag Wiki 編集が承認されるとどうなるか

初めて編集が承認されたユーザーは「Tag Editor」バッジ(銅)を獲得できます。

f:id:koogawa:20181007022042p:plain

さらに、50回編集が承認されると「Research Assistant」バッジ(銀)を獲得できます!

f:id:koogawa:20181007022053p:plain

次なる目標

次は 10,000 reputation で付与される Access To Moderator Tools 権限獲得を目指します💪

私の Stack Overflow 活動はこれからも続きますよ!

*1:もちろん実際はもっといるんでしょうけどね

iOSDC Japan 2018 前夜祭に参加してきたよ #iosdc

飛行機、電車を乗り継ぎ、iOSDC Japan 2018会場へ!2年ぶりの参加です。

ここは WWDC か!と思いました。

ビールもたくさんデプロイされていました。途中、ビールが足りなくなるというハプニングもありましたが、スタッフさんが近くのコンビニなどで補充してくれました。感謝しかない🙏

前夜祭では次のトークを聴講しました。

ひとつだけピックアップするとRyo Usamiさんの「標準アプリから学ぶ、HIGが教えてくれないiOSデザインのこと」がとても印象に残りました。

  • 広く利用されるものを利用する
  • 同じ見た目のものは場所が違えど同じふるまいをしよう
  • 新しい標準が登場したときはその背景、使い方を知ろう

という内容が一貫しており、みんな日常的に使っている「ドア」の例えが非常にしっくりきました。

「ドア」の使い方をあまり意識しないのは何度も使ってるからであり、「ドア」であれば同じふるまい・利用のされ方をするからである。そこに突然「同じドアなのに同じ動きをしない」ドアが現れると人は戸惑う、と。

***

ノベルティもたくさんいただきました!

今日も楽しんでいきます!