Closed14

Swift Developer中級者への道

こまきちこまきち

正直わからないことしかない初学者だけど、中級者になりたい。
わからないと思ったことを調べて、ここにまとめる。

こまきちこまきち

スレッド

ネットでよく、UIの描画や更新はメインスレッドで行わないといけないってみるけど、メインスレッドで行わなければいけない理由はなんだろう?
※スレッドについての詳しい説明なんかは、ネットを調べた方がたくさん出てくるので割愛。

iOSの並行処理やマルチスレッドの話なんかはmixiの研修資料がすごくわかりやすい。

iOS13以降、メインスレッドではなくバックグラウンドスレッドでUI変更するとアプリがクラッシュし、エラーを吐くらしい。
マルチスレッドの時は、メインスレッドにキューとして入れて対応する。この時に DispatchQueue.main.syncを使用する。
参考→swift初心者がiOS13対応でメインスレッド以外でUI更新をしてクラッシュさせてしまった話

なるほど、よくみる DispatchQueue.main.sync 等はメインスレッド使用を示すものだったのか。

こまきちこまきち

DispatchQueue.main.syncDispatchQueue.main.async

  • DispatchQueue.main.sync:同期処理
    処理の途中でこれがあると、他の処理がストップされてアプリが止まる(デッドロック)
  • DispatchQueue.main.async:非同期処理
こまきちこまきち

Observerパターン

デザインパターンの一種で、オブジェクトが状態変化した際に通知させるようにするもの。

protocol PropertyObserver : class {
    func willChange(propertyName: String, newPropertyValue: Any?)
    func didChange(propertyName: String, oldPropertyValue: Any?)
}

final class TestChambers {
    // プロトコルに準拠した変数定義
    weak var observer:PropertyObserver?

    private let testChamberNumberName = "testChamberNumber"

    // newValとoldValは予約語
    var testChamberNumber: Int = 0 {
        // protocolで定義した関数の中身を実装
        willSet(newValue) {
            observer?.willChange(propertyName: testChamberNumberName, newPropertyValue: newValue)
        }
        didSet {
            observer?.didChange(propertyName: testChamberNumberName, oldPropertyValue: oldValue)
        }
    }
}

// こっちはプロトコルの適合の例
final class Observer : PropertyObserver {
    func willChange(propertyName: String, newPropertyValue: Any?) {
        if newPropertyValue as? Int == 1 {
            print("Okay. Look. We both said a lot of things that you're going to regret.")
        }
    }

    func didChange(propertyName: String, oldPropertyValue: Any?) {
        if oldPropertyValue as? Int == 0 {
            print("Sorry about the mess. I've really let the place go since you killed me.")
        }
    }
}
使用例
var observerInstance = Observer()
var testChambers = TestChambers()
testChambers.observer = observerInstance
testChambers.testChamberNumber += 1

【参考】
https://github.com/ochococo/Design-Patterns-In-Swift#-observer

こまきちこまきち

あった

デリゲートパターンとクロージャを用いたイベント通知では、1対1しか有効ではありません。しかし、1つのイベント結果を複数のオブジェクトが知る必要がある場合があります。
オブザーバパターンはそのような1対多のイベント通知を可能にします。

オブザーバパターンは、かなり広く使える方法ですが、関係性が見えづらく可読性は他の方法よりも劣ります。

https://tech.naturalmindo.com/notwork_ios_notification/

こまきちこまきち

StoreKit

App内課金やApp Storeとやり取りするフレームワーク

【できること】

  • App内課金
  • 広告ネットワークのアトリビューション
    広告に金するAppのインストールを検証する
  • Apple Music
    ユーザーがApple Musicを利用可能かチェックし、サブスクリプションを提案する
  • おすすめとレビュー
    サードパーティのコンテンツをおすすめとして表示し、ユーザーがAppの評価とレビューを行えるようにする

https://developer.apple.com/jp/documentation/storekit/

こまきちこまきち

クロージャ

スコープ内の変数や定数を保持したひとまとまりのこと。再利用可能。
関数を定義する時にはfuncが必要だが、クロージャにはクロージャ式という定義があり、名前が不要だったり、型推論によって型の記述が省略可能だったり関数よりも軽量化して定義できる*。

書き方
処理が一行しかない場合は return の省略が可能。

{(引数) -> 戻り値の型 in
    処理
}

Closure構文をClosure型として宣言された変数に代入することも可能。

let c: (Int) -> Void

