cockscomblog?

cockscomb on hatena blog

AndroidのウィジェットをComposeのAPIで開発できるGlanceはアルファ版が開発中

AndroidウィジェットをComposeのAPIで作る方法がAndroid Dev Summitで紹介された。RemoteViewにうまく対応する。

Glanceと呼ばれていて、コードは公開されている。

しかしまだアルファ版も出ていない。

アルファリリースに向かって開発中らしい。

Xcode 13.2とSwift Concurrencyのバックデプロイメント、iPadOS向けのSwiftUIでのアプリ開発をサポートしたSwift Playgrounds、macOSでのSharePlayとUniversal Controlはこの秋にリリースされる

この秋のうちにSharePlayとUniversal ControlをサポートするmacOS Montereyがリリースされる。バージョン番号はおそらく12.1になるだろう。

Available later this fall macOS Monterey - Apple

同じタイミングでmacOS Monterey 12.1のSDKを含んだXcode 13.2がリリースされるはずだ。このバージョンのXcodeからSwift Concurrencyのバックデプロイメントが可能になる。

Xcode 13.2 beta includes SDKs for iOS 15.2, iPadOS 15.2, tvOS 15.2, watchOS 8.3, and macOS Monterey 12.1. Apple Developer Documentation

You can now use Swift Concurrency in applications that deploy to macOS 10.15, iOS 13, tvOS 13, and watchOS 6 or newer. Apple Developer Documentation

Xcode 13.2はSwift Playgrounds 4の新しいプロジェクト形式をサポートする。これはiPadOS向けのSwiftUIでのアプリ開発をサポートしたSwift Playgroundsが今年中にリリースされるはずであることと整合する。

Xcode 13.2 beta includes support for app projects created with Swift Playgrounds 4. Apple Developer Documentation

* Available late 2021. iPadOS 15 - Apple

WEB+DB PRESS Vol.125の特集「GraphQL完全ガイド」を執筆しました

f:id:cockscomb:20211017224108p:plain 今週、10月23日(土)に発売されるWEB+DB PRESS Vol.125に掲載される、特集記事「GraphQL完全ガイド」を執筆しました。よろしくおねがいします。

桃栗三年、GraphQL 6年

原稿を書く過程で、知っているはずのことでも改めて調べなおしたりする。特に歴史みたいなのが好きで、GraphQLは2015年6月に発表されて、2018年に安定版になって、みたいなのをずっと調べてしまう。GraphQLってなんかすごい最近っぽく感じていたけど、発表されてからもう6年経つらしい。

ちなみにjQuery 1.0は2006年8月にリリースされていて、Reactは2013年5月に公開されたらしい。6年というのはだいたいそれくらい。

6年で、GraphQLはよく普及した。Facebookはもちろん、GitHubTwitterNetflixも、GraphQLを使っている。GitHubの新しいプロジェクトでも、普通にGraphQL APIを使っている

GraphQL、使っていますか

筆者は2018年からGraphQLを使っている。仕事で隙あらばGraphQLを導入した。サーバー側もクライアント側も何度も作った。そこで得た知見をこのブログに書いたりもした。

なんでもそうだけど、慣れてしまえばなんでもないが、最初は取っ付きにくい。GraphQLもそういうところがある。ということで、なるべく網羅的に説明するようにした。GraphQLをまだ使ったことがなかったり、本格的に取り組んだ経験がない場合に、特に役に立つようなつもりで書いた。

お買い求めください

見本誌が届いたので知っているのだけど、どの記事もおもしろい。例え僅かな可能性としてGraphQLに興味がない場合でも、WEB+DB PRESS Vol.125を買わない理由にはならない。

筆者はよくGihyo Digital Publishingを使う。リフローのできるEPubと、プロが丁寧にレイアウトしたPDFのどちらも入手できて、便利だ。

Amazonで買っても書店で買ってもいいと思う。全部便利だと思う。

雑誌の原稿を書くのは、前回iOS 14に関する特集を同僚らと書いて以来の二度目だが、そうそう慣れるものでもなく、必死だった。編集の皆さまにもお世話になり、また同僚や家族の助力も得て、ようやくというところ。

ということで、何卒よろしくお願いいたします。

こちらからは以上です。

GitHubの新しいプロジェクトを使ってみている

新しいものが好きなので、GitHubの新しいプロジェクトで仕事をしている。まだベータ版だが、少し前に会社のアカウントで有効になったので、所属するチームで大喜びで使い始めた。

