20200910のiOSに関する記事は4件です。

Grand Central Dispatch (GCD) の理解を深める

GCD とは?

macOS、iOS、watchOS、tvOS のマルチコアハードウェアでの並列実行タスクをするために提供されている言語機能で、開発者は DispatchQueue を使用してスレッドでの処理を管理することができます。また、GCD の Queue は追加されたタスクの順番ごとに処理を実行する FIFO(First In First Out) の性質を持っています。

一旦用語をまとめる

Process(プロセス)

アプリケーションの実行インスタンスであり、専用の仮想メモリ空間およびシステムのリソースが割り当てられる。また、プロセス内には複数のスレッドが含まれる。

Thread(スレッド)

プロセスが実行する処理などを格納した箱のようなもの。

Task(タスク)

スレッドに格納される具体的な処理の単位。

Queue(キュー)

アプリケーションに含まれるスレッドを管理するための高レベルな API で、Thread の作成・管理の役割を担っている。また、実際に処理される時にはシステムが効率的なタスクの実行を行うことで、キューはスレッドの管理に集中することができる。

Queue(キュー)の種類

言語で提供されているキューは主に3つあり、それぞれスレッドの管理範囲が異なります。

Main

DispatchQueue.main でアクセスできるキューで、スレッドはシステムから提供されています(メインスレッド)。また処理の実行は直列で行われていて、原則として UI の更新などはこのスレッドで行う必要があります。

    DispatchQueue.main.async {
        // 処理
    }

Global

DispatchQueue.global(qos:) でアクセスできるキューで、引数に DispatchQoS.QoSClass を指定してタスクを実行するスレッドを指定します。また、システムのタスクの優先度には highdefaultlowbackground の4つがあり、DispatchQoS.QoSClass の値はシステムが効率的にタスクを行う際の優先順位を決める判断材料としても使用されます。基本的には、開発者はどれが最も優先度が高いなどと考える必要がなく、それぞれのタスク内容に応じて、適切な DispatchQoS.QoSClass を指定する必要があります。それぞれのタイプの詳細については公式のドキュメントを参照するといいでしょう。また、このキューはシステムで共有され、それぞれ並列で処理の実行が行われます。

    DispatchQueue.global(qos: .background).async {
        // 処理
    }

Custom

DispatchQueue(label: "hoge") のように label 引数に任意の値を追加することで、独自のスレッドにタスクを追加することができます。また、直列また並列でのタスクの実行を選択することができます。基本的には attributes 引数で .concurrent を指定すると並列で実行され、指定がない場合は(Swift2以前は .serial で指定できたっぽい)、直列での実行になります。

    DispatchQueue(label: "hoge.hoge.hoge").async {
        // 処理
    }

同期・非同期

先で説明した、どのタイプのキューも 同期非同期 での実行を選択することができ、それぞれ sync(同期)async(非同期) ないで処理を書くことができます。また具体的な挙動としては下記のようになります。

  • 同期処理
    • スレッドにタスクを追加した時に、呼び出し元のスレッドはそのタスクの完了をまってから次の処理を行います。
  • 非同期処理
    • スレッドにタスクを追加した時に、呼び出し元のスレッドはそのタスクの完了を待たずに次の処理を行います。

メインスレッド内でバックグランドスレッドにタスクを追加して、同期非同期 でそれぞれ実行したサンプルが下記のようになります。

    // 同期処理
    print("a")
    DispatchQueue.global(qos: .background).sync {
        print("b")
    }
    print("c")

    // 結果
    // a
    // b
    // c

    // 非同期処理
    print("a")
    DispatchQueue.global(qos: .background).async {
        print("b")
    }
    print("c")

    // 結果
    // a
    // c
    // b

これらは、キューの種類の説明でも出てきた 直列並列 の言葉と混合しがちなので、スレッド間の実行順序に使われるのが 同期非同期 で、スレッド内の処理の実行順序に使用されるのが 直列並列 という認識で差し支えないかと思われます。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Maps で吹き出しが表示されない

要件

Google Maps 上のマーカーをタップした際に吹き出しを表示してください。

Google さんの教え

それ info window でできるよ。
https://developers.google.com/maps/documentation/ios-sdk/marker#add_an_info_window

