ハイライト
Safari 15 では、JavaScript、WebAssembly、プライベート クラス フィールドを含む Web API、WeakRef、WebGL2、MediaRecorder、音声認識 API の 3 方向で大規模なアップデートが行われました。
主な内容
Web 開発者は長い間、Safari の問題点に直面してきました。それは、新機能のサポートが常に遅いということです。今回のWWDC2021は、多くのギャップを一気に埋めた。
JavaScript エンジンに関しては、プライベート クラス フィールドがついに実装されました。以前はアンダースコアが接頭辞として付けられていました_startTimeこれは単なる慣例であり、外部コードは引き続きアクセスできます。今すぐ使用#startTime、カプセル化は言語レベルで強制され、アクセスするとエラーがスローされます。
WeakRef と FinalizationRegistry により、メモリ管理がより細分化されます。ガベージ コレクションを妨げることなく、オブジェクトへの弱い参照を保持できます。オブジェクトがリサイクルされるときに、オブジェクトをクリーンアップするための通知を受け取ることもできます。これは、システムのキャッシュやフレームワークのテストに役立ちます。
WebAssembly エンジンには、メモリ命令、i64/BigInt 相互変換、参照型、ストリーミング コンパイルがアップグレードされています。C++/Rust を Web にコンパイルすると、パフォーマンスがさらに一歩近づきます。
Web API に関しては、WebGL2 はすべてのプラットフォームで利用でき、MediaRecorder はオーディオとビデオを記録でき、音声認識は Siri と同じエンジンを使用して音声をテキストに変換し、Web Share はファイルの共有をサポートします。
詳細
JavaScript プライベート クラス フィールド
以前は、クラスを作成するとき、プライベート メンバーは規約に依存していました。
class StopwatchWithOneButton {
constructor() {
this._startTime = null; // これは単なる合意であり、外部の世界からアクセスすることは可能です
}
click() {
if (this._startTime === null) {
this._startTime = Date.now();
} else {
const duration = Date.now() - this._startTime;
this._startTime = null;
return duration;
}
}
}
今すぐ使用#プレフィックス、言語はプライベートに適用されます:
class StopwatchWithOneButton {
#startTime = null; // 本当のプライベートフィールド
click() {
if (this.#startTime === null) {
this.#startTime = Date.now();
} else {
const duration = Date.now() - this.#startTime;
this.#startTime = null;
return duration;
}
}
// 私有方法
#start() { this.#startTime = Date.now(); }
#stop() {
const duration = Date.now() - this.#startTime;
this.#startTime = null;
return duration;
}
// 静的プライベートフィールド
static #startedCount = 0;
// パブリック静的フィールド
static totalCreated = 0;
}
キーポイント:
#field言語レベルのプライベートであるため、外部アクセスは TypeError をスローします (01:49)- プライベートメソッドも使用されます
#プレフィックス、クラス内でのみ呼び出すことができます (03:15) - 静的プライベートフィールドをサポート
#staticFieldおよびパブリック静的フィールドstaticField(03:39) - プライベート プロパティを実装するために WeakMap ハックは必要ありません
WeakRef と FinalizationRegistry
オブジェクトのバッチを追跡する必要があるが、それらがガベージ コレクションされるのを防ぎたくない場合:
const allStopwatches = new Set();
const registry = new FinalizationRegistry((id) => {
// オブジェクトがリサイクルされるときに自動的にクリーンアップします
for (const ref of allStopwatches) {
if (ref.id === id) {
allStopwatches.delete(ref);
break;
}
}
});
function addStopwatch(sw) {
const id = generateId();
const ref = new WeakRef(sw);
ref.id = id;
allStopwatches.add(ref);
registry.register(sw, id); // SWがリサイクルされたときにコールバックをトリガーする
}
function clickAllStopwatches() {
for (const ref of allStopwatches) {
const sw = ref.deref(); // オブジェクトを取得します。未定義を返す場合があります
if (sw) sw.click();
}
}
キーポイント:
WeakRef弱い参照を保持し、ガベージ コレクションを妨げない (04:19)deref()オブジェクトを取得し、リサイクルされている場合は返しますundefined(05:56)FinalizationRegistryオブジェクトがリサイクルされるときにクリーンアップするコールバックをトリガーする (06:21)- ガベージ コレクションのタイミングは不確実なので、主要なロジックに依存しないでください。
トップレベルの await とモジュール ワーカー
モジュール内で直接使用することが可能ですawait:
// stopwatch.mjs
const config = await fetch('/config.json').then(r => r.json());
export class Stopwatch { /* 使用 config */ }
// main.mjs
import { Stopwatch } from './stopwatch.mjs';
// stopwatch.mjs の await は完了するまで実行されません。
const sw = new Stopwatch();
ワーカーは ES モジュールもサポートしています。
// module worker を作成
const worker = new Worker('./worker.mjs', { type: 'module' });
// Service Worker
navigator.serviceWorker.register('./sw.mjs', { type: 'module' });
// Audio Worklet
await audioContext.audioWorklet.addModule('./processor.mjs');
キーポイント:
- トップレベルの await は ES モジュールでのみ利用可能です。通常のスクリプトは構文エラーを報告します (07:37)
- インポートされた非同期モジュールは、それに依存するモジュールの実行をブロックします (08:38)
- モジュール ワーカーは、動的インポート、最適化された読み込み、依存関係管理をサポートします (09:21)
- Webワーカー、サービスワーカー、ワークレット都サポートモジュール(09:51)
WebGL2 および WebM/VP9 のサポート
WebGL2 の新機能:
- 3D テクスチャ、雲などのボリューム効果をサポート (19:03)
- サンプラー オブジェクト、シェーダーでのテクスチャ使用の柔軟性が向上 (19:09)
- フィードバックの変換、GPU 上でのパーティクル システムの実装 (19:16)
- バックエンドは OpenGL から Metal に移行され、iOS シミュレータでも GPU を使用できるようになりました (20:23)
- Xcode Frame Debugger を使用した WebGL コードの分析 (20:40)
WebM と VP9:
- macOS 11.3 以降は、VP8/VP9 ビデオ + Vorbis オーディオ (21:19) をサポートします。
- macOS 12 では Opus オーディオのサポートが追加されました (21:30)
- iPadOS 15 は MSE ストリーミング WebM をサポートします (21:40)
- MediaCapabilities API を使用して特定のサポートを検出する (21:47)
MediaRecorder はオーディオとビデオを記録します
let mediaRecorder;
let recordedChunks = [];
async function startRecording() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm',
audioBitsPerSecond: 128000
});
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) recordedChunks.push(event.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'audio/webm' });
const url = URL.createObjectURL(blob);
document.querySelector('audio').src = url;
};
mediaRecorder.start();
}
function stopRecording() {
mediaRecorder.stop();
}
キーポイント:
MediaRecorderメディア要素または MediaStream からのデータのキャプチャ (25:23)- 指定可能なMIMEタイプとビットレート(25:39)
ondataavailable断片的なデータを収集し、onstopBLOB への組み立て (26:52)- マイク、カメラ、または画面からの録画をサポート
Audio Worklet リアルタイム オーディオ処理
// distortion-processor.mjs
class DistortionProcessor extends AudioWorkletProcessor {
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
for (let channel = 0; channel < input.length; channel++) {
const inputChannel = input[channel];
const outputChannel = output[channel];
for (let i = 0; i < inputChannel.length; i++) {
outputChannel[i] = this.distort(inputChannel[i]);
}
}
return true; // 保持活跃
}
distort(x) {
return x > 0 ? Math.min(x * 2, 1) : Math.max(x * 2, -1);
}
}
registerProcessor('distortion', DistortionProcessor);
// main.js
async function startRecordingWithEffect() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioContext = new AudioContext();
await audioContext.audioWorklet.addModule('./distortion-processor.mjs');
const source = audioContext.createMediaStreamSource(stream);
const effectNode = new AudioWorkletNode(audioContext, 'distortion');
const destination = audioContext.createMediaStreamDestination();
source.connect(effectNode).connect(destination);
// 処理された音声を録音する
mediaRecorder = new MediaRecorder(destination.stream);
mediaRecorder.start();
}
キーポイント:
- オーディオ ワークレットはオーディオ レンダリング スレッドで実行され、メイン スレッドとレンダリング スレッド間の切り替えを減らします (27:37)
- 古い ScriptProcessorNode よりも低いレイテンシ (27:49)
- プロセッサは継承する必要があります
AudioWorkletProcessorそして実装するprocess()メソッド (29:01) - 使用
registerProcessor()登録後にのみメインスレッドに渡すことができます。AudioWorkletNode使用する(29:38)
Web 共有 ファイルを共有する
async function shareAudio(blob, filename) {
const file = new File([blob], filename, { type: 'audio/webm' });
const shareData = {
files: [file],
title: 'Voice Memo',
text: 'Check out my recording'
};
if (navigator.canShare && navigator.canShare(shareData)) {
await navigator.share(shareData);
}
}
キーポイント:
- Web Share API は今年ファイル共有サポートを追加します (30:32)
- 写真、ビデオ、オーディオ、その他の種類のファイルを共有する (30:36)
- まずは電話してください
canShare()対応しているか確認してからお電話くださいnavigator.share()(31:37) - 共有インターフェイスはシステム スタイルと一致しており、メッセージ、メール、AirDrop、その他のターゲットをサポートしています (30:17)
音声認識 音声認識
const recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.interimResults = false;
recognition.onresult = (event) => {
let transcript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
if (event.results[i].isFinal) {
transcript += event.results[i][0].transcript;
}
}
console.log(transcript);
};
recognition.onend = () => {
console.log('Recognition ended');
};
function startRecognition() {
recognition.start();
}
function stopRecognition() {
recognition.stop();
}
キーポイント:
- Siriと同じ音声認識エンジンを使用し、多言語をサポート(32:23)
- まだ必要です
webkitプレフィックス:new webkitSpeechRecognition()(33:25) continuousプロパティは、手動で停止するまで認識を継続するかどうかを制御します (33:36)- 初めて使用するときにプライバシー認証プロンプトが表示されます (32:41)
- 結果は確率によって並べ替えられ、最初のものが最良の認識結果になります (34:02)
重要ポイント
- WeakMap のカプセル化をプライベート クラス フィールドに置き換えます。
#field構文は WeakMap ソリューションよりもシンプルで直感的であり、プライバシーは言語レベルで保証されます。 - Web 共有ファイルがあるにより、Web アプリがよりネイティブに近くなります。数行のコードでシステム合計パネルをばらまいてファイルをメッセージ配信でき、ユーザーエクスペリエンスはネイティブアプリと一致します。
- MediaRecorder + Audio Worklet を組み合わせてオーディオ アプリケーションを作成します。録音、リアルタイム処理、共有はすべて 1 つのリンクを通じて接続されており、完全なオーディオ編集ツールを Web 上に実装できます。
- 音声認識により、音声対話のしきい値が下がります。Siri エンジンを使用して音声をテキストに変換するには、独自のモデルをトレーニングしたり、サードパーティ API を呼び出したりする必要はありません。
- モジュール ワーカーは、重い計算をバックグラウンドに移動します。ES モジュールの依存関係管理と動的インポートにより、複雑な計算がメインスレッドをブロックすることがなくなりました。
関連セッション
- WKWebView の追加機能を確認する — WKWebView の最新アップデートとベスト プラクティス
- HTTP/3 と QUIC でネットワークを高速化 — Web ネットワークの高速化
- Safari Web Extensions について — Safari 拡張機能の開発を始める
コメント
GitHub Issues · utterances