もともとしばらくAsanaを使っていて、Asanaはタスクに依存関係がつけられたりして気に入っている。とはいえ仕事の大部分はGitHub上のリポジトリで行うのだから、GitHub上で完結するなら試してみたいわけである。

スプリント

ということでとりあえず仕掛かりのIssueなどをプロジェクトに入れていたのだけど、なんかまだしっくりきていなかった。ドキュメントにはベストプラクティスとかもあるけど、そんなにおもしろいことは書いていない。Single source of truth志向なのはいいと思う。

しっくりこなかったことの一つは、スプリントの表現がうまくいかない感じがしたからだった。Single source of truthということでリポジトリマイルストーンを使う感じかと思ったが、しかし扱っているリポジトリは残念ながら複数あって、それぞれにマイルストーンを設定してまわりたくはない。

と思っていたら今日になっていいのが出た。

このiteration field typeというのが最高。これで何もかも上手くいった。

GitHubの新しいプロジェクトは、今まさにバリバリ開発している感じでおもしろい。

自動化

プロジェクトにIssueとかPull Requestを追加するところは、けっこう面倒に感じる。ということで自動化を試す。

Automating projects (beta) - GitHub Docs

複数のチームに関連する特別なリポジトリで、特定のラベルがついたIssueを、自分のチームのプロジェクトに追加する、というのをやってみた。次のようなGitHub Actionsを設定した。

name: Projects
on:
  issues:
    types:
      - labeled
jobs:
  projects:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/github-script@v5
        with:
          github-token: ${{ secrets.GITHUB_TOKEN_FOR_PROJECTS }}
          script: |
            const script = require('./.github/workflows/lib/projects.js');
            await script({ github, context, core }, {
              '特定のラベル': 1,
            });

「特定のラベル」がつけられたIssueを「#1」のプロジェクトに追加する、というマッピングが書いてある。シークレットからPersonal Access Tokenをもらって使っている。このトークンの権限は、プロジェクトを操作するために org:write が必要であるほか、Issueの情報を取得するために repo:read も必要だった。

.github/workflows/lib/projects.js は次の通り。

module.exports = async ({ github, context, core }, projectMapping) => {
  const headers = {
    'GraphQL-Features': 'projects_next_graphql',
  };

  const projectNumber = projectMapping[context.payload.label.name];
  if (projectNumber === undefined) {
    return;
  }

  const project = await github.graphql(
    `query($organization: String!, $projectNumber: Int!) {
      organization(login: $organization) {
        projectNext(number: $projectNumber) {
          id
        }
      }
    }`,
    {
      headers,
      organization: context.repo.owner,
      projectNumber,
    }
  );
  const projectId = project.organization.projectNext.id;

  const item = await github.graphql(
    `mutation($projectId: ID!, $contentId: ID!) {
      addProjectNextItem(input: { projectId: $projectId, contentId: $contentId }) {
        projectNextItem {
          id
        }
      }
    }`,
    {
      headers,
      projectId,
      contentId: context.payload.issue.node_id,
    }
  );
  core.exportVariable('itemId', item.addProjectNextItem.projectNextItem.id);
};

ドキュメントでは gh コマンドを使っているが、値を引き回すのが面倒なので、actions/github-script@v5 を使ってJavaScriptで書いた。スクリプトを別なファイルに置いているのはエディタで書きやすいから。でも別なファイルに分けると actions/checkout@v2 が必要になって、もったいない気もしている……。

actions/github-script@v5 を使うとOctokitが使えるのだけど、ヘッダの扱いがよくわからず、Octokitのコードを読む羽目になった。

ラベルが外されたらプロジェクトから取り除くのもやりたかったのだけど、プロジェクト内のアイテムから該当のものを探すのが簡単でなかったので、いったん諦めた。

ということで

いろいろタイミングが合ったので、GitHubの新しいプロジェクトを使ってみている。まだ開発中という感じだけど、その分だけ将来に期待できていいと思う。

Swift ConcurrencyのwithTaskCancellationHandlerとSendable

Swift 5.5がリリースされた。おめでとうございます。

Swift 5.5の目玉はもちろんSwift Concurrencyだ。言語機能として並行処理がサポートされた。async/awaitの構文だけでなく、Structured Concurrencyとしての整理や、actorの導入など、野心的な取り組みと言える。