title or snippet のどちらかを設定すれば、マーカーをタップした際に吹き出し( info window )が表示されるようです。
どちらも null もしくは空文字の場合は吹き出しは表示されません。

let position = CLLocationCoordinate2D(latitude: 51.5, longitude: -0.127)
let marker = GMSMarker(position: position)
marker.title = "London"
marker.snippet = "Population: 8,174,100"
marker.map = mapView

やってみた

やってみました、表示されませんでした。

タップが無視されているような感じだったので isUserInteractionEnabled を一通り確認しましたが、全て true でした。

mapView: didTapMarker

怪しげな delegate メソッドが実装されていました。

func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    print(marker)
    return true 
}

true を返す場合はこのメソッド内でタップイベントを処理し、 false を返す場合はイベントを流すようです。
吹き出しを表示する場合はタップイベントを流して、 GMSMarker のデフォルトのタップ時の処理( info window )に任せる必要がありそうです。

ということで、

func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
    print(marker)
    return false
}

やってみました、表示されませんでした。

ログを確認したところ print(marker) が出力されていませんでした。
ブレークポイントを貼ってみてもこのメソッドは呼び出されていないようです。
つまり info window の話以前にマーカーがタップイベントを取得できていないという事です。

ちなみに mapView.delegate = self は適切に設定されています。
他の delegate メソッドが呼び出されることは確認しました。

clusterManager: didTapCluster

怪しげな delegate メソッドが実装されていました。

func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
    print(cluster)
    return true
}

Cluster とは複数のマーカーがマップ上の近い位置に存在するときに、 1 箇所に纏めて 1 つのアイコンとして表示する機能です。
Google Maps SDK for iOS Utility Library という Google Maps SDK の機能を拡張するためのライブラリに含まれる機能です。

ということで、

func clusterManager(_ clusterManager: GMUClusterManager, didTap cluster: GMUCluster) -> Bool {
    print(cluster)
    return false
}

やってみました、表示されました

今回の犯人はこいつでした。
Cluster のアイコンがタップイベントを処理しており、 Marker にイベントが流れてこない状態となっていました。

まなび

基本的には Google さんの教えに従えば吹き出しは表示されます。
今回はイレギュラーなケースでしたが、その際の着眼点を記事に残しておきたいと思いました。

タップが無視されるという現象はアプリ開発をしていると割と頻繁に遭遇します。
これまでの経験では

  • isUserInteractionEnabled
  • UIButton の isEnabled
  • フォーカスの位置

辺りを見ていると大方解決できていました。
delegate の処理でタップイベントを 流す / 流さない を管理するパターンは初めてだったので知見が広がりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

勢いでMacbook買ってから初めてのiPhoneアプリをリリースするまでのメモ

はじめに

3月にMacbookProを購入し、独学でプログラミング(Swift)を学習し、8月に初めてのiOSアプリをリリースしました!
今回はアプリリリースまでにどうプログラミングを勉強したのか、どういう教材でアプリ開発を学んだのかを紹介します!

リリースしたアプリ

思いっきり宣伝ですがこちらからダウンロードできます↓
https://apps.apple.com/jp/app/prophecy-%E4%BA%88%E8%A8%80%E3%83%9E%E3%82%B8%E3%83%83%E3%82%AF/id1528918453

記念すべき初のアプリは【手品アプリ】です!

image.png

iPhoneでお客さんが選んだカードを当てる簡単なマジックです!
実演動画はYouTubeに載せています↓
https://www.youtube.com/watch?v=yabrcQwxoSY

リリースに向けてSwiftの勉強

まず私のMacbook購入時点のプログラミングスキルですが
・プログラミングの基本文法はわかる。(変数、メソッド、条件分岐、繰り返しなど)
・Java、Javascriptを少し触れたことがある。
・Swiftは一度も触ったことがない。

なので、完璧に初心者というわけではないですが、
Progate(無料のプログラミン技学習サイト)で基本文法を勉強したくらいのレベルだと思います。

以下、私が学習する際に用いた教材を紹介します。

①絶対に挫折しない iPhoneアプリ開発「超」入門 第8版 【Xcode 11 & iOS 13】 完全対応

image.png

