Bartinter 阅读笔记

简介

Bartinter 是一个关于 StatusBar 的库,它的功能很简单也很实用:

Dynamically changes status bar style depending on content behind it

使用也简单:

  • Set "View controller-based status bar appearance" (UIViewControllerBasedStatusBarAppearance) to YES in your Info.plist.
  • Set ViewController's updatesStatusBarAppearanceAutomatically = true

原理

有两个点

  • 一个是在需要的时候,计算状态栏的亮度
  • 一个是利用 AOP 把上面的流程自动化

计算状态栏的亮度

注意 Bartinter 是一个继承自 UIViewController 的类:public final class Bartinter: UIViewController,它以 childViewController 的形式获得了父 VC 的生命周期状态。

@objc public func refreshStatusBarStyle() 是更新状态栏的核心函数。

其中,private func calculateStatusBarAreaAvgLuminance(_ completion: @escaping (CGFloat) -> Void) 方法,获取父 VC 的 CALayer 和图形上下文,计算状态栏平均亮度,以决定 statusBarStyle。

注意这里有一个很贴心的细节是 antiFlickRange,用来防止亮度变更导致的状态栏反复变化。如果没有这一个细节,虽然功能实现了,但是整体体验势必要降低好几个档次。

@objc public func refreshStatusBarStyle() {
    calculateStatusBarAreaAvgLuminance { [weak self] avgLuminance in
        guard let strongSelf = self else { return }
        let antiFlick = strongSelf.configuration.antiFlickRange / 2
        if avgLuminance <= strongSelf.configuration.midPoint - antiFlick {
            strongSelf.statusBarStyle = .lightContent
        } else if avgLuminance >= strongSelf.configuration.midPoint + antiFlick {
            strongSelf.statusBarStyle = .default
        }
    }
}

流程自动化

利用 AOP 把上面的流程自动化。通过 hook 了UIViewController.childForStatusBarStyle,来返回 statusBarStyle。

设置则是通过 @IBInspectable var updatesStatusBarAppearanceAutomatically: Bool 这个入口,为 VC 附上一个 Bartinter 的实例.

虽然示例里说手动触发举的是func scrollViewDidScroll(_ scrollView: UIScrollView),但实际上 AOP 的时候并没有监听这个方法。

它监听的方法是:

public override func viewWillAppear(_ animated: Bool) // 通过 childViewController
public override func viewDidLayoutSubviews() // 通过 childViewController
UIView.layoutSubviews // 通过 AOP 坚挺了 parent.view

这里还有一个很 tricky 的地方,在于 Bartinter 的 static func swizzleIfNeeded() 方法,因为里面并没有做多线程保护,所以第一想法就是担心会产生多次 AOP 的问题。于是就着测试了一下,结果发现这个担忧通过主线程得到了保护:

Bartinter 的 static func swizzleIfNeeded() 方法只在初始化的时候调用,而如果我们在异步线程去调用 init 方法,会触发 Apple 的错误:“-[UIViewController initWithNibName:bundle:] must be used from main thread only”。通过主线程限制而规避了多线程竞争问题。

并且它是 internal 的,外部不可访问,防止了开发者的滥用。

static func swizzleIfNeeded() {
    guard isSwizzlingEnabled && !isSwizzlingPerformed else { return }
    UIViewController.setupChildViewControllerForStatusBarStyleSwizzling()
    UIView.setupSetNeedsLayoutSwizzling()
    isSwizzlingPerformed = true
}

public init(_ configuration: Configuration = Configuration()) {
    self.configuration = configuration
    Bartinter.swizzleIfNeeded()
    super.init(nibName: nil, bundle: nil)
}

Throttler

这个 Throttler 类挺有意思,可以直接用。Throttle 作为一个很实用的功能,在 Rx 里面也有集成。

这个 Throttler 采用的是首次立即实行,后续延迟执行的方案,保证一个maxInterval 内最多只会执行一次。在一直调用的情况下最坏下次执行需要间隔 (2 * maxInterval - 1最小时间单位)。

你可能感兴趣的:(Bartinter 阅读笔记)