Swift Concurrency

Swift Concurrencyに直接関係するSwift Evolutionの提案はこれだけある。

これだけの提案が行われ、実装されたことに圧倒される。

The Swift Programming Language」にもConcurrencyの章が追加されている。こちらではわかりやすく説明されているので、概要を掴みやすい。

そろそろ本題。

タスクのキャンセル

Structured Concurrencyでは、親のタスクがキャンセルされると子のタスクもキャンセルされる。キャンセルされたかどうかはTask.isCancelledTask.checkCancellation()とかで確認できる。

withTaskCancellationHandler

タスクがキャンセルされたときに何か実行するにはwithTaskCancellationHandlerを使う。

func withTaskCancellationHandler<T>(operation: () async throws -> T, onCancel handler: @Sendable () -> Void) async rethrows -> T

onCancel@Sendableであることは重要なので、覚えておいてください。

コールバックを引数に取る関数からasync関数を作るwithUnsafeThrowingContinuationwithTaskCancellationHandlerを組み合わせる例が、SE-0300 Continuations for interfacing async tasks with synchronous codeのAdditional examplesにある。

import Foundation

func download(url: URL) async throws -> Data? {
    var urlSessionTask: URLSessionTask?

    return try await withTaskCancellationHandler {
        try await withUnsafeThrowingContinuation { continuation in
            urlSessionTask = URLSession.shared.dataTask(with: url) { data, _, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: data)
                }
            }
            urlSessionTask?.resume()
        }
    } onCancel: {
        urlSessionTask?.cancel()
    }
}

最新のAPIに合わせて少し書き換えているが、これは次のエラーでコンパイルできない。

Reference to captured var 'urlSessionTask' in concurrently-executing code

var urlSessionTask: URLSessionTask?varであるため、エラーが起きている。これはdiag::concurrent_access_of_local_captureというメッセージだった。

diag::concurrent_access_of_local_capture

エラーを出しているのはここ。

ちょっとコードを読むと、「flow-sensitive concurrent captures」なる機能についても言及されているが、今はフラグで無効になっている。

これはキャプチャした後に変更されないとわかっていればOKとするもので、今回のケースではいずれにせよコンパイルが通らないのが正しいように見える。

varじゃなくてletにして、参照型を間に挟めばコンパイルが通る。

import Foundation

class Wrapper<Wrapped> {
    var value: Wrapped
    init(_ value: Wrapped) { self.value = value }
}

func download(url: URL) async throws -> Data? {
    let urlSessionTask: Wrapper<URLSessionTask?> = Wrapper(nil)

    return try await withTaskCancellationHandler {
        try await withUnsafeThrowingContinuation { continuation in
            urlSessionTask.value = URLSession.shared.dataTask(with: url) { data, _, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: data)
                }
            }
            urlSessionTask.value?.resume()
        }
    } onCancel: {
        urlSessionTask.value?.cancel()
    }
}

一見よいように思うかもしれないが、var変数のキャプチャはNGでこれはOKなのって不思議じゃないですか?ということで、Other Swift Flags-Xfrontend -warn-concurrencyを設定する。

f:id:cockscomb:20210926102445p:plain
Other Swift Flags

-warn-concurrency

-warn-concurrencyってなんなんだという話だけど、これは互換性と関係している。Swift 5系では破壊的な変更を避けつつ、並行処理でデータ競合を避けるための道具を提供する。そしてSwift 6ではチェックを厳密にすることで、デフォルトでデータの競合について安全になる。-warn-concurrencyオプションをつけると、Swift 6で問題になる箇所を予め知るためのもの、ということらしい。

さて、-warn-concurrencyオプションをつけると、該当の箇所に次の警告が現れる。

Cannot use let 'urlSessionTask' with a non-sendable type 'Wrapper<URLSessionTask?>' from concurrently-executed code

onCancel@Sendableクロージャだが、そこからSendableではない値を参照していることで、警告が出た。class Wrapper<Wrapped>@unchecked Sendableにすれば警告は消えるが、Wrapperがちゃんとスレッドセーフなわけではないので、コンパイラを誤魔化しているだけの効果しかない。

Sendable

Swift 6でチェックされる項目の一つはSendableについてである。

Sendableの提案は受理されているが、現時点では実装済みになっていない。Swift 6でデフォルトでチェックされるようになるのを待っているのだと思うが、Swiftの標準ライブラリでは既に使われているし、自分たちで使うこともできる。

