20210213のiOSに関する記事は6件です。

SectionModelType.ItemにEnumを用いたUITableView+RxDataSourcesの実装サンプル

環境

  • Xcode 12.4
  • Swift 5.3.2
  • RxSwift 6.0.0
  • RxCocoa 6.0.0
  • RxDataSources 5.0.0

前置き

RxSwiftRxCocoaに触れたことがあり、
RxDataSourcesをこれから利用するかもしれない方に向けて書いています。

自分にとってRxSwift/RxCocoa同様、学習コストが高く苦労した覚えがあったので、
サンプルが一つでも多くあるといいなと思い、今回記事にしました。

RxSwiftとRxCocoaの記述についてはあまり触れないのでご了承ください:pray:

実装内容

SectionModelTypetypealias ItemにはEnum(Associated value)を利用しています。
表示するデータはViewModelから適当に流すようにしています。
リロードする度に新しいセクション情報を構成して表示する仕様になっています。

iPhone8(iOS14.4)

実装サンプルコード

ViewController.swift
class ViewController: UIViewController {
    private let viewModel = ViewModel()
    private let tableView = UITableView()
    private let refreshControl = UIRefreshControl()
    private let refreshRelay = PublishRelay<Void>()
    private let disposeBag = DisposeBag()

    // ジェネリッククラスのRxTableViewSectionedReloadDataSourceの型パラメータには
    // SectionModelTypeを適用したクラスを指定する
    private let dataSource = RxTableViewSectionedReloadDataSource<SectionModel>(
        // tableView(_:cellForRowAt:)で行っている処理
        configureCell: { _, tableView, indexPath, item in
            switch item {
            case .stringSection(let text):
                guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else { fatalError() }
                cell.textLabel?.text = text.rawValue
                return cell
            case .intSection(let number):
                guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else { fatalError() }
                cell.textLabel?.text = "\(number.rawValue)"
                return cell
            }
        },
        // tableView(_:titleForHeaderInSection:)で行っている処理
        titleForHeaderInSection: { dataSource, section in
            dataSource.sectionModels[section].title
        }
    )

    override func loadView() {
        super.loadView()
        view.backgroundColor = .systemBackground   

        refreshControl.addTarget(self, action: #selector(reloadTableView), for: .valueChanged)

        tableView.refreshControl = refreshControl
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.tableFooterView = UIView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        ])
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let viewDidLoadRelay = PublishSubject<Void>()

        viewModel
            .observeSectionInfo(viewDidLoad: viewDidLoadRelay,
                                refresh: refreshRelay)
            .drive(tableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)

        viewDidLoadRelay.onNext(())
    }

    @objc private func reloadTableView() {
        refreshRelay.onNext(())
        refreshControl.endRefreshing()
    }
}
ViewModel.swift
// セクション情報
enum SectionItem {
    case stringSection(StringSectionRowItem) // ①
    case intSection(IntSectionRowItem) // ②
}
// ①の要素(row)
enum StringSectionRowItem: String {
    case first
    case second
    case third
}
// ②の要素(row)
enum IntSectionRowItem: Int {
    case first = 1
    case second = 2
    case third = 3
}
// SectionModelTypeを適用した構造体を定義する。
// sectionのtitle用のプロパティとinitを追加しています。
struct SectionModel: SectionModelType {
    typealias Item = SectionItem
    var items: [SectionItem]
    // 追加したプロパティ
    var title: String

    init(original: SectionModel, items: [Item]) {
        self = original
        self.items = items
    }
    // 追加したinitializer
    init(title: String, items: [Item]) {
        self.title = title
        self.items = items
    }
}
// MARK: - ViewModel
struct ViewModel {
    func observeSectionInfo(viewDidLoad: Observable<Void>,
                            refresh: Observable<Void>) -> Driver<[SectionModel]> {
        Observable
            .merge(viewDidLoad, refresh)
            .map {
                // section, rowの並びをシャッフルして返す
                [
                    SectionModel(title: "String Section",
                                 items: [.stringSection(.first),
                                         .stringSection(.second),
                                         .stringSection(.third)].shuffled()),
                    SectionModel(title: "Int Section",
                                 items: [.intSection(.first),
                                         .intSection(.second),
                                         .intSection(.third)].shuffled())
                ]
                .shuffled()
            }
            .asDriver(onErrorRecover: { _ in fatalError() })
    }
}

