20190307のSwiftに関する記事は4件です。

【SwiftChaining】イベントを送受信するプロトコル

SwiftChainingの解説記事その6です。

SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。

今までの記事ではSwiftChainingで用意されたクラスを使用して、バインディングする方法などを紹介してきました。

これまでそれら用意されたクラスに関して「監視対象」とか「値を返せる・返せない」といった表現で説明をしていたのですが、実際のところは何のプロトコルに適合しているかという話になります。今回はそのプロトコルについて解説します。

プロトコル

クラスをSwiftChainingでイベントの送受信などができるようにするために用意しているプロトコルがあります。以下の3つです。

  • Sendable
  • Fetchable
  • Receivable

独自に作ったクラスでも、これらのプロトコルに適合させることでSwiftChainingで同じように扱うことができます。

Sendable

Sendableに適合したクラスは、監視対象となりイベントの送信ができます。

定義は以下のような感じです。

public protocol Sendable: AnySendable {
    // 送信するイベントの型
    associatedtype SendValue
    // chain()で返す型(あまり意識しなくて良い)
    typealias SenderChain = Chain<SendValue, SendValue, Self>
}

extension Sendable {
    // イベントを送信する(実装済。そのまま使う)
    public func broadcast(value: SendValue) { ... }
    // イベントの処理を構築する(実装済。そのまま使う)
    public func chain() -> SenderChain { ... }
}

実装例として、Intを送ることに限定したNotifierを書いてみると、このようになります。

class IntNotifier: Sendable {
    typealias SendValue = Int

    func notify(value: Int) {
        self.broadcast(value: value)
    }
}

イベントとして送る値をSendValueで定義し、イベントを送信するときはbroadcast(value:)を呼びます。必要な実装はこれだけです。

このIntNotifierは、以下のように使うことができます。

let notifier = IntNotifier()

// chain()を呼べるので、イベントが送信された時に実行される処理を構築できる
let observer = notifier.chain().do { print($0) }.end()

// 値が送信されてdoが実行され、1がログに出力される
notifier.notify(value: 1)

Fetchable

Fetchableに適合したクラスは、Sendableのイベントを送信する機能に加え、sync()が呼ばれた時に値を引き出すことができます。

定義は以下のようになっています。

public protocol Fetchable: Sendable {
    // 値を返す(要実装)
    func fetchedValue() -> SendValue
    // 値が返せるかをBoolで返す(必要に応じて実装)
    func canFetch() -> Bool
}

extension Fetchable {
    public func canFetch() -> Bool {
        // デフォルトではtrueを返す
        return true
    }
}

Sendableで必要なSendValueに加えて、fetchedValue()を必ず実装する必要があります。返り値に返すのはsync()を呼んだ時に送信される値です。

fetchedValue()で値を引き出せないことがある場合にcanFetch()falseを返すとfetchedValue()は呼ばれません。デフォルトはtrueなので、必要があれば実装をしてください。

こちらも実装例として、Intを保持するIntHolderを書いてみるとこのようになります。

class IntHolder: Fetchable {
    typealias SendValue = Int

    var value: Int = 0 {
        didSet {
            if self.value != oldValue {
                self.broadcast(value: self.value)
            }
        }
    }

    func fetchedValue() -> Int {
        return self.value
    }
}

以下のように使うことができます。

let holder = IntHolder()

// sync()で初期値の0がログに出力される
let observer = holder.chain().do { print($0) }.sync()

// 値をセットすることでイベントが送信され、1がログに出力される
holder.value = 1

基本的にはbroadcast(value:)で送る値とfetchedValue()で返す値は同じものになると思いますが、あえて変えるという使い方も考えられます。

例えば、Viewを最初に表示するときはアニメーションしたくないけど、表示中のViewを更新するときはアニメーションさせたいなんていう場合には、enumなどで値を送ってアニメーションの有無を変えるというのもありでしょう。

Receivable

Receivableに適合したクラスは、sendTo()でイベントを受信することができます。

定義は以下の通りです。

public protocol Receivable: class {
    // 受信するイベントの型
    associatedtype ReceiveValue
    // 受信した時の処理(要実装)
    func receive(value: ReceiveValue)
}

受け取るイベントの型をReceiveValueで定義し、receive(value:)を実装する必要があります。イベントで送信されてきた値がvalueに入ってきますので、受け取った時の処理を書いてください。

例として、Fetchableの例のIntHolderをさらに拡張してみたいと思います。

class IntHolder: Fetchable, Receivable {
    typealias SendValue = Int
    typealias ReceiveValue = Int

    var value: Int = 0 {
        didSet {
            if self.value != oldValue {
                self.broadcast(value: self.value)
            }
        }
    }

    func fetchedValue() -> Int {
        return self.value
    }

    func receive(value: Int) {
        self.value = value
    }
}

以下のように使うことができます。

let sender = IntHolder(0)
let receiver = IntHolder(1)

// syncで値が同期され、receiverは0になる
let observer = sender.chain().sendTo(receiver).sync()

// 0がログに出力される
print(receiver.value)

// イベントが送信され、receiverは2になる
sender.value = 2

// 2がログに出力される
print(receiver.value)

Receivableに関しては、SendableFetchableのように重要な機能を有しているわけではなく、doを使えば可能なことをsendToで簡単にする程度のものになります。

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

CS193p class note - 4 More Swift

Memo:
  1. We have "mutating" keyword only on func. Because for a property, the keyword "let", "var", "get", "set" will let us know whether the property is immutable.
  2. Swift is "copy on write". It means it only copies the whole bits when the object is mutating.
  3. You can set the default values for an IBOutlet through didSet