Sendableというのはactor間でやり取りできる型を表すマーカープロトコルである。マーカープロトコルはコンパイラに対する目印。

Sendableにできるかどうかは、例えば次のようになっている。

  • structのような値型は、Copy on Writeなので、actor間でやり取りしても競合が起きない。変更可能なプロパティがSendableであればSendableにできる
  • classは参照型なので、変更可能なプロパティを持っていないときだけSendableにできる。ただしfinalじゃないといけない
  • actorは参照型だけど内部状態をデータ競合から保護しているのでSendable

classだけど、内部的にデータ競合を防ぐ仕組み(ロックとか)を持っていることもある。そういうときは@unchecked Sendableに指定できる。これはコンパイラにチェックされないSendableである。

そしてクロージャ@Sendableアノテーションすると、クロージャもSendableになる。SendableなクロージャがキャプチャできるのはSendableだけである。

Swift Atomics

ここでとりあえずapple/swift-atomicsManagedAtomicLazyReferenceを使ってみる。要するにデータの競合が排除されていればいいので、今回の用途には大袈裟な感じもするが、使ってしまう。

import Foundation
import Atomics

func download(url: URL) async throws -> Data? {
    let urlSessionTask = ManagedAtomicLazyReference<URLSessionTask>()

    return try await withTaskCancellationHandler {
        try await withUnsafeThrowingContinuation { continuation in
            let task = urlSessionTask.storeIfNilThenLoad(URLSession.shared.dataTask(with: url) { data, _, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: data)
                }
            })
            task.resume()
        }
    } onCancel: {
        urlSessionTask.load()?.cancel()
    }
}

ManagedAtomicLazyReferenceは、遅延して初期化される参照型をメモリ管理しつつ保持してくれる。

Swift Atomicsの1.0.2時点ではManagedAtomicLazyReferenceはSendableではないが、次のバージョンではSendableになるはずだ。

次のようにしてこれを先取りする。

extension ManagedAtomicLazyReference: @unchecked Sendable where Instance: Sendable {}
extension URLSessionTask: @unchecked Sendable {}

勢いよくURLSessionTaskもSendableにしてしまっているが、スレッドセーフなはずなので大丈夫だと思う。

ここまでやって、ついに-warn-concurrencyでも警告が出なくなったはずだ。

実際にはresumeする前にもキャンセルのチェックをしないと、タイミングによってうまくキャンセルされないはずなので、チェックする。ここまでのコードをまとめると次のようになるはず。

import Foundation
import Atomics

extension ManagedAtomicLazyReference: @unchecked Sendable where Instance: Sendable {}
extension URLSessionTask: @unchecked Sendable {}

func download(url: URL) async throws -> Data? {
    let urlSessionTask = ManagedAtomicLazyReference<URLSessionTask>()

    return try await withTaskCancellationHandler {
        return try await withUnsafeThrowingContinuation { continuation in
            let task = urlSessionTask.storeIfNilThenLoad(URLSession.shared.dataTask(with: url) { data, _, error in
                if let error = error {
                    continuation.resume(throwing: error)
                } else {
                    continuation.resume(returning: data)
                }
            })
            if Task.isCancelled {
                continuation.resume(throwing: CancellationError())
                return
            }
            task.resume()
        }
    } onCancel: {
        urlSessionTask.load()?.cancel()
    }
}

いかがでしたか?

withTaskCancellationHandlerを題材にしてSwift Concurrencyの-warn-concurrencyを試してみたつもりですが、いかがでしたか?なんとなく大袈裟に感じたように思う。現実には、URLSessionはすでにSwift Concurrencyに対応したインターフェースを持っているので、こういうコードを書くことはない。とはいえ-warn-concurrencyすると、普通のアプリでもSendableに関連した警告がいくらでも出てくるのではないか。

Swiftは安全な言語を志向しているから、デフォルトでデータ競合からも安全になっていく。(これは別にSwiftに限ったことではない。)このことはポジティブに受け止めていいはずだし、Swift 6が楽しみである。

とはいえ現状では、-warn-concurrencyでやっていくのは少し難しい。スレッドセーフなclassでもSendableがついていなかったりするので、単純にやりにくい。Swift自体もConcurrencyのサポートを始めたところで、Swift 6までにまだよくなるだろうから、焦らなくてもいいはずだ。