終わりに

慣れるまで時間はかかりましたが、DelegateとDataSourceのメソッドをRxでまとめられるので便利だなと思っています(小並感)

section, rowがそこまで複雑ではない場合は利用しなくても良さそうです。

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

Enumを用いたUITableView+RxDataSourcesの実装サンプル

環境

  • Xcode 12.4
  • Swift 5.3.2
  • RxSwift 6.0.0
  • RxCocoa 6.0.0
  • RxDataSources 5.0.0

前置き

RxSwiftRxCocoaに触れたことがあり、
RxDataSourcesをこれから利用するかもしれない方に向けて書いています。

自分にとってRxSwift/RxCocoa同様、学習コストが高く苦労した覚えがあったので、
サンプルが一つでも多くあるといいなと思い、今回記事にしました。

RxSwiftとRxCocoaの記述についてはあまり触れないのでご了承ください:pray:

実装内容

SectionModelTypetypealias ItemにはEnum(Associated value)を利用しています。
表示するデータはViewModelから適当に流すようにしています。
リロードする度に新しいセクション情報を構成して表示する仕様になっています。

iPhone8(iOS14.4)

実装サンプルコード

ViewController.swift
class ViewController: UIViewController {
    private let viewModel = ViewModel()
    private let tableView = UITableView()
    private let refreshControl = UIRefreshControl()
    private let refreshRelay = PublishRelay<Void>()
    private let disposeBag = DisposeBag()

    // ジェネリッククラスのRxTableViewSectionedReloadDataSourceの型パラメータには
    // SectionModelTypeを適用したクラスを指定する
    private let dataSource = RxTableViewSectionedReloadDataSource<SectionModel>(
        // tableView(_:cellForRowAt:)で行っている処理
        configureCell: { _, tableView, indexPath, item in
            switch item {
            case .stringSection(let text):
                guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else { fatalError() }
                cell.textLabel?.text = text.rawValue
                return cell
            case .intSection(let number):
                guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else { fatalError() }
                cell.textLabel?.text = "\(number.rawValue)"
                return cell
            }
        },
        // tableView(_:titleForHeaderInSection:)で行っている処理
        titleForHeaderInSection: { dataSource, section in
            dataSource.sectionModels[section].title
        }
    )

    override func loadView() {
        super.loadView()
        view.backgroundColor = .systemBackground   

        refreshControl.addTarget(self, action: #selector(reloadTableView), for: .valueChanged)

        tableView.refreshControl = refreshControl
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.tableFooterView = UIView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        NSLayoutConstraint.activate([
            tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        ])
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let viewDidLoadRelay = PublishSubject<Void>()

        viewModel
            .observeSectionInfo(viewDidLoad: viewDidLoadRelay,
                                refresh: refreshRelay)
            .drive(tableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)

        viewDidLoadRelay.onNext(())
    }

    @objc private func reloadTableView() {
        refreshRelay.onNext(())
        refreshControl.endRefreshing()
    }
}
ViewModel.swift
// セクション情報
enum SectionItem {
    case stringSection(StringSectionRowItem) // ①
    case intSection(IntSectionRowItem) // ②
}
// ①の要素(row)
enum StringSectionRowItem: String {
    case first
    case second
    case third
}
// ②の要素(row)
enum IntSectionRowItem: Int {
    case first = 1
    case second = 2
    case third = 3
}
// SectionModelTypeを適用した構造体を定義する。
// sectionのtitle用のプロパティとinitを追加しています。
struct SectionModel: SectionModelType {
    typealias Item = SectionItem
    var items: [SectionItem]
    // 追加したプロパティ
    var title: String

