Skip to content

Marker protocols for common classes in Apple SDKs

License

Notifications You must be signed in to change notification settings

CaptureContext/swift-marker-protocols

Repository files navigation

swift-marker-protocols

CI

Package that declares empty protocols for base classes of various system frameworks.

Table of contents

Motivation

Marker protocols is an approach of partially erasing type constraints for writing generic extensions for types in Swift.

Such protocols can be required by different packages and declaring them in-place may cause name collisions.

This lightweight package declares a set of core marker protocols to potentially address such collisions.

Examples

Marker protocols are useful for building type-aware generic extensions.

Methods on generic Self

Without MarkerProtocols

extension AVCaptureDevice {
  func withExclusiveLock(
    // ❌ `Self` can't be used on non-final class AVCaptureDevice
    perform configuration: (AVCaptureDevice) async -> Void
  ) async throws {
    try self.lockForConfiguration()
    await configuration(self)
    self.unlockForConfiguration()
  }
}

let instance: CustomAVCaptureDevice = .init()
instance.withExlusiveLock { device in
  // ❌ Type of device is erased to AVCaptureDevice
  // not just base AVCaptureDevice
}

With MarkerProtocols

import AVFoundationMarkerProtocols

extension _AVCaptureDeviceProtocol {
  func withExclusiveLock(
    // ✅ `Self` can be used on a marker protocol
    perform configuration: (Self) async -> Void
  ) async throws {
    try self.lockForConfiguration()
    await configuration(self)
    self.unlockForConfiguration()
  }
}

let instance: CustomAVCaptureDevice = .init()
instance.withExlusiveLock { device in
  // ✅ Type of device is kept - CustomAVCaptureDevice
}

Properties on generic Self

Lets say you want to build some layout proxy for Cocoa views

  • Target API

    view.layout.chain.of.calls()
  • Chainable proxy type

    public struct LayoutProxy<Target: UIView> {
      public let target: Target
    
      internal init(_ target: Target) {
        self.target = target
      }
    }

Without MarkerProtocols

extension UIView {
  // ❌ `Self` can't be used on non-final class UIView
  // Using this property on any type will always
  // erase a type of the view
  var layout: LayoutProxy<UIView> { .init(self) }
}

With MarkerProtocols

extension _UIViewProtocol {
  // ✅ `Self` can be used here and the type
  // of the view is kept at the call site
  var layout: LayoutProxy<Self> { .init(self) }
}

Optionals

struct GenericContainer<Content> {
  var content: Content
}

Without MarkerProtocols

extension GenericContainer {
  // This declaration is completely fine, except
  // of being a bit too verbose
  func unwrapped<Value>(with value: Value) -> GenericContainer<Value>
  where Content == Value {
    .init(content: content ?? value)
  }
  
  // ❌ Won't compile since properties can't be 
  // generic and we can't declare `Value` type here
  var unsafelyUnwrapped: GenericContainer<Value> { /*...*/ }
}

With MarkerProtocols

extension GenericContainer where Content: _OptionalProtocol {
  func unwrapped(with value: Value) -> GenericContainer<Value> {
    .init(with: content._optional ?? value)
  }
  
  var unsafelyUnwrapped: GenericContainer<Content.Wrapped> {
    get { .init(content: content._optional!) }
    set { self.content = newValue.content }
  }
}

Products

SwiftMarkerProtocols

  • _OptionalProtocol<Wrapped>
  • _AnyKeyPathProtocol - alternative to Swift._AppendKeyPath protocol

QuartzCoreMarkerProtocols

  • _CALayerProtocol
  • Exports FoundationMarkerProtocols

AVFoundationMarkerProtocols

  • _AVCaptureDeviceProtocol
  • Exports FoundationMarkerProtocols

FoundationMarkerProtocols

  • _NotificationCenterProtocol
  • Exports SwiftMarkerProtocols

CocoaMarkerProtocols

  • _UIViewProtocol / _NSViewProtocol
  • _UIViewControllerProtocol / _NSViewControllerProtocol
  • Exports FoundationMarkerProtocols

Tip

  • UIKit is basically a CocoaTouch framework
  • AppKit is basically Cocoa framework without CoreData

Both frameworks could have shared Cocoa prefix for their types, so capturecontext/cocoa-aliases package provides Cocoa-prefixed aliases for UI/NS prefixed Cocoa types, it also exports aliased _CocoaViewProtocol and _CocoaViewControllerProtocol marker protocols.

MarkerProtocols

This is an umbrella product that exports all available marker protocols.

Installation

Primary targets for this package are other packages

.package(
  url: "https://github.com/capturecontext/swift-marker-protocols.git", 
  .upToNextMajor(from: "1.1.0")
)

Do not forget about target dependencies:

.product(
  name: "MarkerProtocols", 
  package: "swift-marker-protocols"
)

License

This library is released under the MIT license. See LICENSE for details.