特にアプリの開発では、たまに-warn-concurrencyを有効にしてみて、なるほど、くらいでいいような気がする。反対にライブラリの場合は、今のうちから-warn-concurrencyを設定しておくと、将来的な破壊的変更を避けられるかもしれない。

こちらからは以上です。

WWDC21大夢想

毎年この時期になると、毎日のようにWWDCのことを夢想している。

去年はSwiftUIのアップデートとApple Silicon搭載のMac、ホーム画面のウィジェットに期待していた。

去年の期待は、いろいろなことをうまく言い当てているようにも見えるし、少し過剰なところもあった。WWDC20では叶わなかったいくつかの部分については、引き続きWWDC21でも期待している。

ではWWDC21では何が発表されるのか。

Swift

2014年にSwiftが発表されてから7年になる。SwiftはOSSで開発されているので、次にどのようなアップデートがあるか、事前に窺い知ることができる。

swift-evolutionによると、次のバージョンはSwift 5.5となり、特に並行処理の言語的なサポートに注力されている。async/awaitの構文や並行処理の単位としてのTask、actorモデルの導入が決まっている。

OSSではありながらも、プロジェクトの大きな方向性はAppleが握っており、Appleプラットフォームと足並みを揃えている。裏を返せば、Swiftの開発状況からAppleプラットフォームの方向性を占えるということになる。

例えば基本的な部分でも、Concurrency Interoperability with Objective-Cによって、既存のAPIがSwiftの並行処理の仕組みで扱いやすくなるだろう。しかし何よりも、SwiftUIに期待が集まる。

SwiftUI

SwiftUIが今年も大きく強化されることは疑いようがない。

Swiftの並行処理とSwiftUIがどのように連携するのかは、大きなトピックだ。少なくともCombineフレームワークとの相互運用性が担保されるはずだ。例えばStructured concurrencyTaskとCombineのFutureは形が似ているし、あるいはCombineのPublisherAsyncSequenceはコンセプトが近いように感じられる。これらがうまく連携しないはずはない。

CombineとSwiftの並行処理が関連づけられるだけでも、最低限SwiftUIからSwiftの並行処理が扱える。しかしそれでは少しまどろっこしい。ObservableObjectにasyncメソッドを生やしたくなるし、それをTask.detatchすることなくViewから直接呼び出せたらとも思う。ViewbodyプロパティにEffectful Read-only Propertiesでasyncやthrowsをつけられるようになればおもしろい。おもしろいが、これはとりもなおさず副作用を持つということで、扱いが難しくなる懸念もある。

ところで、actorモデルがSwiftUIとの接点を持つのか計りかねている。プロセッサがメニーコアの時代を迎えたいま、複数のコアを効果的に活用することは至上命題であり、actorモデルは並行性の複雑さに対する強力な武器になる。Actorを導入すると、actorで分離されたデータを参照するような処理はすべてasyncになる。SwiftUIがこのような非同期的な処理をうまく扱えると、actorを使いやすいはずだ。

他にも、Extend Property Wrappers to Function and Closure Parametersでプロパティラッパが色々なところで使えるようになるが、これがSwiftUIの新しいAPIに利用される可能性もある。

当然ながら、新しい組み込みのViewも多く追加されるだろう。ナビゲーション周りがもう少し抽象化されると便利なように思う。テストを書くための仕組みも必要だ。

Swift Package Manager

Swift Package Managerもまた、Swift 5.5でしっかりと進化する。これはおそらくXcodeに影響を与える。Xcode 11からパッケージ管理にSwift Package Managerが利用できるようになっているが、引き続き連携が改良されることだろう。Xcodeがパッケージ管理の機能を強化していくことで、サードパーティのツールに依存せずに開発が行えるようになっていく。

要するに、Xcode for iPadに期待している。

パッケージ管理の強化に伴ってもう一つ期待したいのは、Appleが提供するオフィシャルなライブラリの導入である。Androidで言うJetpackのように、OSに含まれるフレームワークとは別のライブラリが導入されるとおもしろい。OSに含まれるライブラリはOSと同時にしか更新されないし、利用者がOSをアップデートしない限りは、アプリ側で新しい機能を使えない。裏を返すと、OSに内蔵できるフレームワークというのは、より保守的なものになる。しかしSwift Package Managerで導入できるライブラリはもっと自由度が高い。高い頻度で改善できるし、バージョンの古いOSに向けてバックポートすることもできる。Appleが高品質で多様なライブラリを提供することで、アプリのエコシステムが大きく改善されるはずだ。