    init(original: SectionModel, items: [Item]) {
        self = original
        self.items = items
    }
    // 追加したinitializer
    init(title: String, items: [Item]) {
        self.title = title
        self.items = items
    }
}
// MARK: - ViewModel
struct ViewModel {
    func observeSectionInfo(viewDidLoad: Observable<Void>,
                            refresh: Observable<Void>) -> Driver<[SectionModel]> {
        Observable
            .merge(viewDidLoad, refresh)
            .map {
                // section, rowの並びをシャッフルして返す
                [
                    SectionModel(title: "String Section",
                                 items: [.stringSection(.first),
                                         .stringSection(.second),
                                         .stringSection(.third)].shuffled()),
                    SectionModel(title: "Int Section",
                                 items: [.intSection(.first),
                                         .intSection(.second),
                                         .intSection(.third)].shuffled())
                ]
                .shuffled()
            }
            .asDriver(onErrorRecover: { _ in fatalError() })
    }
}

終わりに

慣れるまで時間はかかりましたが、DelegateとDataSourceのメソッドをRxでまとめられるので便利だなと思っています(小並感)

section, rowがそこまで複雑ではない場合は利用しなくても良さそうです。

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

Swiftのオプショナル型について

オプショナル型とは

オプショナル型とはデータ型の一つで、変数の宣言時に使用します。
特徴としては変数にnilの代入を許可することで、逆に非オプショナル型はnilの代入を許可しないことになります。

nilとはデータが無い(変数が空の状態)を表します。

iOSではnilに対して操作するとアプリケーションが落ちてしまうことがあり、そのような問題を解決するためにswiftではnilを基本的には許容しないとのことです。しかし、オプショナル型を使うことでnilを扱うことができるようになります。

オプショナル型の使い方

// オプショナル型
var age: Int? 
var name: String!

// 非オプショナル型
var age: Int 
var name: String

オプショナル型は最後にをつけます。非オプショナル型は最後に何もつけません。
また、一般的にオプショナル型 ( Optional Value ) というとは暗黙的アンラップ型 ( Implicitly Unwrapped Optional )と呼ばれるオプショナル型になります。

非オプショナル型の変数は普通に扱う変数で、nilを扱うことのできるオプショナル型の変数は通常とは扱い方が少し異なります。オプショナル型の変数を通常の変数と同様に扱おうとするとおもわぬエラーが起きるため、オプショナル型の値を通常の値に変換するアンラップという方法が必要になります。

暗黙的アンラップ型

一般的なオプショナル型と違い、使用するとき必ず強制的にアンラップされます。利点として、初期値はないが後から必ず値を入れる保証があるものに使うと効果を発揮します。

なぜアンラップが必要なのか?

まずは、オプショナル型と非オプショナル型の出力結果の違いを見ていきます。

// オプショナル型
var name: String?
name = "Taro"
print(name)  // 出力結果 Optional("Taro")

// 非オプショナル型
var name: String
name = "Taro"
print(name)  // 出力結果 Taro

上記の通り、オプショナル型は Optional("Taro") 、非オプショナル型は Taro と出力されていることから大きな違いがあるとわかります。

また、「Optional(値)」「値」は異なるもので、同様には扱うことができません。

var a: Int? = 10 // オプショナル型 
var b: Int = 10 // 非オプショナル型
a + b
/* 
-> a + b はエラーとなる
Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
オプションタイプ「Int?」の値 タイプ「Int」の値にアンラップする必要があります。
*/

結果はエラーとなり実行することができません。
なぜなら、変数aOptional(10)変数b10 という値であるため、Optional(10) + 10は計算することができないということになります。

ここで、オプショナル型の値を通常の値 ( Optiona(10)10 ) に変換するためにアンラップという処理が必要ということになります。

アンラップとは

Optionalは値を包み込むラップのようなイメージで、オプショナル型は値をOptionalというラップで包み込んでいます。そうすると、たとえ中身がない ( nil ) 状態でも包み紙だけは存在するため、扱うことができます。

しかし、この便利な包み紙があるために値はラッピングされ、直接扱うことができません。扱うためにはラップを取り除き、中身の値を取り出さなくてはいけません。この包み紙を取り除き、値を取り出すことをアンラップと言います。

アンラップの種類

1. 強制的アンラップ

そのままの意味になりますが、オプショナル型を強制的にアンラップする方法です。オプショナル型の変数の中にどんな値が入っていても関係なく、その値を取り出します。

var name: String?
name = "Taro"
print(name!)  // 出力結果 Taro

オプショナル型の変数に対して!をつけ強制的アンラップをすると、出力がTaroとなりました。

簡単に扱える強制的アンラップですが、アンラップする対象のオプショナル型の変数の中身がnilだった場合、エラーとなりアプリケーションが落ちてしまうという欠点があります。なので、強制的アンラップをする場合は、必ずラッピングの中身が存在する(nilではない)ことが保証されていなければいけません。

2. オプショナルバインディング

強制的アンラップはオプショナル型の変数を中身の値に関係なくアンラップしました。そのため変数にnilが入ってしまうとアプリケーションが落ちてしまうという問題があり、それを解決するアンラップの方法がオプショナルバインディングです。

オプショナルバインディングの特徴は条件式を用いることで、アンラップする対象のオプショナル型の変数がnilかどうかを判定するためのものです。

if let

