Swift Digest
ST-0017 | Swift Evolution

Swift Testing の画像添付 API をプラットフォーム間で統合する

Consolidate Swift Testing’s image attachments API across platforms

Proposal
ST-0017
Authors
Jonathan Grynspan
Review Manager
Rachel Brindle
Status
Implemented (Swift 6.3)

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

01 何が問題だったのか

Swift Testing の画像添付機能は、Apple プラットフォーム向けの ST-0014 と Windows 向けの ST-0015 で別々に導入されました。両者は同じ「画像をテストに添付する」という機能を提供するものの、画像を表すプロトコルがプラットフォームごとに分かれており(Apple は AttachableAsCGImage、Windows は AttachableAsIWICBitmapSource)、AttachableImageFormat の初期化子もプラットフォームごとに微妙に異なる API になっていました。

両プラットフォームでインターフェイスがそろっていないと、クロスプラットフォームで動くテストコードを書きづらく、ライブラリ側の実装やドキュメントもプラットフォームごとに分岐させる必要があります。また、ST-0014 で導入された AttachableImageFormat.init(_:encodingQuality:) は第 1 引数のラベルが省略されており、Windows 側で導入された init(encoderCLSID:encodingQuality:) と並べたときに引数ラベルの形が不揃いでした。

これらは Swift 6.3 で導入されたばかりの機能であり、開発者に広く出荷される前にインターフェイスを整える機会が残されている、というのが本 Proposal の出発点です。

02 どのように解決されるのか

画像添付の API を次の 3 点で整理します。

  • プラットフォームごとに分かれていた AttachableAsCGImageAttachableAsIWICBitmapSource を、単一のプロトコル AttachableAsImage に統合
  • AttachableImageFormat の Apple 向け初期化子に明示的な引数ラベルを与え、Equatable / Hashable / CustomStringConvertible / CustomDebugStringConvertible への適合を追加
  • Attachment に、画像として記録する際の AttachableImageFormat を取り出すための imageFormat プロパティを追加

これらは Swift 6.3 でのリリース前の調整であり、ST-0014 / ST-0015 の AttachableAsCGImage / AttachableAsIWICBitmapSourceAttachableImageFormat.init(_:encodingQuality:) を利用していたコードには破壊的変更となります。

AttachableAsImage への統合

プラットフォーム固有のプロトコルである AttachableAsCGImageAttachableAsIWICBitmapSource は撤去され、共通のプロトコル AttachableAsImage に置き換えられます。これに伴い、両プロトコルが持っていた次の要求も削除されます。

- var attachableCGImage: CGImage { get throws }
- func copyAttachableIWICBitmapSource() throws -> UnsafeMutablePointer<IWICBitmapSource>

代わりに、画像のエンコード操作そのものを表す要求が AttachableAsImage に置かれます。CoreGraphics オーバーレイと WinSDK オーバーレイがこの要求を実装するため、CGImage / CIImage / NSImage / UIImageHBITMAP / HICON / IWICBitmapSource といった標準型はこれまでと同様にそのまま添付できます。要求は public なので、CGImageIWICBitmapSource を内部で持つ独自の画像型に対して AttachableAsImage への適合を追加する用途にも使えます。

public protocol AttachableAsImage {
  // ...

  /// Encode a representation of this image in a given image format.
  ///
  /// - Parameters:
  ///   - imageFormat: The image format to use when encoding this image.
  ///   - body: A function to call. A temporary buffer containing a data
  ///     representation of this instance is passed to it.
  ///
  /// - Returns: Whatever is returned by `body`.
  ///
  /// - Throws: Whatever is thrown by `body`, or any error that prevented the
  ///   creation of the buffer.
  borrowing func withUnsafeBytes<R>(as imageFormat: AttachableImageFormat, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R
}

CGImageIWICBitmapSource を内部に持つ独自の画像型に適合を与える場合は、内部の値の withUnsafeBytes(as:_:) に処理を委譲するだけで済みます。

import Testing
import CoreGraphics

struct MyImage {
  var cgImage: CGImage
  // ...
}

extension MyImage: AttachableAsImage {
  func withUnsafeBytes<R>(as imageFormat: AttachableImageFormat, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
    try cgImage.withUnsafeBytes(as: imageFormat, body)
  }
}

AttachableImageFormat の調整

Apple プラットフォーム向けの AttachableImageFormat 初期化子は、第 1 引数に明示的なラベル contentType: を持つように改名されます。Windows 側の init(encoderCLSID:encodingQuality:) と引数ラベルの形をそろえるためです。

 public struct AttachableImageFormat {
   // ...
-  public init(_ contentType: UTType, encodingQuality: Float = 1.0)
+  public init(contentType: UTType, encodingQuality: Float = 1.0)
 }

加えて、AttachableImageFormat に次の適合が追加されます。

extension AttachableImageFormat: Equatable, Hashable {}
extension AttachableImageFormat: CustomStringConvertible, CustomDebugStringConvertible {}

Equatable への適合は、上で示した withUnsafeBytes(as:_:) を正しく実装するうえでも必要です。Hashable は併せて自然に実装でき、CustomStringConvertible / CustomDebugStringConvertible への適合はエンコード失敗時などの診断出力を読みやすくします。

Attachment.imageFormat

Attachment には、添付対象が画像のときにそのエンコードに使う AttachableImageFormat を取り出すためのプロパティが追加されます。値が指定されていない場合は nil を返します。

extension Attachment where AttachableValue: AttachableWrapper,
                           AttachableValue.Wrapped: AttachableAsImage {
  /// The image format to use when encoding the represented image.
  public var imageFormat: AttachableImageFormat? { get }
}

03 今後の見通し

将来の発展として、画像添付の API で利用しているバッファ型を UnsafeRawBufferPointer から RawSpan に置き換える方向が挙げられています。attachment 機能は RawSpan が利用可能になる前、特に古い Apple プラットフォームへ back-deploy される前にリリースされた経緯があり、現状の API は UnsafeRawBufferPointer を使っています。attachment 全体で扱うバッファ型を統一したいという考えから、画像添付の層だけを RawSpan 化することは目指されていません。将来的には、UnsafeRawBufferPointer を使う既存 API を非推奨化し、RawSpan を使う新しい API を導入することが検討されています。

これは将来の構想であり、実現を約束するものではありません。