データ

Core Dataは、SwiftUIから扱いやすいようなサポートが行われている。Environmentに.managedObjectContextが用意されているほか、@FetchRequestFetchedResultsを使って簡単にデータを取得できる。さらにNSManagedObjectObservableObjectになっていることで、データの変更に追従できる。

いっけんよさそうに思われるが、実際に使ってみると、少し使いにくい。例えばNSManagedObjectのプロパティは、プリミティブ型でない限り、デフォルト値が指定されていてもOptionalになってしまう。

Swiftから扱いやすいSwiftDataのようなものが登場するのを期待する声がある。Swiftの並行処理ともうまく連携されていたらさらに便利そうだ。これは別にCloudKitでもいいかもしれない。

OS

WWDCでは各OSのメジャーアップデートが発表されるのが常である。

iPadOS

今年一番の期待はiPadOSのアップデートだろう。Apple M1を搭載した高性能なiPad Proが発売され、その評判のほとんどは、ハードウェアの性能をソフトウェアが活かせていない、というものだ。当然そんなことはAppleだって百も承知のはずであるから、iPadOSは飛躍的な改善がなされるに違いない。そうでないと困る。

マルチタスキングに関する改善は、想像しやすい。自由にウインドウを配置できるようになるとは思いにくいが、それでもふたつ以上のアプリを自由に行き来しながら利用できるようになってほしい。また外部ディスプレイとポインティングデバイスを利用しているときに、外部ディスプレイをミラーリングではなく拡張されたデスクトップとして利用できるようになってほしい。

プロ向けのアプリは当然必要だ。Final Cut ProやLogic Pro、そしてXcodeiPadバージョンが発表されることに期待したい。Xcodeは例えばSwiftUIアプリだけを開発できるのでもよさそうだ。ところで、プロ向けのアプリが作りやすいように、UIKit側にも拡張があっていいはずだ。iPadOS 14でサイドバーや新しいピッカーが追加されたように、例えば複数のUIWindowをタブで扱えるようなUIとか、そういうものが増えてもいいと思う。

iOS

iOS 14ではホーム画面のウィジェットが一大トピックだった。ウィジェットiOSをより魅力的なものにしたと思う。ウィジェットの体験を演繹すると、いくつかの可能性が考えられる。

ひとつはインタラクティブウィジェットである。ウィジェットはその仕組みから、リンクを設定する以外にはインタラクティブな要素がない。これは余分なリソースを使わないための仕組みであるから、それが変わるとは思いにくいが、例えば事前に用意しておいた表示と行き来させるとか、そういった対応は可能なはずだ。

また別な発想で、アプリのアイコンをウィジェットと同じ仕組みで変えられるようにする、というのも考えられる。天気予報のアプリとかで有用だろうと思うし、特に技術的な制約があるとは思えない。

あるいはホーム画面にとどまらずに、ロックスクリーンの表示をカスタマイズできるようにする方向性もある。ロックスクリーンをうまく設定できるようになると、通知以外の方法で即時性の高い情報を得られるようになりそうだ。

加えて、iOS 5から搭載されているSiriも、ついに10歳だ。近年では音声アシスタントとしてだけでなく、様々なAI機能のブランドになってきているが、そろそろ大きなアップデートがあってもよさそうに思う。

macOS

昨年のmacOS 11でユーザーインターフェースがアップデートされ、またApple Silicon搭載のMaciOSアプリがそのまま実行できるようになった。macOS 12は、それと較べると規模の小さなアップデートになると噂されている。過去の例からすると、ユーザーインターフェースはもう少し調整されていくだろうから、macOS 12でも改善が期待できる。

iOSやiPadOSと較べれば、macOSは始めから生産性のためのOSである。新型コロナウイルスパンデミックですっかり様相が変わったこの世界では、生産性もまた再定義されつつある。このことからして、macOS 12では(もちろん他のプラットフォームでも)コラボレーションに関連する新しい機能がフィーチャーされるのではないかと予想する。特にメッセージアプリは、macOS 12でMac Catalyst製になったこともあって、iOSと合わせて改善しやすい状況になっており、わかりやすい例になると思う。