  • nilだった場合に行う処理が異なる時に使用します。
  • nilの場合はAの処理nilではない場合はBの処理をするケースでよく使います。
let name: String? = "Taro"

if let unwrappedName = name {
    //「name」が「nil」ではない場合の処理
    print(unwrappedName)
} else {
    //「name」が「nil」である場合の処理
    print("名前がありません") 
}

// -> 出力結果 Taro

guard let

  • これ以上処理を進めたくない場合に使用します。
  • nilが入っていたらエラーとして扱うケースだった場合などによく使います。
let name: String? = "Taro"

guard let unwrappedName = name else { return }
//「name」が「nil」ではない場合の処理を進める
print(unwrappedName)

// -> 出力結果 Taro

3. オプショナルチェイニング

オプショナルチェイニングもオプショナル型の変数の中身がnilのときに安全にプログラムを実行するための機能です。
オプショナルチェイニングの使い方はオプショナル型の変数のあとにをつけます。

オプショナル型の変数?.メソッド()
オプショナル型の変数?.プロパティ

変数!で強制的にアンラップする場合と、変数?でオプショナルチェイニングを使用する場合で比較してみます。

変数!で強制的にアンラップする場合

var str1: String? = "HELLO"
let str2 = str1!.lowercased() 
print(str2) // 出力結果 hello

結果としてhelloが表示されます。ただし、変数str1nilの場合にはエラーとなり、プログラムが落ちます。

変数?で強制的にアンラップする場合

// 変数str1が「nil」でない場合
var str1: String? = "HELLO"
let str2 = str1?.lowercased() 
print(str2) // 出力結果 Optional("hello")

// 変数str1が「nil」である場合
var str1: String? = nil
let str2 = str1?.lowercased() 
print(str2) // 出力結果 nil

この場合、変数str2の値はオプショナル型になり、出力結果はOptional("hello")と表示されます。また、変数str1nilの場合にもエラーとならず、結果はnilとなります。オプショナルチェイニングの結果はオプショナル型となるため、オプショナルバインディングと組み合わせて使用されます。

オプショナルバインディングと組み合わせた使用

var str1: String? = "HELLO"
if let str2 = str1?.lowercased() {
    print(str2)
} else {
    print("str1はnilです!")
}

// -> 出力結果 hello

??演算子nilであった場合のデフォルト値を設定する

オプショナル型の変数 ?? デフォルト値

??演算子は、左辺のオプショナル型の変数がnilでない場合にはその値を戻し、nilの場合には右辺のデフォルト値を戻します。

var name: String?
let defaultName = "Taro"
let yourName = name ?? defaultName
print(yourName) // 出力結果 Taro

まとめ

オプショナル型についてまとめてみました。私の意見にはなりますが、強制的アンラップはアプリのクラッシュの要因の一つになると思いましたので、基本的には条件分岐が可能if letを用いてアンラップし、エラー処理の場合は以降の処理が実行されないguard letを用いてアンラップする考え方で行こうと思いました。

参考

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

CollectionViewで指定のカラム数のレイアウトを簡単に作成するためのクラス拡張

説明

UICollectionViewで毎回画面サイズや余白の計算をしてレイアウトを組むのが面倒なのでExtension化してみました。
カラム数、セル間と外側の余白、セルの縦横比を指定したレイアウトが一行で組めます。

コード

import UIKit

extension UICollectionView {