こちらの本ではiPhoneアプリの開発に必要なXcodeという開発ツールのインストール方法からSwiftの基本文法、いくつかのサンプルアプリの作成手順などが画像付きで載っており、初心者にはオススメです!

ちなみに私がこの本を買った当時は第7版だったので、本の中の画像が一部古かったりもしましたが、
2020年6月に最新の第8版が出たので、今時点では画像に差異はほとんどないかと思われます。

私はこの本を1ヶ月ほどかけて読み終えましたが、本の内容で一部理解が曖昧だったところ(デリゲート、クロージャなど)があったため、別の教材を手に取りました。

②【iOS13対応】未経験者がiPhoneアプリ開発者になるための全て iOS Boot Camp

こちらは動画学習サイトUdemyの教材となります。
https://www.udemy.com/course/ios13_swift5_iphone_ios_boot_camp/

こちらの動画では30個近くのサンプルアプリの作成手順が動画で解説されており、サンプルアプリ開発を通してSwiftの様々な機能について学べます。
Swiftの基本文法はもちろん、GoogleAdmobの広告の付け方までかなり幅広く解説されています。

しかも、動画内でわからないところがあればコメントにて質問することができます。

動画時間は全て合計すると約30時間ほどあるので、毎日1〜2時間視聴したとしても1ヶ月ほどかかります。
私は普段仕事もあったので、週5日1〜2時間ほどのペースで進めており、1.5ヶ月ほどかかりました。
本である程度理解できているところは1.5倍速で見ていました。

①の本で分からなかったところは動画で理解することができ、動画で分からなかったところは本を読み返して理解することができたので、教材は2つ以上あると勉強しやすいのかなと思いました。

その他

分からないところは随時検索したり、QiitaやYouTubeを見たりしていました。
特にAppleにアプリの申請を行うところなどはたくさん検索しました。
先人様の知恵に感謝です。

いざオリジナルアプリ開発へ!

アプリの構想自体は②の動画教材を勉強しながらぼんやりと考え始めていました。

構想時点ではあれもこれもと色々機能をつけがちですが、
初心者にとって一番大切なのは【シンプルでもいいからとりあえず物を完成させる】ことです。
いきなり自動車を作るのではなく、まずは一輪車を作るようにした方がいいです。

初めからあれもこれもと色々な機能を付けようとすると、思っている以上にうまく動作しなくなります。
そうなってくると開発のモチベーションが下がり挫折してしまいます。
私も最初は多機能アプリの構想を練っていたので、かなり大変でした。。。

なので、今回は画面数も極力減らし、かなりシンプルな画面構成にしてみました。
image.png

結果的にバグもあまり発生せず、かなりスムーズに開発を進めることができました!

おわりに

今回はアプリリリースまでにどうプログラミングを勉強したのか、どういう教材でアプリ開発を学んだのかを紹介してみました!
リリースしたアプリは正直かなりシンプル構造です。誰でも作れますレベルです。

が、やはり自分で作ったアプリがAppStoreに表示されるともうそれだけで嬉しくなります…!!!

これからiPhoneアプリを作ってみたいという方にぜひ参考になればと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アプリで取り扱っている画像(UIImage)を端末に保存する

UIImageViewに表示したりするのに使っている画像(UIImage)を端末のアルバムに保存したかったのでやり方を調べました。

まず、Info.plistの<dict>の中にNSPhotoLibraryAddUsageDescriptionがあるか確認します。なかったら以下のように追加します。パーミッション要求時の文言は、アプリの内容に応じて指定します。

<key>NSPhotoLibraryAddUsageDescription</key>
<string>パーミッション要求時の文言</string>

そしたら、画像を保存しようとしているところで以下のような処理を実行します。

func save(image: UIImage) {
    // パーミッションを確認
    PHPhotoLibrary.requestAuthorization { status in
        guard status == .authorized else {
            // 権限がない
            return
        }

        // 保存処理
        PHPhotoLibrary.shared().performChanges(
            {
                // このブロック内でPHAssetCreationRequestを作成すると保存される
                PHAssetCreationRequest.creationRequestForAsset(from: image)
            }, completionHandler: { success, error in
                if let error = error {
                    // エラー処理
                    return
                } else if success {
                    // 保存完了
                } else {
                    // ここに来ることがあるのかどうか、ちょっと調べた限りわからない…
                }
            })
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む