これおもしろい

らふにかいてこ

Nuke読んでて詰まったとこ 7/11〜

@_monoさんが、TwitterでNukeはお手本の様だと仰っていたのもあって、読んでみた
GitHub - kean/Nuke: A powerful image loading and caching framework
読み終えるまで随時更新

読み解けた

internal

internal final class Lock {

クラスのアクセス修飾子
同じライブラリ内ではpublicとして使える
import先の外部からは見えない、という認識でいる

参照:
[iOS 8][Swift] アクセス修飾子を理解する | Developers.IO

DispatchQueue

private let queue = DispatchQueue(label: "com.github.kean.Nuke.Manager")

同期、非同期するにせよ、作っておくタスク
この変数に対して、同期/非同期実行を関数で呼び出す

参照:
Swift3のGCD周りのまとめ - Qiita

private var observers = [() -> Void]()

クロージャの配列 配列の後の()が別物感醸してたけど、配列で良いらしい

参照:
[Swift]クロージャーの使い方 ※swift2.2の記事の為、不可となっているが

subscript

public protocol Caching: class {
    /// Accesses the image associated with the given key.
    subscript(key: AnyHashable) -> Image? { get set }
}

public extension Caching {
    /// Accesses the image associated with the given request.
    public subscript(request: Request) -> Image? {
        get { return self[Request.cacheKey(for: request)] }
        set { self[Request.cacheKey(for: request)] = newValue }
    }
}

これすごくかっこいい
まだざっくりとでも自分の言葉で説明できるほどに理解はしてない
※配列な訳ではない(確保されるメモリはこのクラス1つ分)
2箇所挙げたのは、ここの引数が違うことに気付かなくて数分混乱したから
上の方のは、準拠先のCacheでオーバーライドされている

参照:
サブスクリプト | Swift言語を学ぶ

objc_setAssociatedObject

objc_setAssociatedObject(target, &contextAK, context, .OBJC_ASSOCIATION_RETAIN)

変数をクラスに生やす(外部から)
ランタイムの機能を使っているそう ← 今度調べる
ここで渡している&contextAKが分からない
private static var contextAK = "Manager.Context.AssociatedKey"
第2引数自体がイマイチ分かってない ← 今度試し書きしてみる

参照:
継承を使わずにクラスにプロパティを設定する方法! - VASILY DEVELOPERS BLOG

Result列挙型

public enum Result<T> {
    case success(T), failure(Error)

    /// Returns a `value` if the result is success.
    public var value: T? {
        if case let .success(val) = self { return val } else { return nil }
    }

    /// Returns an `error` if the result is failure.
    public var error: Error? {
        if case let .failure(err) = self { return err } else { return nil }
    }
}

// 以下、実際に使用している箇所. 
// 設定箇所. 
if let data = data, let response = response, error == nil {
    completion(.success((data, response)))    // タプルだよね?. 
} else {
    completion(.failure((error ?? NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil))))
}

// valueの使用箇所. 
public func handle(response: Result<Image>, isFromMemoryCache: Bool) {
    guard let image = response.value else { return }

valueを呼ぶと、自身(設定箇所で渡されているResult変数)が.successなら、.successの値を返す
swiftの列挙型にまだ慣れていなくてやたら時間掛かった…

参照:
Swiftの列挙型(enum)おさらい - Qiita
Pattern Matching, Part 4: if case, guard case, for case – Crunchy Development

isKnownUniquelyReferenced()

    private mutating func applyMutation(_ closure: (Container) -> Void) {
        if !isKnownUniquelyReferenced(&container) {
            container = container.copy()
        }
        closure(container)
    }

名前通り、参照(※強参照)が1つかどうかを確認する
この関数が何をしようとしているのかは未だ理解できてない

参照:
isKnownUniquelyReferenced(_:) - Swift Standard Library | Apple Developer Documentation

reduce

    public func process(_ input: Image) -> Image? {
        return processors.reduce(input as Image!) { image, processor in
            return autoreleasepool { image != nil ? processor.process(image!) : nil }
        }
    }

コメント読む限り、Image!に変換したinput(=image)を追加した順のprocessorに渡しつつprocess実行したい、ということだと思う(コード的にもそう思う)
ということは、processorは配列だし、for文でも出来る??
けどこっちのがスマート、ってことで合ってるのだろうか?

参照:
Swiftのmap, filter, reduce(などなど)はこんな時に使う! - Qiita

DispatchWorkItem

        let work = DispatchWorkItem(block: closure)
        queue.async(execute: work)

キューの指定が必要ない + 細かい制御ができる 今回は渡したclosureを非同期実行させたいから使ってる

参照:
[Swift 3] Swift 3時代のGCDの基本的な使い方 | Developers.IO

convenience

    public convenience init(maxConcurrentOperationCount: Int) {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = maxConcurrentOperationCount
        self.init(queue: queue)
    }

親クラスのinitの前に処理したい為につけてる

参照:
[Swift] convenienceイニシャライザとdesignated(指定)イニシャライザ - Qiita

willChangeValue/didChangeValue

    override var isExecuting: Bool {
        get { return _isExecuting }
        set {
            willChangeValue(forKey: "isExecuting")
            _isExecuting = newValue
            didChangeValue(forKey: "isExecuting")
        }
    }

プロパティがコードで変更された時に明示的に通知する 変更内容はwillとdidで挟んであげる isExecutingのsetterの中なのだから、通知する必要がなさそうに思ったけど、どこで必要になるのだろう??

参照:
willChangeValue(forKey:) - NSObject | Apple Developer Documentation
Swift で Model を書いてみた (observer編) - Qiita
ヒレガス本の例題を Swift で書いてみる(3) - Qiita

CALayerを設定している意味

    extension ImageView: Target {
        /// Displays an image on success. Runs `opacity` transition if
        /// the response was not from the memory cache.
        public func handle(response: Result<Image>, isFromMemoryCache: Bool) {
            guard let image = response.value else { return }
            self.image = image
            if !isFromMemoryCache {
                let animation = CABasicAnimation(keyPath: "opacity")
                animation.duration = 0.25
                animation.fromValue = 0
                animation.toValue = 1
                let layer: CALayer? = self.layer // Make compiler happy on macOS
                layer?.add(animation, forKey: "imageTransition")
            }
        }
    }

表示する時に綺麗なフェードインをしてくる所以がここか!
不透明度を0から1にするまでのアニメーションを設定している


読み解けてない

CancellationTokenSource::cancel

    public func cancel() {
        if isCancelling { return } // fast pre-lock check
        lock.sync {
            if !isCancelling {
                isCancelling = true
                observers.forEach { $0() }
                observers.removeAll()
            }
        }
    }

ManagerクラスのloadImage関数ののcancelRequest(for: target)でtargetが既に登録されているなら、cancel掛ける、という意味だと思ったけれど…。
CancellationTokenSourceクラスのcancel関数の中で、キャンセル状態に切り替える時にobservers.forEach { $0() }で実行してからクロージャを削除しているところが分からない。