    /// 指定のカラム数のレイアウトを作成する
    /// - Parameters:
    ///   - column: カラム数
    ///   - interMargin: セル間の余白。初期値0。
    ///   - outerMargin: セル全体に付与する上下左右の余白。初期値0。
    ///   - aspectRatio: 横を1としたときの縦の比率。初期値1。
    func makeColumnLayout(column: Int, interMargin: CGFloat = 0,
                          outerMargin: CGFloat = 0, aspectRatio: CGFloat = 1) {
        let layout = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = interMargin
        layout.minimumLineSpacing = interMargin
        layout.sectionInset = UIEdgeInsets(top: outerMargin, left: outerMargin,
                                           bottom: outerMargin, right: outerMargin)
        let intervalCount = CGFloat(column) - 1
        let totalInterMargin = interMargin * intervalCount
        let totalOuterMargin = outerMargin * 2
        let baseCellSize = (bounds.width - totalInterMargin - totalOuterMargin) / CGFloat(column)
        layout.itemSize = CGSize(width: baseCellSize,
                                 height: baseCellSize * aspectRatio)
        collectionViewLayout = layout
    }
}

使い方

layoutSubviewsとかで適当に呼ぶ。

final class CollectionViewSample: UIView {

    @IBOutlet weak private var collectionView: UICollectionView!

    override func layoutSubviews() {
        super.layoutSubviews()
        collectionView.makeColumnLayout(column: 2, interMargin: 4, outerMargin: 4, aspectRatio: 1.4)
    }
}

使用例

カラム:2, セル間:4, 外側:4, 縦横比:1.2 カラム:3, セル間:8, 外側:8, 縦横比:1.6
2cv.png 3cvn.png

参考

画像引用元: PAKUTASO

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

iOSにおけるマルチスレッドを実装して確認してみる

iOSにおけるマルチスレッドの概念について、
理解を深めるために実際に動きで確認できるよう実装してみました。

マルチスレッドについて

  • マルチスレッドとは並行処理のことである
  • スレッドには、メインスレッドとバックグラウンドスレッドの二種類がある
  • メインスレッドはUI更新時に、バックグラウンドスレッドは通信などUI以外の処理に使用される

iOSでは、主にGCD(Grand Central Dispatch)を用いて、タスクをクロージャで渡す。

なぜマルチスレッドが必要か?
  • 通信処理時には待ち時間が発生する
  • この処理をメインスレッドで行うと、画面描画やユーザーからのアクションにも待ちが生じる
  • iPhone自体の処理能力は非常に高いので、CPUを有効活用する必要がある
GCD を用いた並行処理
  • メインスレッド
DispatchQueue.main.async {
// UI更新処理
}
  • バックグランド
DispatchQueue.global.async {
// バックグランド処理
}

以下で、Web API のリクエストを行い、結果をUIに表示させるだけの簡単な実装を行います。

viewdidloadで呼ばれた"ロード中"の表示は、メインスレッドで呼ばれます。
その時、非同期のバックグラウンドスレッドでは通信が行われています。
そして通信が終了したところで、レスポンス結果を受け取ります。それをUIに反映させるのはメインスレッドで行うため、DispatchQueue.main.asyncで記述します。

ドキュメントの中に、

To browse different pages, or change the number of items per page (up to 100), use the page and per_page query string parameters:
(別のページを参照したり、1ページあたりのアイテム数を変更したりするには(最大100個まで)、pageとper_pageクエリ文字列パラメータを使用します。)

とあるので、例として以下の様にURLを組み立てます。

let page = "2"
let per_page = "5"

let urlString = "https://api.discogs.com/artists/1/releases?page=\(page)&per_page=\(per_page)"

これを使って、ViewControllerだけでマルチスレッドの処理を確認します。

class ViewController: UIViewController {