Protocol:
  1. A type which is a declaration of functionality only. Protocols are a way to express an API more concisely. So a protocol is simply a collection of method and property declaration.
  2. No data storage of any kind
  3. What are protocols good for?
    • Making API more flexible and expressive
    • Blind, structured communication between View and Controller (delegation)
    • Mandating behavior (e.g. the keys of a Dictionary must be hashable)
    • Sharing functionality in disparate types(String, Array, CountablRange are all Collections)
    • Multiple inheritance(of functionality, not data)
  4. Three aspects to a protocol
    • the protocol declaration (which properties and methods are inside)
    • a class, struct or enum declaration that makes the claim to implement the protocol
    • the code in said class, struct or enum (or extension) that implements the protocol
  5. In Swift, you need to implement all declared methods/properties in the protocol. But in Objective C, it is possible to mark some methods as optional. Any protocol that has optional methods must be marked @objc. Any any class that implements an optional protocol must inherit from NSObject
  6. You must specify whether a property is get only or both get and set
  7. Any functions that are expected to mutate the receiver should be marked mutating (unless you are going to restrict your protocol to class implementations only with class keyword).
  8. In a class, inits must be marked required (or otherwise a subclass might not conform).
  9. You are allowed to add protocol conformance via an extension.
  10. Using extension to provide protocol implementation
String:
  1. String is not indexed by Int, it is indexed by a different type called String.Index.
NSAttributedString:
let attributes: [NSAttributedString.Key: Any] = [.strokeColor: UIColor.orange,
                                                 .strokeWidth: 5.0]
let attributeText = NSAttributedString(string: "Test", attributes: attributes)
someLabel.attributedText = attributeText
  1. A String with attributes attached to each character
  2. It's a class not a struct. It is a completely different data structure than String. To get mutability, you have to use a subclass of it called NSMutableAttributedString.
Function Types:
  • You can declare a variable (or parameter to a method or whatever) to be of type "function".
  • Often you want to create the function "on the fly"(rather than already-existing ones). You can do this by using a closure
  • Array has a method called map which takes a function as an argument. It applies that function to each element of the Array to create and return a new Array.
let primes = [2.0, 3.0, 5.0, 7.0, 11.0]
let negativePrimes = primes.map({-$0}) // [-2.0, -3.0, -5.0, -7.0, -11.0]
let invertedPrimes = primes.map(){1.0/$0} // [0.5, 0.333, 0.2, etc]
let primeStrings = primes.map{String($0)} // ["2.0", "3.0", "5.0", "7.0", "11.0"]

Note that if the last (or only) argument to a method is a closure, you can put it outside the method's parentheses that contain its arguments and if the closure was the only argument, you can skip the () completely if you want.

  • Closures with property initialization: you can also execute a closure to do initialization of a property if you want. This is especially useful with lazy property initialization
var someProperty: Type = {
    // construct the value of someProperty here
    return <the constructed value>
}()
  • Closures are regular types, so they can be put in Array, Dictionary, etc. When this happens, they are stored in the heap (i.e. they are reference types). They capture variables they use from the surrounding code into the heap too. Those captured variables need to stay in the heap as long as the closure stays in the heap.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift MkMapViewで地図アプリ作成してみた(9)- スケールバーとコンパスを追加する

記事一覧

Swift MkMapViewで地図アプリ作成してみた(記事一覧)

スケールバーを表示する

スケールを変更すると自動でスケールバーを表示する。

ViewController.swift:スケールバーを表示する
// スケールバーの表示
let scale = MKScaleView(mapView: mapView)
scale.frame.origin.x = 15
scale.frame.origin.y = 45
scale.legendAlignment = .leading
self.view.addSubview(scale)

legendAlignmentに設定できる値は以下の通り。

1 2
.leading 0を左起点にする
.trailing 0を右起点にする

scale.png

コンパスを表示する

ViewController.swift:コンパスを表示する
// コンパスの表示
let compass = MKCompassButton(mapView: mapView)
compass.compassVisibility = .adaptive
compass.frame = CGRect(x: width - 50, y: 150, width: 40, height: 40)
self.view.addSubview(compass)
// デフォルトのコンパスを非表示にする
mapView.showsCompass = false

compassVisibilityに設定できる値は以下の通り。

1 2
.hidden 常に非表示
.visible 常に表示 
.adaptive 地図の方位を動かしたとき(ノースアップでない時)だけ表示。

デバイスで実行した結果

device.png

補足

前回の記事で設定したトラッキングモードを変更するボタンの位置を変えている。
Swift MkMapViewで地図アプリ作成してみた(7)- トラッキングモードを切り替える(自作ボタン)
Swift MkMapViewで地図アプリ作成してみた(8)- トラッキングモードを切り替える(MKUserTrackingButton)

ViewController.swift:トラッキングボタンの位置を変更
trakingBtn.frame = CGRect(x:width - 50, y:100, width:40, height:40)

参考文書

iOS 11で大幅に強化されたMapKit

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

【Swift4.2】EnumEnumerable が使えなくなっていた、、

Xcode10対応

Swift4.2が登場してからかなりお世話になっていたCaseIterable

これ自体はかなり助けられていたのだが、、

2019/03以降のXcode10対応が必須となって、保守していたアプリをXcode10に対応させる必要が生じた、、
Xcode10対応について

Xcode9.4.1からXcode10.1でビルドしたが、、まさかのEnumEnumerableのCasesが取得できない、、という自体が発生。

影響範囲を抑えた修正をしたかったが、方法が見当たらなかったので、CaseIterableへの変更にて対応した

もし、なにか情報いただけましたら、教えてください。

参考資料

Swift4.0→4.2でEnumEnumerableが不要になった

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