他にも、iOSやiPadOSからmacOSに持ち込まれるものがあるかもしれない。例えばショートカットアプリがmacOSにも導入されると便利だろうと思う。Automatorと競合するのがネックではある。あるいはTestFlightのmacOSバージョンがあってもよさそうだ。

watchOS

watchOSは、ヘルスケア関係の機能が強化されるであろうこと以外には、あまり想像が及ばない。今秋に発売されるであろうApple Watch Series 7でハードウェアの刷新があるとすれば、watchOSアプリもその影響を受けて、ユーザーインターフェースがリフレッシュされる可能性がある。

tvOS

tvOSはここ数年、比較的マイナーなアップデートにとどまっている。順当に考えれば、今年もそうなる可能性が高いように思われる。HomeKitに関連した部分では、今年ついに共通企画となるMatterが策定されており、対応する機器が出てくるであろうという情勢だ。tvOSは家庭内の機器のハブとしての役割が担わされており、なんらかのアップデートがあるかもしれない。

サービス

Appleはサービス面でも拡大を続けている。iCloudWWDC 2011で発表されたサービスで、こちらも今年10周年を迎えようとしている。WWDC21でもiCloudに新しい機能が加わる可能性はあるだろう。

あるいはまだ日本でサービスインしていない、Apple NewsやApple Fitnessについても、サービスする地域の拡大はもちろん、その強化が期待されるところである。ニュースとプラットフォーマーの関係は昨今でもホットなトピックである。信頼できるニュースが21世紀も生き残っていくためには、そのエコシステムが重要である。

ハードウェア

昨年のWWDC20でMacApple Siliconへ移行させていくことが発表されてから1年経った。その間にMacBook AirMacBook ProMac Mini、そしてiMacの一部が、Apple M1を搭載するようになった。サードパーティApple Siliconへの対応状況なども含めて、現況がしっかりと宣伝されるのは想像に難くない。

WWDC21ではその続報が望まれる。Apple M1XなのかApple M2なのか(Apple A14と同じ世代ならM1Xであろうと思われるが)わからないものの、より高性能な(TDPの大きな)Apple Siliconを搭載したMacがアナウンスされる可能性は高いだろう。何しろApple SiliconはAppleにとっての虎の子であるはずで、Macがこれほど注目されるタイミングは他にない。

ほか

Apple GlassesのようなXRプラットフォームは毎年期待している。そろそろ何か発表されてもよいようにも思うが、なにもわからない。

ということで

WWDCへの期待が溢れ出ている。

SwiftUIのDynamicPropertyを試す

SwiftUIにはDynamicPropertyというprotocolがある。

これを使ってみようという趣旨の記事を見かけた。

ので、私も試してみました。

@Now

import Combine
import SwiftUI

class Clock: ObservableObject {
    @Published private(set) var date: Date = Date()

    init() {
        Timer.publish(every: 1, on: .main, in: .default)
            .autoconnect()
            .assign(to: &$date)
    }
}

@propertyWrapper
struct Now: DynamicProperty {
    @StateObject private var clock = Clock()

    var wrappedValue: Date {
        get {
            clock.date
        }
    }
}

こういうのを作っておいて

import SwiftUI

struct ContentView: View {
    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .none
        formatter.timeStyle = .long
        return formatter
    }()

    @Now private var date: Date

    var body: some View {
        Text(date, formatter: Self.dateFormatter)
    }
}

こう使う。

これでContentViewは毎秒更新され、現在時刻を表示し続ける。

DynamicPropertyって何

ドキュメントには、Viewが再計算される前にDynamicPropertyupdateメソッドを呼んでくれるくらいの情報しかないが、実際にはもうちょっといろいろあるようだ。

上記の例でもわかるように、@ObservedObjectの更新によってViewが再描画される。

ここで急に、SwiftUIのCore Dataサポートのように、@FetchRequest的な形で何かできるんじゃないか、と気づくと思う。

実際にRealmにはそういう機能があった。

現実のユースケース

DynamicPropertyでは、StateなりStateObject(またはObservedObject)なり、あるいはEnvironment(やEnvironmentObject)を、Viewのpropertyで使えるようだ。

これは少しReact Hooksに似ている。ReactのuseStateとSwiftUIの@Stateの対称性のように、ReactのCustom Hookとちょっと似ている。

とはいえ、@Nowの例は、単にObservableObjectをそのまま使えばいいわけで、独自のDynamicPropertyを作ることが正当化されるような場面は稀かも。