    var label: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()

        label = UITextView()
        label.text = "ロード中"
        label.frame = CGRect(x: 10, y: 30, width: self.view.frame.width - 20, height: 300)
        self.view.addSubview(label)

        let page = "2"
        let per_page = "5"

        let urlString = "https://api.discogs.com/artists/1/releases?page=\(page)&per_page=\(per_page)"

        let url = URL(string: urlString)!
        var request = URLRequest(url: url)
        request.httpMethod = "GET"

        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            guard let data = data else { return }

            do {
                let object = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                DispatchQueue.main.async { // UI更新の処理
                    self.label.text = object?.description
                    self.label.sizeToFit()
                }
            } catch let e {
                print(e)
            }
        }
        task.resume()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

繰り返しになりますが、上記のHTTP通信処理はバックグラウンドスレッドで行われています。
一方で、UI更新の DispatchQueue.main.async の処理は、メインスレッドで行われます。

イメージ 5.GIF

"ロード中"が表示され、その後通信結果が表示されることが確認できました。

参考文献

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

iOSにおけるマルチスレッドを実装で確認してみる

iOSにおけるマルチスレッドの概念について、
理解を深めるために実際に動きで確認できるよう実装してみました。

マルチスレッドについて

  • マルチスレッドとは並行処理のことである
  • スレッドには、メインスレッドとバックグラウンドスレッドの二種類がある
  • メインスレッドはUI更新時に、バックグラウンドスレッドは通信などUI以外の処理に使用される

iOSでは、主にGCD(Grand Central Dispatch)を用いて、タスクをクロージャで渡す。

なぜマルチスレッドが必要か?
  • 通信処理時には待ち時間が発生する
  • この処理をメインスレッドで行うと、画面描画やユーザーからのアクションにも待ちが生じる
  • iPhone自体の処理能力は非常に高いので、CPUを有効活用する必要がある
GCD を用いた並行処理
  • メインスレッド
DispatchQueue.main.async {
// UI更新処理
}
  • バックグランド
DispatchQueue.global.async {
// バックグランド処理
}

以下で、Web API のリクエストを行い、結果をUIに表示させるだけの簡単な実装を行います。
通信中にメインスレッドでUIを更新し、"ロード中"と表示させ、
通信が終了したところで、レスポンス結果を反映させることで、
マルチスレッドの動きを目視確認してみます。

ドキュメントの中に、

To browse different pages, or change the number of items per page (up to 100), use the page and per_page query string parameters:
(別のページを参照したり、1ページあたりのアイテム数を変更したりするには(最大100個まで)、pageとper_pageクエリ文字列パラメータを使用します。)

とあるので、例として以下の様にURLを組み立てます。

let page = "2"
let per_page = "5"

let urlString = "https://api.discogs.com/artists/1/releases?page=\(page)&per_page=\(per_page)"

これを使って、ViewControllerだけでマルチスレッドの処理を確認します。

class ViewController: UIViewController {

    var label: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()

        label = UITextView()
        label.text = "ロード中"
        label.frame = CGRect(x: 10, y: 30, width: self.view.frame.width - 20, height: 300)
        self.view.addSubview(label)

        let page = "2"
        let per_page = "5"

        let urlString = "https://api.discogs.com/artists/1/releases?page=\(page)&per_page=\(per_page)"

        let url = URL(string: urlString)!
        var request = URLRequest(url: url)
        request.httpMethod = "GET"

        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            guard let data = data else { return }

            do {
                let object = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                DispatchQueue.main.async { // UI更新の処理
                    self.label.text = object?.description
                    self.label.sizeToFit()
                }
            } catch let e {
                print(e)
            }
        }
        task.resume()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

上記のHTTP通信処理はバックグラウンドスレッドで行われています。
一方で、UI更新の DispatchQueue.main.async の処理は、メインスレッドで行われます。

イメージ 5.GIF

"ロード中"が表示され、その後通信結果が表示されることが確認できました。

参考文献

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