// cへclosureを代入
c = { (a: Int) -> Void in
    print(a)
}

c(10)


typealiasを用いたClosureの型宣言

複雑になりがちなClosure構文の可読性を上げるために、typealiasがよく使われる。

typealias 型の名前 = (引数の型,...) -> (返り値の型)

以下、通常のClosure文をtypealiasを使用して書き直した例

typealiasなし
// "Int型を引数としてとり、Double型"を返すClosure型を返り値として、Float型の引数aを取る関数func2の定義
let func2: (Float) -> ((Int) -> Double) = { a in
    return { (b) -> Double in
        return Double(a) + Double(b)
    }
}

// func2の使い方 //
let myClosure: (Int) -> Double = func2(10)  
let value = myClosure(20) // val = 10 + 20 が代入される
typealias使用
typealias Closure2Type = (Int) -> Double

let func2: (Float) -> Closure2Type = { a in
    return { (b) -> Double in
        return Double(a) + Double(b)
    }
}

// func2の使い方 //
let myClosure: Closure2Type = func2(10)
let value = myClosure(20) // val = 10 + 20 が代入される


Closureオブジェクトをプロパティとして持つ場合の注意点

あるクラスのプロパティとしてClosureオブジェクトを持たせる際に、Closureプロパティからselfを参照すると循環参照に陥る可能性がある。

この循環参照を避けるためには、片方の参照をstring(強参照)からweak(弱参照)に変更する。

循環参照してしまう例
class ClosureSample {
    var name: String = ""
    private(set) var printNameClosure: (() -> ())!

    init() {
        setup()
    }

    private func setup() {
        printNameClosure = {
            print("name = \(self.name)")
        }
    }
}

参考にあるmixiのこの説明&図がわかりやすい

*https://qiita.com/Howasuto/items/eba4cb4b6d3e6bde0a17

【参考】
https://docs.swift.org/swift-book/LanguageGuide/Closures.html
https://github.com/mixi-inc/iOSTraining/blob/master/Swift/pages/day3/1-1_Closure.md

こまきちこまきち

Escaping Closure

クロージャをパラメータの1つとして受け取る関数を実行する際に、@escapingをパラメータTypeの前につける。
つまりクロージャがスコープを抜けても存在し続ける時に、@escapingが必要になる。

具体例

  • クロージャがスコープ外で参照される時
  • クロージャを非同期的に実行する時

【参考】
https://qiita.com/mishimay/items/1232dbfe8208e77ed10e
https://docs.swift.org/swift-book/LanguageGuide/Closures.html#:~:text=same running total.-,Escaping Closures,-A closure is

こまきちこまきち

Optional型

Optional型とは何かは他サイトを見れば大体わかる。ここでは疑問に思ったことだけ。

as? String と as String?の違い

let any: Any = "sample" as Any

// any(not Optional)を無理やりString?(Optional)に変換しようとしているためError
let text1 = any as String?    //  'Any' is not convertible to 'String?'

// もしanyがStringに変換できなければnilが返る
let text2 = any as? String

// これもOK
let text3 = any as? String?
こまきちこまきち

コメント

通常コメント

/*
 * 通常コメント
 */
// 通常コメント

ドキュメントコメント

Quick HelpのDescription欄に出てくるようになる。Markdownも記述可能

/// ドキュメントコメント
こまきちこまきち

ライブラリ関連

  • SDWebImage:画像URLをUIImageViewで画像を管理するためのツール。キャッシュをとってこれる。
  • VerticalCardSwiper:横スワイプできるカードを作成するライブラリ
  • PKHUD:ローディングのグルグル実装
  • Alamofire:通信関係
  • DTradientButton:ボタンの背景をいい感じにしてくれる
  • ChameleonFramework/Swift:色をいい感じにしてくれる
  • AVFountdation:音を鳴らすときに必要なライブラリ

  • UITextFieldDelegate:キーボードをしまうためのもの(?)
    A set of optional methods to manage the editing and validation of text in a text field object.
こまきちこまきち

画面遷移

self.present(hoge, animated: true, completion: nil)
これがわからんと思ったけど、画面遷移する一手段だった。(?)
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-present


例) ログインしていなかったらログイン画面に飛ばす処置

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginViewController = storyboard.instantiateInitialViewController()
loginViewController.modelPresentationStyrle = .fillScreen
self.present(loginViewController, animated: true, completion: nil)
このスクラップは2022/02/15にクローズされました