メインコンテンツにスキップ

Swift 開発者向けの Dart 学習

このガイドは、Dart を学習する際に、あなたの Swift プログラミングの知識を活用することを目的としています。両言語における主要な類似点と相違点を示し、Swift には存在しない Dart の概念を紹介します。Swift 開発者にとって、Dart は馴染みやすいかもしれません。なぜなら、両言語は多くの概念を共有しているからです。

Swift と Dart はどちらもサウンド null セーフティをサポートしています。どちらの言語も、デフォルトで変数が null になることを許可しません。

Swift と同様に、Dart は コレクションジェネリック並行処理 (async/await を使用)、および 拡張機能 を同様にサポートしています。

Mixin は、Swift 開発者には新しい概念かもしれませんが、Dart にもあります。Swift と同様に、Dart は AOT (ahead-of-time) コンパイルをサポートしています。しかし、Dart は、増分再コンパイルやデバッグなどのさまざまな開発側面を支援するために、JIT (just-in-time) コンパイルモードもサポートしています。詳細については、Dart の概要 を参照してください。

規約とリンティング

#

Swift と Dart はどちらも標準的な規約を強制するためのリンティングツールを持っています。しかし、Swift がスタンドアロンツールとして SwiftLint を持つ一方、Dart は公式のレイアウト規約を持ち、リンターを含んでおり、準拠を容易にします。プロジェクトのリンティングルールをカスタマイズするには、静的解析のカスタマイズ の手順に従ってください。(Dart および Flutter の IDE プラグインもこの機能を提供していることに注意してください。)

Dart はコードフォーマッターも提供しており、コマンドラインまたは IDE から dart format を実行することで、任意の Dart プロジェクトを自動的にフォーマットできます。

Dart の規約とリンティングの詳細については、Effective Dart および Linter ルール を参照してください。

変数

#

Swift と比較すると、Dart で変数を宣言および初期化する方法は少し異なります。変数の宣言は常に、変数の型、var キーワード、または final キーワードで始まります。Swift と同様に、Dart は型推論をサポートしており、コンパイラは変数に割り当てられた値に基づいて型を推論します。

dart
// String-typed variable.
String name = 'Bob';

// Immutable String-typed variable.
final String name = 'Bob';

// This is the same as `String name = 'Bob';`
// since Dart infers the type to be String.
var name = 'Bob';

// And this is the same as `final String name = 'Bob';`.
final name = 'Bob';

各 Dart ステートメントはセミコロンで終わり、ステートメントの終了を示します。Dart では var を明示的な型に置き換えることができます。ただし、慣例として、アナライザーが型を暗黙的に推論できる場合は var が推奨されます

dart
// Declare a variable first:
String name; 
// Initialize the variable later:
name = 'bob';
// Declare and initialize a variable at once with inference:
var name = 'bob';

上記の Dart コードの Swift 相当は次のようになります。

swift
// Declare a variable first: 
var name: String
// Initialize the variable later
name = "bob"

// Declare and initialize a variable at once with inference:
var name = "bob"

Dart では、明示的な型を持たない変数が宣言後に初期化される場合、その型はすべてをキャッチする dynamic 型として推論されます。同様に、型が自動的に推論されない場合、デフォルトで dynamic 型になります。**これにより、すべての型安全性が失われます**。したがって、Dart リンターは警告を生成することでこれを推奨しません。変数が任意の型を持つことを許可することを意図している場合は、dynamic よりも Object? に割り当てる方が推奨されます。

詳細については、Dart 言語ツアーの 変数セクション を参照してください。

Final

#

Dart の final キーワードは、変数が一度だけ設定できることを示します。これは Swift の let キーワードに似ています。

Dart と Swift の両方で、final 変数は、宣言ステートメントまたは初期化リストのいずれかで一度だけ初期化できます。2 回目の値の代入はコンパイル時エラーになります。次のコード スニペットはどちらも有効ですが、その後 name を設定するとコンパイル エラーが発生します。

dart
final String name;
if (b1) {
  name = 'John';
} else {
  name = 'Jane';
}
swift
let name: String
if (b1) {
  name = "John"
} else {
  name = "Jane"
}

Const

#

final に加えて、Dart には const キーワードもあります。const の利点の 1 つは、コンパイル時に完全に評価され、アプリケーションの存続期間中に変更できないことです。

dart
const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

クラスレベルで定義された const 変数は、static const としてマークする必要があります。

dart
class StandardAtmosphere {
  static const bar = 1000000; // Unit of pressure (dynes/cm2)
  static const double atm = 1.01325 * bar; // Standard atmosphere
}

const キーワードは定数変数の宣言のためだけではなく、定数値を作成するためにも使用できます。

dart
var foo = const ['one', 'two', 'three'];
foo.add('four'); // Error: foo contains a constant value.
foo = ['apple', 'pear']; // This is allowed as foo itself isn't constant.
foo.add('orange'); // Allowed as foo no longer contains a constant value.

上記の例では、const 値 (リスト内の要素の追加、更新、削除) を変更することはできませんが、foo に新しい値を割り当てることはできます。foo に新しい (定数ではない) リストが割り当てられた後、リストの内容を**追加、更新、または削除**できるようになります。

final フィールドに定数値を割り当てることもできます。定数コンテキストで final フィールドを使用することはできませんが、定数を使用することはできます。たとえば、次のようになります。

dart
final foo1 = const [1, 2, 3];
const foo2 = [1, 2, 3]; // Equivalent to `const [1, 2, 3]`
const bar2 = foo2; // OK
const bar1 = foo1; // Compile-time error, `foo1` isn't constant

const コンストラクタを定義することもできます。これにより、これらのクラスは不変 (変更不可) になり、これらのクラスのインスタンスをコンパイル時定数として作成できるようになります。詳細については、const コンストラクタ を参照してください。

組み込み型

#

Dart には、プラットフォーム ライブラリに多数の型が含まれています。たとえば、次のものがあります。

  • 基本的な値型、たとえば
    • 数値 (numintdouble)
    • 文字列 (String)
    • ブール値 (bool)
    • null 値 (Null)
  • コレクション
    • リスト/配列 (List)
    • セット (Set)
    • マップ/辞書 (Map)

詳細については、Dart 言語ツアーの 組み込み型 を参照してください。

数値

#

Dart は、数値を保持するために 3 つの数値型を定義しています。

num
汎用 64 ビット数値型。
int
プラットフォーム依存の整数。ネイティブコードでは 64 ビット 2 の補数整数です。Web では、非小数部 64 ビット浮動小数点数です。
double
64 ビット浮動小数点数。

Swift とは異なり、符号なし整数用の特別な型はありません。

これらの型はすべて Dart API のクラスでもあります。intdouble の両方の型は、親クラスとして num を共有しています。

Object is the parent of num, which is the parent of int and double

数値値は技術的にはクラスインスタンスであるため、独自のユーティリティ関数を公開するという利便性があります。このため、たとえば intdouble に変換できます。

dart
int intVariable = 3;
double doubleVariable = intVariable.toDouble();

Swift では、専用のイニシャライザーを使用して同じことが実現されます。

swift
var intVariable: Int = 3
var doubleVariable: Double = Double(intVariable)

リテラル値の場合、Dart は整数リテラルを自動的に double 値に変換します。次のコードは問題ありません。

dart
double doubleValue = 3;

Swift とは異なり、Dart では、整数値を浮動小数点数と比較するために等価演算子 (==) を使用できます。次のように。

dart
int intVariable = 3;
double doubleVariable = 3.0;
print(intVariable == doubleVariable); // true

このコードは true を出力します。ただし、Dart では、Web とネイティブプラットフォーム間の数値の基盤となる実装が異なります。Dart における数値 ページでは、これらの違いを詳しく説明し、違いが問題にならないようにコードを書く方法を示しています。

文字列

#

Swift と同様に、Dart は String 型を使用して一連の文字を表しますが、Dart は 1 文字を表す Character 型をサポートしていません。String は単一引用符または二重引用符で定義できますが、**単一引用符が推奨されます**。

dart
String c = 'a'; // There isn't a specialized "Character" type
String s1 = 'This is a String';
String s2 = "This is also a String";
swift
let c: Character = "a"
let s1: String = "This is a String"
let s2: String = "This is also a String"

特殊文字のエスケープ

#

Dart での特殊文字のエスケープは、Swift (およびほとんどの他の言語) と似ています。特殊文字を含めるには、バックスラッシュ文字を使用してエスケープします。

次のコードはいくつかの例を示しています。

dart
final singleQuotes = 'I\'m learning Dart'; // I'm learning Dart
final doubleQuotes = "Escaping the \" character"; // Escaping the " character
final unicode = '\u{1F60E}'; // 😎,  Unicode scalar U+1F60E

4 桁の 16 進数値も直接使用できます (例: \u2665)。ただし、波括弧も機能します。Unicode 文字の操作の詳細については、Dart 言語ツアーの ルーンとグラフームクラスター を参照してください。

文字列の連結と複数行の定義

#

Dart と Swift の両方で、複数行文字列の改行をエスケープできます。これにより、ソースコードを読みやすく保ちながら、String を 1 行で出力できます。Dart には、複数行文字列を定義する方法がいくつかあります。

  1. 暗黙的な文字列連結の使用: 隣接する文字列リテラルは、複数行にまたがっていても自動的に連結されます。

    dart
    final s1 = 'String '
      'concatenation'
      " even works over line breaks.";
  2. 複数行文字列リテラルの使用: 文字列の各側に 3 つの引用符 (単一または二重) を使用すると、リテラルは複数行にまたがることが許可されます。

    dart
    final s2 = '''You can create
    multiline strings like this one.''';
    
    final s3 = """This is also a
    multiline string.""";
  3. Dart は、+ 演算子を使用した文字列の連結もサポートしています。これは、文字列リテラルと文字列変数の両方で機能します。

    dart
    final name = 'John';
    final greeting = 'Hello ' + name + '!';

文字列補間

#

${<expression>} 構文を使用して、文字列リテラルに式を挿入します。Dart は、式が単一の識別子である場合に波括弧を省略できるようにすることで、これを拡張します。

dart
var food = 'bread';
var str = 'I eat $food'; // I eat bread
var str = 'I eat ${bakery.bestSeller}'; // I eat bread

Swift では、変数または式を丸括弧で囲み、バックスラッシュを前に付けることで、同じ結果を得ることができます。

swift
let s = "string interpolation"
let c = "Swift has \(s), which is very handy."

生の文字列

#

Swift と同様に、Dart でも生の文字列を定義できます。生の文字列はエスケープ文字を無視し、文字列に含まれる特殊文字を含みます。Dart では、次の例のように、文字列リテラルの前に文字 r を付けることでこれを行うことができます。

dart
// Include the \n characters.
final s1 = r'Includes the \n characters.';
// Also includes the \n characters.
final s2 = r"Also includes the \n characters.";

final s3 = r'''
  The \n characters are also included
  when using raw multiline strings.
  ''';
final s4 = r"""
  The \n characters are also included
  when using raw multiline strings.
  """;
swift
let s1 = #"Includes the \n characters."#
let s2 = #"""
  The \n characters are also included
  when using raw multiline strings.
  """#

等価性

#

Swift と同様に、Dart の等価演算子 (==) は、2 つの文字列が等しいかどうかを比較します。2 つの文字列は、コードユニットの同じシーケンスを含んでいる場合に等しくなります。

dart
final s1 = 'String '
  'concatenation'
  " works even over line breaks.";
assert(s1 ==
  'String concatenation works even over '
  'line breaks.');

一般的に使用される API

#

Dart は文字列に対していくつかの一般的な API を提供しています。たとえば、Dart と Swift はどちらも isEmpty を使用して文字列が空かどうかを確認できます。toUpperCasetoLowerCase のような他の便利なメソッドもあります。詳細については、Dart 言語ツアーの 文字列 を参照してください。

ブール値

#

ブール値は、Dart (bool) と Swift (Bool) の両方でバイナリ値を表します。

Null 安全性

#

Dart はサウンド null セーフティを強制します。デフォルトでは、型は nullable としてマークされない限り、null 値を許可しません。Dart は、型の末尾に疑問符 (?) を付けてこれを示します。これは Swift の *オプショナル* と同様に機能します。

Null許容演算子

#

Dart は null 許容性を処理するためのいくつかの演算子をサポートしています。null 合体演算子 (??) とオプショナルチェイン演算子 (?.) は Dart で利用可能であり、Swift と同じように機能します。

dart
a = a ?? b;
swift
let str: String? = nil
let count = str?.count ?? 0

さらに、Dart は cascade 演算子の null セーフバージョン (?..) を提供します。この演算子は、ターゲット式が null に解決された場合、すべての操作を無視します。Dart は null 代入演算子 (??=) も提供しており、これは Swift にはありません。null 許容型の変数に現在の値が null である場合、この演算子は変数に値を割り当てます。a ??= b; として表現される場合、次のことのショートカットとして機能します。

dart
a = a ?? b;

// Assign b to a if a is null; otherwise, a stays the same
a ??= b;
swift
a = a ?? b

! 演算子 (「強制アンラップ」とも呼ばれます)

#

null 許容変数または式が実際には non-null であると安全に仮定できる場合、コンパイラにコンパイル時エラーを抑制するように指示できます。これは、接尾辞 ! 演算子を使用して、式に接尾辞として配置することで行われます。(Dart の「not」演算子と混同しないでください。同じ記号を使用します)

dart
int? a = 5;

int b = a; // Not allowed.
int b = a!; // Allowed.

実行時に、a が null であることが判明した場合、実行時エラーが発生します。

?. 演算子と同様に、オブジェクトのプロパティまたはメソッドにアクセスする場合は、! 演算子を使用します。

dart
myObject!.someProperty;
myObject!.someMethod();

実行時に myObjectnull である場合、実行時エラーが発生します。

Late フィールド

#

late キーワードはクラスフィールドに割り当てることができ、それらが後で初期化されることを示しますが、non-nullable のままです。これは Swift の「暗黙的にアンラップされたオプショナル」に似ています。これは、変数が初期化される前に決して参照されない場合で、後で初期化できるようにする場合に役立ちます。non-nullable の late フィールドには、後で null を割り当てることはできません。また、non-nullable の late フィールドは、初期化される前に参照されると実行時エラーが発生します。これは、正常に動作するアプリでは避けたいシナリオです。

dart
// Using null safety:
class Coffee {
  late String _temperature;

  void heat() { _temperature = 'hot'; }
  void chill() { _temperature = 'iced'; }

  String serve() => _temperature + ' coffee';
}

この場合、_temperatureheat() または chill() を呼び出した後にのみ初期化されます。serve() が他のものより先に呼び出された場合、実行時例外が発生します。_temperature は決して null にならないことに注意してください。

初期化子と組み合わせた場合に lazy 初期化を行うために late キーワードを使用することもできます。

dart
class Weather {
  late int _temperature = _readThermometer();
}

この場合、_readThermometer() は、初期化時ではなく、フィールドが最初にアクセスされたときにのみ実行されます。

Dart のもう 1 つの利点は、late キーワードを使用して final 変数の初期化を遅延させることです。late としてマークしながら final 変数をすぐに初期化する必要はありませんが、それでも一度だけ初期化できます。2 回目の代入は実行時エラーになります。

dart
late final int a;
a = 1;
a = 2; // Throws a runtime exception because
       // "a" is already initialized.

関数

#

Swift は main.swift ファイルをアプリのエントリーポイントとして使用します。Dart は main 関数をアプリのエントリーポイントとして使用します。すべてのプログラムは、実行可能であるために main 関数を持つ必要があります。たとえば、次のようになります。

dart
void main() {
  // main function is the entry point
  print("hello world");
}
swift
// main.swift file is the entry point
print("hello world")

Dart は Tuples をサポートしていません (ただし、pub.dev にはいくつかのタプルパッケージ があります)。関数が複数の値を返す必要がある場合、それらをリスト、セット、またはマップのようなコレクションにラップするか、インスタンスがこれらの値を含むものを返せるラッパー クラスを記述できます。これについては、コレクション および クラス のセクションで詳しく説明します。

例外とエラー処理

#

Swift と同様に、Dart の関数とメソッドは 例外エラー の両方を処理することをサポートしています。Dart の *エラー* は通常、プログラマーのミスやスタックオーバーフローのようなシステム障害を表します。Dart エラーはキャッチされるべきではありません。一方、Dart の *例外* は回復可能な障害を表し、キャッチされることを意図しています。たとえば、実行時にコードがストリーミング フィードにアクセスしようとするかもしれませんが、代わりに例外を受け取り、キャッチされないとアプリの終了につながります。Dart で例外を管理するには、関数呼び出しを try-catch ブロックでラップします。

dart
try {
  // Create audio player object
  audioPlayer = AVAudioPlayer(soundUrl);
            
  // Play the sound
  audioPlayer.play();
}
catch {
  // Couldn't create audio player object, log the exception
  print("Couldn't create the audio player for file $soundFilename");
}

同様に、Swift は do-try-catch ブロックを使用します。たとえば、次のようになります。

swift
do {
  // Create audio player object
  audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
            
  // Play the sound
  audioPlayer?.play()
}
catch {
  // Couldn't create audio player object, log the error
  print("Couldn't create the audio player for file \(soundFilename)")
}

try-catch ブロックは、同期および非同期 Dart コードの両方で使用できます。詳細については、Error および Exception クラスのドキュメントを参照してください。

パラメータ

#

Swift と同様に、Dart は関数で名前付きパラメータをサポートしています。ただし、Swift とは異なり、Dart ではこれらはデフォルトではありません。Dart のデフォルトのパラメータタイプは位置パラメータです。

dart
int multiply(int a, int b) {
  return a * b;
}

Swift での相当するものは、引数ラベルの必要性をなくすために、パラメータの前にアンダースコアを付けます。

swift
func multiply(_ a: Int, _ b: Int) -> Int {
  return a * b
}

Dart で名前付きパラメータを作成する場合、それらを位置パラメータの後に、別の波括弧ブロックで定義します。

dart
int multiply(int a, int b, {int c = 1, int d = 1}) {
  return a * b * c * d;
}

// Calling a function with both required and named parameters
multiply(3, 5); // 15
multiply(3, 5, c: 2); // 30
multiply(3, 5, d: 3); // 45
multiply(3, 5, c: 2, d: 3); // 90
swift
// The Swift equivalent
func multiply(_ a: Int, _ b: Int, c: Int = 1, d: Int = 1) -> Int {
  return a * b * c * d
}

名前付きパラメータには、次のいずれかを含める必要があります。

  • デフォルト値
  • 型名の末尾に ? を付けて、型を nullable に設定する
  • 変数型の前に required キーワード

Nullable 型の詳細については、null セーフティ を参照してください。

Dart で名前付きパラメータを必須としてマークするには、required キーワードを前に付ける必要があります。

dart
int multiply(int a, int b, { required int c }) {
  return a * b * c;
}
// When calling the function, c has to be provided
multiply(3, 5, c: 2);

3 番目のパラメータタイプは *オプショナル位置パラメータ* です。名前が示すように、これらはデフォルトの位置パラメータに似ていますが、関数呼び出し時に省略できます。これらは、必須の位置パラメータの後ろにリストする必要があり、名前付きパラメータと併用することはできません。

dart
int multiply(int a, int b, [int c = 1, int d = 1]) {
  return a * b * c * d;
}
// Calling a function with both required and optional positioned parameters.
multiply(3, 5); // 15
multiply(3, 5, 2); // 30
multiply(3, 5, 2, 3); // 90
swift
// The Swift equivalent
func multiply(_ a: Int, _ b: Int, _ c: Int = 1, _ d: Int = 1) -> Int {
  return a * b * c * d
}

名前付きパラメータと同様に、オプショナル位置パラメータには、デフォルト値または nullable 型のいずれかが必要です。

ファーストクラス関数

#

Swift と同様に、Dart 関数も ファーストクラス市民 であるため、他のオブジェクトと同じように扱われます。たとえば、次のコードは、関数から関数を返す方法を示しています。

dart
typedef int MultiplierFunction(int value);
// Define a function that returns another function
MultiplierFunction multiplyBy(int multiplier) {
  return (int value) {
    return value * multiplier;
  };
}
// Call function that returns new function
MultiplierFunction multiplyByTwo = multiplyBy(2);
// Call the new function
print(multiplyByTwo(3)); // 6
swift
// The Swift equivalent of the Dart function below
// Define a function that returns a closure
typealias MultiplierFunction = (Int) -> (Int)

func multiplyBy(_ multiplier: Int) -> MultiplierFunction {
  return { $0 * multiplier} // Returns a closure
}

// Call function that returns a function
let multiplyByTwo = multiplyBy(2)
// Call the new function
print(multiplyByTwo(3)) // 6

無名関数

#

Dart の *匿名関数* は、構文の違いを除けば、Swift のクロージャとほぼ同じように機能します。名前付き関数と同様に、匿名関数を他の値と同じように渡すことができます。たとえば、匿名関数を変数に格納したり、別の関数に引数として渡したり、別の関数から返したりできます。

Dart には匿名関数を宣言する方法が 2 つあります。最初の方法は波括弧を使用し、他の関数と同じように機能します。複数行の使用が可能で、値を返すには return ステートメントが必要です。

dart
// Multi line anonymous function
[1,2,3].map((element) { 
  return element * 2; 
}).toList(); // [2, 4, 6]
swift
  // Swift equivalent anonymous function
  [1, 2, 3].map { $0 * 2 }

もう 1 つの方法は、アロー関数を使用することです。これは、構文で使用される矢印のような記号にちなんで名付けられています。関数本体が単一の式のみを含み、値が返される場合にこのショートカット構文を使用できます。これにより、暗黙的に示されるため、波括弧や return ステートメントは不要になります。

dart
// Single-line anonymous function
[1,2,3].map((element) => element * 2).toList(); // [2, 4, 6]

アロー構文または波括弧のどちらを選択するかは、匿名関数だけでなく、すべての関数で利用できます。

dart
multiply(int a, int b) => a * b;

multiply(int a, int b) {
  return a * b;
}

ジェネレーター関数

#

Dart は、項目を遅延的に構築する反復可能コレクションを返す ジェネレーター関数 をサポートしています。yield キーワードを使用して最終的なコレクションに項目を追加するか、yield* を使用して項目のコレクション全体を追加します。

次の例は、基本的なジェネレーター関数を記述する方法を示しています。

dart
Iterable<int> listNumbers(int n) sync* {
  int k = 0;
  while (k < n) yield k++;
}

// Returns an `Iterable<int>` that iterates
// through 0, 1, 2, 3, and 4.
print(listNumbers(5));

Iterable<int> doubleNumbersTo(int n) sync* {
  int k = 0;
  while (k < n) { 
    yield* [k, k]; 
    k++;
  }
}

print(doubleNumbersTo(3)); // Returns an iterable with [0, 0], [1, 1], and [2, 2].

これは **同期** ジェネレーター関数の例です。 **非同期** ジェネレーター関数も定義でき、これは反復可能ではなくストリームを返します。並行処理セクションで詳しく学びます。

ステートメント

#

このセクションでは、Swift のステートメントにおける類似点と相違点について説明します。

制御フロー (if/else, for, while, switch)

#

Dart のすべての制御フロー ステートメントは、いくつかの構文の違いを除けば、Swift の対応するものと非常によく似ています。

if

#

Swift とは異なり、Dart の if ステートメントは条件の周りに丸括弧が必要です。Dart スタイル ガイドはフロー制御ステートメントの周りに波括弧を使用することを推奨していますが (以下に示すように)、else 節のない if ステートメントがあり、if ステートメント全体が 1 行に収まる場合は、必要に応じて波括弧を省略できます。

dart
var a = 1;
// Parentheses for conditions are required in Dart.
if (a == 1) {
  print('a == 1');
} else if (a == 2) {
  print('a == 2');
} else {
  print('a != 1 && a != 2');
}

// Curly braces are optional for single line `if` statements.
if (a == 1) print('a == 1');
swift
let a = 1;
if a == 1 {
  print("a == 1")
} else if a == 2 {
  print("a == 2")
} else {
  print("a != 1 && a != 2")
}

for(-in)

#

Swift では、for ループはコレクションをループするためにのみ使用されます。コードのチャンクを複数回ループするために、Swift は範囲をループできます。Dart は範囲を定義するための構文をサポートしていませんが、コレクションをループする for-in に加えて、標準の for ループが含まれています。

Dart の for-in ループは、Swift の対応するものと同じように機能し、List の例のように、Iterable である任意の値をループできます。

dart
var list = [0, 1, 2, 3, 4];
for (var i in list) {
  print(i);
}
swift
let array = [0, 1, 2, 3, 4]
for i in array {
  print(i)
}

Dart には、Swift が辞書に対して持つような、マップをループできる for-in ループの特別な構文はありません。同様の効果を達成するには、マップのエントリを Iterable 型として抽出できます。または、Map.forEach を使用することもできます。

dart
Map<String, int> dict = {
  'Foo': 1,
  'Bar': 2
};
for (var e in dict.entries) {
  print('${e.key}, ${e.value}');
}
dict.forEach((key, value) {
  print('$key, $value');
});
swift
var dict:[String:Int] = [
  "Foo":1,
  "Bar":2
]
for (key, value) in dict {
   print("\(key),\(value)")
}

演算子

#

Swift とは異なり、Dart は新しい演算子の追加を許可しませんが、operator キーワードを使用して既存の演算子をオーバーロードすることはできます。たとえば、次のようになります。

dart
class Vector {
  final double x;
  final double y;
  final double z;

  Vector operator +(Vector v) {
    return Vector(x: x + v.x, y: y + v.y, z: z+v.z);
  }
}
swift
struct Vector {
  let x: Double
  let y: Double
  let z: Double
}

func +(lhs: Vector, rhs: Vector) -> Vector {
  return Vector(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z)
}

...

算術演算子

#

ほとんどの場合、算術演算子は Swift と Dart で同じように動作しますが、除算演算子 (/) が顕著な例外です。Swift (および他の多くのプログラミング言語) では、let x = 5/2 の結果は 2 (整数) です。Dart では、int x = 5/2, の結果は 2.5 (浮動小数点値) になります。整数結果を得るには、Dart の切り捨て除算演算子 (~/) を使用します。

++ および 演算子は Swift の以前のバージョンに存在していましたが、Swift 3.0 では削除されました。Dart の相当するものは同じように機能します。たとえば、次のようになります。

dart
assert(2 + 3 == 5);
assert(2 - 3 == -1);
assert(2 * 3 == 6);
assert(5 / 2 == 2.5); // Result is a double
assert(5 ~/ 2 == 2); // Result is an int
assert(5 % 2 == 1); // Remainder

a = 0;
b = ++a; // Increment a before b gets its value.
assert(a == b); // 1 == 1

a = 0;
b = a++; // Increment a AFTER b gets its value.
assert(a != b); // 1 != 0

型テスト演算子

#

テスト演算子の実装は、両言語間で少し異なります。

意味Dart 演算子Swift 相当
型キャスト (下記参照)expr as Texpr as! T
expr as? T
オブジェクトが指定された型を持っている場合は trueexpr is Texpr is T
オブジェクトが指定された型を持っていない場合は trueexpr is! T!(expr is T)

obj is T の結果は、objT で指定された型のサブタイプである場合に true になります。たとえば、obj is Object? は常に true です。

オブジェクトが T 型であると確信している場合にのみ、型キャスト演算子を使用してオブジェクトを特定の型にキャストします。たとえば、次のようになります。

dart
(person as Employee).employeeNumber = 4204583;

Dart には単一型キャスト演算子しかありません。これは Swift の as! 演算子のように機能します。Swift の as? 演算子に相当するものはありません。

swift
(person as! Employee).employeeNumber = 4204583;

オブジェクトが T 型であると確信できない場合は、使用する前に is T を使用して確認してください。

Dart では、型昇格は if ステートメントのスコープ内でローカル変数の型を更新します。これは null チェックでも発生します。昇格はローカル変数にのみ適用され、インスタンス変数には適用されません。

dart
if (person is Employee) {
  person.employeeNumber = 4204583;
}
swift
// Swift requires the variable to be cast.
if let person = person as? Employee {
  print(person.employeeNumber) 
}

論理演算子

#

論理演算子 (AND (&&)、OR (||)、NOT (!) など) は、両言語で同一です。たとえば、次のようになります。

dart
if (!done && (col == 0 || col == 3)) {
  // ...Do something...
}

ビット演算子およびシフト演算子

#

ビット演算子は、両言語でほとんど同一です。

dart
final value = 0x22;
final bitmask = 0x0f;

assert((value & bitmask) == 0x02); // AND
assert((value & ~bitmask) == 0x20); // AND NOT
assert((value | bitmask) == 0x2f); // OR
assert((value ^ bitmask) == 0x2d); // XOR
assert((value << 4) == 0x220); // Shift left
assert((value >> 4) == 0x02); // Shift right
assert((-value >> 4) == -0x03); // Shift right // Result may differ on the web

条件演算子

#

Dart と Swift の両方に、if-else ステートメントが必要になる可能性のある式を評価するための条件演算子 (?:) が含まれています。

dart
final displayLabel = canAfford ? 'Please pay below' : 'Insufficient funds';
swift
let displayLabel = canAfford ?  "Please pay below" : "Insufficient funds"

カスケード (.. 演算子)

#

Swift とは異なり、Dart はカスケード演算子を使用したカスケードをサポートしています。これにより、単一のオブジェクトに対して複数のメソッド呼び出しまたはプロパティ代入をチェーンできます。

次の例は、カスケード演算子を使用して、単一のチェーン内で、複数のプロパティの値を設定し、その後、新しく構築されたオブジェクトで複数のメソッドを呼び出す方法を示しています。

dart
Animal animal = Animal()
  ..name = 'Bob'
  ..age = 5
  ..feed()
  ..walk();

print(animal.name); // "Bob"
print(animal.age); // 5
swift
var animal = Animal()
animal.name = "Bob"
animal.age = 5
animal.feed()
animal.walk()

print(animal.name)
print(animal.age)

コレクション

#

このセクションでは、Swift のコレクション型と、それらが Dart の対応するものと比較してどうなるかについて説明します。

リスト

#

List リテラルは、Swift の配列と同じように、角括弧を使用してコンマで区切って Dart で定義されます。両言語間の構文は非常によく似ていますが、いくつかの微妙な違いがあります。次の例で示します。

dart
final List<String> list1 = <String>['one', 'two', 'three']; // Initialize list and specify full type
final list2 = <String>['one', 'two', 'three']; // Initialize list using shorthand type
final list3 = ['one', 'two', 'three']; // Dart can also infer the type
swift
var list1: Array<String> = ["one", "two", "three"] // Initialize array and specify the full type
var list2: [String] = ["one", "two", "three"] // Initialize array using shorthand type
var list3 = ["one", "two", "three"] // Swift can also infer the type

次のコードサンプルは、Dart List で実行できる基本的なアクションの概要を示しています。最初の例は、index 演算子を使用してリストから値を取得する方法を示しています。

dart
final fruits = ['apple', 'orange', 'pear'];
final fruit = fruits[1];

リストの末尾に値を追加するには add メソッドを使用します。別の List を追加するには addAll メソッドを使用します。

dart
final fruits = ['apple', 'orange', 'pear'];
fruits.add('peach');
fruits.addAll(['kiwi', 'mango']);

完全な List API については、List クラス のドキュメントを参照してください。

変更不可

#

配列を定数 (Swift の let) に割り当てると、配列は不変になり、サイズと内容を変更できません。定数に新しい配列を割り当てることもできません。

Dart では、これは少し異なり、ニーズに応じていくつかのオプションから選択できます。

  • リストがコンパイル時定数であり、変更されないようにするには、const キーワードを使用します。
    const fruits = ['apple', 'orange', 'pear'];
  • リストを final フィールドに割り当てます。これは、リスト自体がコンパイル時定数である必要がなく、フィールドが別のリストでオーバーライドされないことを保証します。ただし、リストのサイズまたは内容を変更することは still できます。
    final fruits = ['apple', 'orange', 'pear'];
  • 変更不可コンストラクタ (次の例に示す) を使用して final List を作成します。これにより、サイズまたは内容を変更できない List が作成され、Swift の定数 Array と同様に動作します。
dart
final fruits = List<String>.unmodifiable(['apple', 'orange', 'pear']);
swift
let fruits = ["apple", "orange", "pear"]

Spread 演算子

#

Dart のもう 1 つの便利な機能は、**spread 演算子** (...) と **null 許容 spread 演算子** (...?) で、コレクションに複数の値を挿入する簡潔な方法を提供します。

たとえば、spread 演算子 (...) を使用して、リストのすべての値を別のリストに挿入できます。次のように。

dart
final list = [1, 2, 3];
final list2 = [0, ...list]; // [ 0, 1, 2, 3 ]
assert(list2.length == 4);

Swift には spread 演算子はありませんが、上記の 2 行目の相当するものは次のようになります。

swift
let list2 = [0] + list

spread 演算子の右側の式が null になる可能性がある場合は、null 許容 spread 演算子 (...?) を使用して例外を回避できます。

dart
List<int>? list;
final list2 = [0, ...?list]; //[ 0 ]
assert(list2.length == 1);
swift
let list2 = [0] + list ?? []

セット

#

Dart と Swift はどちらもリテラルを使用して Set を定義することをサポートしています。セットはリストと同じ方法で定義されますが、角括弧ではなく波括弧を使用します。セットは順序付けられていないコレクションであり、一意の項目のみを含みます。これらの項目のユニークさはハッシュコードを使用して実装されます。つまり、オブジェクトが Set に格納されるにはハッシュ値が必要です。すべての Dart オブジェクトにはハッシュコードが含まれていますが、Swift ではオブジェクトを Set に格納する前に明示的に Hashable プロトコルを適用する必要があります。

次のコード スニペットは、Dart と Swift で Set を初期化する際の違いを示しています。

dart
final abc = {'a', 'b', 'c'};
swift
var abc: Set<String> = ["a", "b", "c"]

空のセットを Dart で空の波括弧 ({}) を指定して作成しないでください。これは空の Map を作成します。空の Set を作成するには、{} 宣言の前に型引数を付けるか、{}Set 型の変数に割り当てます。

dart
final names = <String>{};
Set<String> alsoNames = {}; // This works, too.
// final names = {}; // Creates an empty map, not a set.

変更不可

#

List と同様に、Set にも変更不可バージョンがあります。たとえば、次のようになります。

dart
final abc = Set<String>.unmodifiable(['a', 'b', 'c']);
swift
let abc: Set<String> = ["a", "b", "c"]

マップ

#

Dart の Map 型は、Swift の Dictionary 型と比較できます。どちらの型もキーと値を関連付けます。これらのキーと値は任意の型のオブジェクトにできます。各キーは 1 回だけ出現しますが、同じ値を複数回使用できます。

どちらの言語でも、辞書はハッシュテーブルに基づいているため、キーはハッシュ可能である必要があります。Dart では、すべてのオブジェクトにハッシュが含まれていますが、Swift では、オブジェクトを Dictionary に格納する前に明示的に Hashable プロトコルを適用する必要があります。

リテラルを使用して作成された、いくつかの単純な Map および Dictionary の例を次に示します。

dart
final gifts = {
 'first': 'partridge',
 'second': 'turtle doves',
 'fifth': 'golden rings',
};

final nobleGases = {
 2: 'helium',
 10: 'neon',
 18: 'argon',
};
swift
let gifts = [
   "first": "partridge",
   "second": "turtle doves",
   "fifth": "golden rings",
]

let nobleGases = [
   2: "helium",
   10: "neon",
   18: "argon",
]

次のコードサンプルは、Dart Map で実行できる基本的なアクションの概要を示しています。最初の例は、key 演算子を使用して Map から値を取得する方法を示しています。

dart
final gifts = {'first': 'partridge'};
final gift = gifts['first']; // 'partridge'

containsKey メソッドを使用して、キーが Map に既に存在するかどうかを確認します。

dart
final gifts = {'first': 'partridge'};
assert(gifts.containsKey('fifth')); // false

インデックス代入演算子 ([]=) を使用して、Map のエントリを追加または更新します。Map にまだキーが含まれていない場合、エントリが追加されます。キーが存在する場合、エントリの値が更新されます。

dart
final gifts = {'first': 'partridge'};
gifts['second'] = 'turtle'; // Gets added
gifts['second'] = 'turtle doves'; // Gets updated

Map からエントリを削除するには remove メソッドを使用し、指定されたテストを満たすすべてのエントリを削除するには removeWhere メソッドを使用します。

dart
final gifts = {'first': 'partridge'};
gifts.remove('first');
gifts.removeWhere((key, value) => value == 'partridge');

クラス

#

Dart はインターフェース型を定義していません — **任意の** クラスをインターフェースとして使用できます。インターフェースのみを導入したい場合は、具体的なメンバーを持たない抽象クラスを作成します。これらのカテゴリをより詳細に理解するには、抽象クラス暗黙的なインターフェース、および クラスの拡張 のセクションのドキュメントを参照してください。

Dart は値型のサポートを提供していません。 組み込み型 セクションで述べたように、Dart のすべての型は参照型です (プリミティブでさえも)。これは、Dart が struct キーワードを提供していないことを意味します。

Enum

#

列挙型 (列挙または enums とも呼ばれます) は、固定数の定数値を表すために使用される特別な種類のクラスです。Enums は長い間 Dart 言語の一部でしたが、Dart 2.17 はメンバーに対する強化された enums サポートを追加しました。これは、状態を保持するフィールド、その状態を設定するコンストラクタ、機能を持つメソッド、および既存のメンバーをオーバーライドすることさえできることを意味します。詳細については、Dart 言語ツアーの 強化された enums の宣言 を参照してください。

コンストラクタ

#

Dart のクラスコンストラクタは Swift のクラスイニシャライザと似たように機能します。ただし、Dart では、クラスプロパティを設定するためにより多くの機能を提供します。

標準コンストラクタ

#

標準クラスコンストラクタは、Swift のイニシャライザと宣言および呼び出しの両方で非常によく似ています。init キーワードの代わりに、Dart は完全なクラス名を使用します。以前は新しいクラスインスタンスを作成するために必要だった new キーワードは、現在はオプションであり、推奨されなくなりました。

dart
class Point {
  double x = 0;
  double y = 0;

  Point(double x, double y) {
    // There's a better way to do this in Dart, stay tuned.
    this.x = x;
    this.y = y;
  }
}

// Create a new instance of the Point class
Point p = Point(3, 5);

コンストラクタパラメータ

#

コンストラクタで this を使用してすべてのクラスフィールドを割り当てるコードを書くのは冗長なことが多いため、Dart にはこれを容易にするための構文糖があります。

dart
class Point {
  double x;
  double y;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}

// Create a new instance of the Point class
Point p = Point(3, 5);

関数と同様に、コンストラクタはオプショナルな位置または名前付きパラメータを受け取ることもできます。

dart
class Point {
  ...
  // With an optional positioned parameter
  Point(this.x, [this.y = 0]);
  // With named parameters
  Point({required this.y, this.x = 0});
  // With both positional and named parameters
  Point(int x, int y, {int scale = 1}) {
    ...
  }
  ...
}

初期化子リスト

#

初期化リストも使用できます。これは、コンストラクタ パラメータで this を使用して直接設定されたフィールドの後に実行されますが、コンストラクタ本体の前に実行されます。

dart
class Point {
  ...
  Point(Map<String, double> json)
      : x = json['x']!,
        y = json['y']! {
    print('In Point.fromJson(): ($x, $y)');
  }
  ...
}

初期化リストは、assert を使用するのに適した場所です。

名前付きコンストラクタ

#

Swift とは異なり、Dart はクラスに名前を付けることで複数のコンストラクタを持つことを許可します。1 つの無名コンストラクタを使用するオプションがありますが、追加のコンストラクタは名前付きである必要があります。クラスは名前付きコンストラクタのみを持つこともできます。

dart
class Point {
  double x;
  double y;

  Point(this.x, this.y);

  // Named constructor
  Point.fromJson(Map<String, double> json)
      : x = json['x']!,
        y = json['y']!;
}

constコンストラクタ

#

クラスインスタンスが常に不変 (変更不可) である場合、const コンストラクタを追加することでこれを強制できます。const コンストラクタを削除することは、クラスを使用するユーザーにとっては破壊的な変更なので、この機能は慎重に使用してください。コンストラクタを const として定義すると、クラスは変更不可になります。クラス内のすべての非静的フィールドは final としてマークする必要があります。

dart
class ImmutablePoint {
  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

これにより、そのクラスを定数値として使用することもでき、オブジェクトはコンパイル時定数になります。

dart
const ImmutablePoint origin = ImmutablePoint(0, 0);

コンストラクタのリダイレクト

#

コードの重複を防ぐため、またはパラメータに追加のデフォルトを追加するために、他のコンストラクタからコンストラクタを呼び出すことができます。

dart
class Point {
  double x, y;

  // The main constructor for this class.
  Point(this.x, this.y);

  // Delegates to the main constructor.
  Point.alongXAxis(double x) : this(x, 0);
}

ファクトリコンストラクタ

#

新しいクラスインスタンスを作成する必要がない場合は、ファクトリコンストラクタを使用できます。1 つの例は、キャッシュされたインスタンスを代わりに戻せる場合です。

dart
class Logger {
  static final Map<String, Logger> _cache =
    <String, Logger>{};
  
  final String name;
  
  // Factory constructor that returns a cached copy,
  // or creates a new one if it's not yet available.
  factory Logger(String name)=> _cache[name] ??= Logger._internal(name);
  // Private constructor used only in this library
  Logger._internal(this.name);
}

メソッド

#

Dart と Swift の両方で、メソッドはオブジェクトの動作を提供する関数です。

dart
void doSomething() { // This is a function
 // Implementation..
}

class Example {
 void doSomething() { // This is a method
   // Implementation..
 }
}
swift
func doSomething() { // This is a function
  // Implementation..
}

class Example {
  func doSomething() { // This is a method
    // Implementation..
  }
}

ゲッターとセッター

#

フィールド名の前に get または set キーワードを付けることで、getter と setter を定義できます。各インスタンスフィールドには暗黙的な getter があり、適切であれば setter もあることを思い出してください。Swift では、getset キーワードはプロパティ ステートメント内に定義する必要があり、ステートメントとしてのみ定義でき、式としては定義できないため、構文が少し異なります。

dart
class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => width = value - left;

  double get bottom => top + height;
  set bottom(double value) => height = value - top;
}
swift
class Rectangle {
 var left, top, width, height: Double;

 init(left: Double, top: Double, width: Double, height: Double) {
   self.left = left
   self.top = top
   self.width = width
   self.height = height
 }

 // Define two calculated properties: right and bottom.
 var right: Double {
   get {
     return left + width
   }
   set { width = newValue - left }
 }

 var bottom: Double {
   get {
     return top + height
   }
   set { height = newValue - top }
 }
}

抽象クラス

#

Dart には *抽象* クラスの概念があり、これは Swift ではサポートされていません。抽象クラスは直接インスタンス化できず、サブクラス化することしかできません。これにより、抽象クラスはインターフェース (Swift のプロトコルに相当) を定義するのに役立ちます。

抽象クラスには、実装を持たないメソッド宣言である *抽象* メソッドが含まれることがよくあります。非抽象サブクラスは、これらのメソッドをオーバーライドして適切な実装を提供することを強制されます。抽象クラスには、デフォルト実装を持つメソッドも含まれる場合があります。サブクラスは、抽象クラスを拡張する際にこれらのメソッドをオーバーライドしない場合、この実装を継承します。

抽象クラスを定義するには、abstract 修飾子を使用します。次の例は、抽象メソッドとデフォルト実装を含むメソッドを持つ抽象クラスを宣言しています。

dart
// This class is declared abstract and thus can't be instantiated.
abstract class AbstractContainer {
  void updateChildren(); // Abstract method.

  // Method with default implementation.
  String toString() => "AbstractContainer";
}

暗黙的なインターフェース

#

Dart 言語では、すべてのクラスは、クラスのすべてのインスタンスメンバーと、実装しているすべてのインターフェースを含むインターフェースを暗黙的に定義します。クラス AB の実装を継承せずに B の API をサポートするようにしたい場合、クラス AB インターフェースを実装する必要があります。

Dart とは異なり、Swift クラスはインターフェースを暗黙的に定義しません。インターフェースはプロトコルとして明示的に定義され、開発者によって実装される必要があります。

クラスは 1 つ以上のインターフェースを実装し、インターフェースで要求される API を提供できます。Dart と Swift はどちらもインターフェースを実装するさまざまな方法があります。たとえば、次のようになります。

dart
abstract class Animal {
  int getLegs();
  void makeNoise();
}

class Dog implements Animal {
  @override
  int getLegs() => 4;

  @override
  void makeNoise() => print('Woof woof');
}
swift
protocol Animal {
   func getLegs() -> Int;
   func makeNoise()
}

class Dog: Animal {
  func getLegs() -> Int {
    return 4;
  }

  func makeNoise() {
    print("Woof woof"); 
  }
}

クラスの拡張

#

Dart のクラス継承は Swift と非常によく似ています。Dart では、extends を使用してサブクラスを作成し、super を使用してスーパークラスを参照できます。

dart
abstract class Animal {
  // Define constructors, fields, methods...
}

class Dog extends Animal {
  // Define constructors, fields, methods...
}
swift
class Animal {
  // Define constructors, fields, methods...
}

class Dog: Animal {
  // Define constructors, fields, methods...
}

Mixin

#

Mixin は、コードがクラス間で機能を共有できるようにします。Mixin のフィールドとメソッドをクラスで使用でき、その機能はクラスの一部であるかのように使用できます。クラスは複数の Mixin を使用できます。これは、複数のクラスが同じ機能を共有している場合に役立ちます。互いに継承したり、共通の祖先を共有したりすることなく。

Swift は Mixin をサポートしていませんが、プロトコルと、プロトコルで指定されたメソッドのデフォルト実装を提供する拡張機能を作成することで、この機能を近似できます。このアプローチの主な問題は、Dart とは異なり、これらのプロトコル拡張機能は独自の状態を維持しないことです。

Mixin は、Object 以外のクラスを拡張せず、コンストラクタを持たない限り、通常のクラスと同様に宣言できます。with キーワードを使用して、クラスに 1 つ以上のコンマ区切りの Mixin を追加します。

次の例は、Dart でこの動作がどのように実現されるか、および Swift で同様の動作がどのように再現されるかを示しています。

dart
abstract class Animal {}

// Defining the mixins
mixin Flyer {
  fly() => print('Flaps wings');
}
mixin Walker {
  walk() => print('Walks legs');
}
  
class Bat extends Animal with Flyer {}
class Goose extends Animal with Flyer, Walker {}
class Dog extends Animal with Walker {}

// Correct calls
Bat().fly();
Goose().fly();
Goose().walk(); 
Dog().walk();

// Incorrect calls
Bat().walk(); // Not using the Walker mixin
Dog().fly(); // Not using the Flyer mixin
class Animal {
}
swift
// Defining the "mixins"
protocol Flyer {
  func fly()
}

extension Flyer {
  func fly() {
    print("Flaps wings")
  }
}

protocol Walker {
  func walk()
}

extension Walker {
  func walk() {
    print("Walks legs")
  }
}

class Bat: Animal, Flyer {}
class Goose: Animal, Flyer, Walker {}
class Dog: Animal, Walker {}

// Correct calls
Bat().fly();
Goose().fly();
Goose().walk();
Dog().walk();

// Incorrect calls
Bat().walk(); // `bat` doesn't have the `walk` method
Dog().fly(); // "dog" doesn't have the `fly` method

class キーワードを mixin に置き換えると、Mixin を通常のクラスとして使用できなくなります。

dart
mixin Walker {
  walk() => print('Walks legs');
}

// Impossible, as Walker is no longer a class.
class Bat extends Walker {}

複数の Mixin を使用できるため、それらのメソッドまたはフィールドは、同じクラスで使用されるときに互いに重複する可能性があります。これらは、それらを使用するクラス、またはそのクラスのスーパークラスと重複することさえあります。これを回避するために、Dart はそれらを積み重ねるため、クラスに追加される順序が重要になります。

例を挙げると、次のようになります。

dart
class Bird extends Animal with Consumer, Flyer {

Bird クラスのインスタンスでメソッドが呼び出されると、Dart はスタックの最下部にある Bird クラスから開始し、他の実装よりも優先されます。Bird に実装がない場合、Dart はスタックを上に移動し、次に Flyer、次に Consumer と進み、実装が見つかるまで続きます。実装が見つからない場合、親クラスである Animal が最後にチェックされます。

拡張メソッド

#

Swift と同様に、Dart は既存の型に機能 (具体的には、メソッド、getter、setter、および演算子) を追加できる拡張メソッドをサポートしています。Dart と Swift の両方で拡張機能を作成するための構文は非常によく似ています。

dart
extension <name> on <type> {
  (<member definition>)*
}
swift
extension <type> {
  (<member definition>)*
}

たとえば、Dart SDK の String クラスの次の拡張機能は、整数の解析を可能にします。

dart
extension NumberParsing on String {
  int parseInt() {
    return int.parse(this);
  }
}

print('21'.parseInt() * 2); // 42
swift
extension String {
  func parseInt() -> Int {
    return Int(self) ?? 0
  }
}

print("21".parseInt() * 2) // 42

拡張機能は Dart と Swift で似ていますが、いくつかの重要な違いがあります。次のセクションでは最も重要な違いを説明しますが、完全な概要については 拡張メソッド を参照してください。

名前付き拡張

#

必須ではありませんが、Dart で拡張機能に名前を付けることができます。拡張機能に名前を付けると、そのスコープを制御できます。つまり、別のライブラリと競合する場合に拡張機能を非表示または表示することができます。名前がアンダースコアで始まる場合、拡張機能はその拡張機能が定義されているライブラリ内でのみ使用できます。

dart
// Hide "MyExtension" when importing types from
// "path/to/file.dart".
import 'path/to/file.dart' hide MyExtension; 
// Only show "MyExtension" when importing types
// from "path/to/file.dart".
import 'path/to/file.dart' show MyExtension;

// The `shout()` method is only available within this library.
extension _Private on String {
  String shout() => this.toUpperCase();
}

初期化子

#

Swift では、拡張機能を使用して型に新しいコンビニエンスイニシャライザを追加できます。Dart では、拡張機能を使用してクラスにコンストラクタを追加することはできませんが、型のインスタンスを作成する静的拡張メソッドを追加することはできます。次の例を検討してください。

dart
class Person {
  Person(this.fullName);

  final String fullName;
}

extension ExtendedPerson on Person {
  static Person create(String firstName, String lastName) {
    return Person("$firstName $lastName");
  }
}

// To use the factory method, use the name of
// the extension, not the type.
final person = ExtendedPerson.create('John', 'Doe');

メンバーのオーバーライド

#

インスタンスメソッド (演算子、getter、setter を含む) のオーバーライドも、両言語間で非常によく似ています。Dart では、@override 注釈を使用して、メンバーを意図的にオーバーライドしていることを示すことができます。

dart
class Animal {
  void makeNoise => print('Noise');
}

class Dog implements Animal {
  @override
  void makeNoise() => print('Woof woof');
}

Swift では、メソッド定義に override キーワードを追加します。

swift
class Animal {
  func makeNoise() {
    print("Noise")
  }
}

class Dog: Animal {
  override func makeNoise() {
    print("Woof woof"); 
  }
}

ジェネリクス

#

Swift と同様に、Dart は型安全性やコードの重複の削減を改善するためにジェネリックの使用をサポートしています。

ジェネリックメソッド

#

ジェネリックをメソッドに適用できます。ジェネリック型を定義するには、メソッド名の後に < > シンボルで囲んで配置します。この型は、メソッド内 (戻り型として) またはメソッドのパラメータで使用できます。

dart
// Defining a method that uses generics.
T transform<T>(T param) {
  // For example,  doing some transformation on `param`...
  return param;
}

// Calling the method. Variable "str" will be
// of type String.
var str = transform('string value');

この場合、transform メソッドに String を渡すと、String が返されることが保証されます。同様に、int が提供された場合、戻り値は int です。

複数のジェネリックを定義するには、コンマで区切ります。

dart
// Defining a method with multiple generics.
T transform<T, Q>(T param1, Q param2) {
  // ...
}
// Calling the method with explicitly-defined types.
transform<int, String>(5, 'string value');
// Types are optional when they can be inferred.
transform(5, 'string value');

ジェネリッククラス

#

ジェネリックはクラスにも適用できます。コンストラクタを呼び出すときに型を指定できるため、再利用可能なクラスを特定の型に合わせて調整できます。

次の例では、Cache クラスは特定の型のキャッシュ用です。

dart
class Cache<T> {
  T getByKey(String key) {}
  void setByKey(String key, T value) {}
}
// Creating a cache for strings.
// stringCache has type Cache<String>
var stringCache = Cache<String>();
// Valid, setting a string value.
stringCache.setByKey('Foo', 'Bar')
// Invalid, int type doesn't match generic.
stringCache.setByKey('Baz', 5)

型宣言が省略されている場合、実行時型は Cache<dynamic> であり、両方の setByKey 呼び出しは有効です。

ジェネリックの制限

#

ジェネリックを使用して、extends を使用してコードを型のファミリーに制限できます。これにより、クラスが特定の型を拡張するジェネリック型でインスタンス化されることが保証されます (Swift に似ています)。

dart
class NumberManager<T extends num> {
  // ...
}
// Valid
var manager = NumberManager<int>(); 
var manager = NumberManager<double>(); 
// Invalid, neither String nor its parent classes extend num.
var manager = NumberManager<String>();

リテラルのジェネリック

#

MapSet、および List リテラルは、ジェネリック型を明示的に宣言できます。これは、型が推論されない場合や誤って推論された場合に役立ちます。

たとえば、List クラスにはジェネリック定義があります: class List<E>。ジェネリック型 E はリストの内容の型を参照します。通常、この型は自動的に推論され、List クラスのいくつかのメンバー型で使用されます。(たとえば、最初の getter は E 型の値を返します)。List リテラルを定義する場合、ジェネリック型を次のように明示的に定義できます。

dart
var objList = [5, 2.0]; // Type: List<num> // Automatic type inference
var objList = <Object>[5, 2.0]; // Type: List<Object> // Explicit type definition
var objSet = <Object>{5, 2.0}; // Sets work identically

これは Map にも当てはまります。Mapkeyvalue の型をジェネリック (class Map<K, V>) を使用して定義します。

dart
// Automatic type inference
var map = {
  'foo': 'bar'
}; // Type: Map<String, String>
// Explicit type definition:
var map = <String, Object>{
  'foo': 'bar'
}; // Type: Map<String, Object>

並行処理

#

Swift はマルチスレッドをサポートし、Dart はアイソレートをサポートしています。これらは軽量スレッドに似ており、ここでは説明しません。各アイソレートには独自のイベントループがあります。詳細については、アイソレートの仕組み を参照してください。

Future

#

バニラ Swift には Dart の Future に相当するものはありません。ただし、Apple の Combine フレームワーク、または RxSwift や PromiseKit のようなサードパーティライブラリに慣れている場合は、このオブジェクトにまだ慣れているかもしれません。

要するに、future は非同期操作の結果を表し、後で利用可能になります。String ではなく StringFuture を返す関数がある場合 (Future<String>)、実際には後で存在する可能性のある値 — 将来 — を受け取っています。

future の非同期操作が完了すると、値が利用可能になります。ただし、future は値ではなくエラーで完了することもあることに注意してください。

この例としては、HTTP リクエストを行い、すぐに future を応答として受け取った場合が考えられます。結果が来ると、future はその値で完了します。しかし、HTTP リクエストが失敗した場合、たとえばインターネット接続が中断されたために、future は代わりにエラーで完了します。

Future は手動で作成することもできます。Future を作成する最も簡単な方法は、次のセクションで説明する async 関数を定義して呼び出すことです。Future にする必要がある値がある場合、Future クラスを使用して簡単に変換できます。

dart
String str = 'String Value';
Future<String> strFuture = Future<String>.value(str);

Async/await

#

future はバニラ Swift の一部ではありませんが、Dart の async/await 構文には Swift の対応するものがあり、Future オブジェクトなしで同様に機能します。

Swift と同様に、関数は async としてマークできます。Dart の違いは、すべての async 関数が常に暗黙的に Future を返すことです。たとえば、関数が String を返す場合、この関数の async 対応物は Future<String> を返します。

Swift では async キーワードの後に配置される (ただし、関数がスロー可能な場合のみ) throws キーワードは、Dart の構文には存在しません。なぜなら、Dart の例外とエラーはコンパイラによってチェックされないからです。むしろ、async 関数で例外が発生した場合、返された Future は例外で失敗し、適切に処理できます。

dart
// Returns a future of a string, as the method is async
Future<String> fetchString() async {
  // Typically some other async operations would be done here.
  
  Response response = await makeNetworkRequest();
  if (!response.success) {
    throw BadNetwork();
  }

  return 'String Value';
}

この async 関数は、次のように呼び出すことができます。

dart
String stringFuture = await fetchString();
print(str); // "String Value"

Swift での相当する async 関数

swift
func fetchString() async throws -> String {
  // Typically some other async operations would be done here.
  let response = makeNetworkRequest()
  if !response.success {
    throw BadNetwork()
  }
  
  return "String Value"
}

同様に、async 関数で発生する例外は、catchError メソッドを使用して、失敗した Future を処理するのと同じ方法で処理できます。

Swift では、async 関数は非 async コンテキストから呼び出すことはできません。Dart では、そうすることは許可されていますが、結果の Future を適切に処理する必要があります。不要に async 関数を非 async コンテキストから呼び出すことは悪い習慣と見なされます。

Swift と同様に、Dart にも await キーワードがあります。Swift では、awaitasync 関数を呼び出す場合にのみ使用できますが、Dart の awaitFuture クラスで機能します。その結果、すべての async 関数が Dart で Future を返すため、awaitasync 関数でも機能します。

Future を待機すると、現在の関数の実行が一時停止され、イベントループに制御が戻り、Future が値またはエラーで完了するまで他の作業を行うことができます。しばらくすると、await 式は値に評価されるか、エラーをスローします。

完了すると、future の値が返されます。Swift と同様に、async コンテキストでのみ await できます。

dart
// We can only await futures within an async context.
asyncFunction() async {
  String returnedString = await fetchString();
  print(returnedString); // 'String Value'
}

待機中の future が失敗すると、await キーワードを含む行でエラーオブジェクトがスローされます。これは通常の try-catch ブロックを使用して処理できます。

dart
// We can only await futures within an async context.
Future<void> asyncFunction() async {
  String? returnedString;
  try {
    returnedString = await fetchString();
  } catch (error) {
    print('Future encountered an error before resolving.');
    return;
  }
  print(returnedString);
}

詳細とインタラクティブな練習については、非同期プログラミング チュートリアルを参照してください。

Stream

#

Dart の async ツールボックスにあるもう 1 つのツールは Stream クラスです。Swift には独自のストリームの概念がありますが、Dart のストリームは Swift の AsyncSequence に似ています。同様に、Observables (RxSwift) または Publishers (Apple の Combine フレームワーク) を知っている場合は、Dart のストリームは馴染みがあるはずです。

StreamsAsyncSequencePublishers、または Observables に慣れていない人向けに、概念は次のようになります。Stream は基本的に Future として機能しますが、イベントバスのように、時間とともに広がる複数の値があります。Stream はリッスンして値またはエラーイベントを受信でき、それ以上イベントが送信されないときに閉じることができます。

リッスン

#

Stream をリッスンするには、Stream と for-in ループを async コンテキストで組み合わせることができます。for ループは、エミットされた各項目に対してコールバックメソッドを呼び出し、Stream が完了するかエラーが発生すると終了します。

dart
Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  try { 
    await for (final value in stream) {
      sum += value;
    }
  } catch (error) {
    print('Stream encountered an error! $err');
  }
  return sum;
}

Stream をリッスン中にエラーが発生した場合、エラーは await キーワードを含む行でスローされ、try-catch ステートメントで処理できます。

dart
try {
  await for (final value in stream) { ... }
} catch (err) {
  print('Stream encountered an error! $err');
}

これは Stream をリッスンする唯一の方法ではありません。listen メソッドを呼び出してコールバックを提供することもできます。これは Stream が値を発行するたびに呼び出されます。

dart
Stream<int> stream = ...
stream.listen((int value) {
  print('A value has been emitted: $value');
});

listen メソッドには、エラー処理、または Stream が完了したときのオプションのコールバックがあります。

dart
stream.listen(
  (int value) { ... },
  onError: (err) {
    print('Stream encountered an error! $err');
  },
  onDone: () {
    print('Stream completed!');
  },
);

listen メソッドは StreamSubscription のインスタンスを返します。これは、Stream をリッスンするのを停止するために使用できます。

dart
StreamSubscription subscription = stream.listen(...);
subscription.cancel();

Stream の作成

#

future と同様に、Stream を作成する方法はいくつかあります。最も一般的な 2 つの方法は、async ジェネレーターまたは SteamController を使用することです。

Async ジェネレーター
#

async ジェネレーター関数は、同期ジェネレーター関数と同じ構文ですが、sync* の代わりに async* キーワードを使用し、Iterable の代わりに Stream を返します。このアプローチは、Swift の AsyncStream 構造体に似ています。

async ジェネレーター関数では、yield キーワードは指定された値を Stream に発行します。しかし、yield* キーワードは、他の反復可能ではなく Stream で機能します。これにより、他の Stream からのイベントがこの Stream に発行されます。次の例では、新しく発行された Stream が完了するまで、関数は実行を続行しません。

dart
Stream<int> asynchronousNaturalsTo(int n) async* {
  int k = 0;
  while (k < n) yield k++;
}

Stream<int> stream = asynchronousNaturalsTo(5);

StreamController API を使用して Stream を作成することもできます。詳細については、StreamController の使用 を参照してください。

ドキュメントコメント

#

通常のコメントは、Dart では Swift と同じように機能します。二重バックスラッシュ (//) を使用すると、その行の残りの部分で二重スラッシュ以降のすべてがコメントアウトされ、/* ... */ は複数行にまたがるブロックコメントになります。

通常のコメントに加えて、Dart には dart doc と連携する ドキュメントコメント もあります。dart doc は Dart パッケージの HTML ドキュメントを生成するファーストパーティツールです。パブリックメンバーのすべての宣言の上にドキュメントコメントを配置することがベストプラクティスと見なされます。このプロセスは、Swift でさまざまなドキュメント生成ツールにコメントを追加する方法に似ていることに気付くかもしれません。

Swift と同様に、2 つではなく 3 つのフォワードスラッシュ (///) を使用してドキュメントコメントを定義します。

dart
/// The number of characters in this chunk when unsplit.
int get length => ...

ドキュメントコメント内で、型、パラメータ、およびメソッド名を角括弧で囲みます。

dart
/// Returns the [int] multiplication result of [a] * [b].
multiply(int a, int b) => a * b;

JavaDoc スタイルのドキュメントコメントはサポートされていますが、それらは避けて /// 構文を使用する必要があります。

dart
/** 
 * The number of characters in this chunk when unsplit. 
 * (AVOID USING THIS SYNTAX, USE /// INSTEAD.)
 */
int get length => ...

ライブラリと可視性

#

Dart の可視性セマンティクスは Swift と似ており、Dart ライブラリは Swift モジュールにほぼ相当します。

Dart は 2 つのレベルのアクセス制御を提供します: パブリックとプライベート。メソッドと変数はデフォルトでパブリックです。プライベート変数はアンダースコア文字 (_) でプレフィックスされ、Dart コンパイラによって強制されます。

dart
final foo = 'this is a public property';
final _foo = 'this is a private property';

String bar() {
  return 'this is a public method';
}
String _bar() {
  return 'this is a private method';
}

// Public class
class Foo {
}

// Private class
class _Foo {
},

プライベートメソッドと変数は Dart ではライブラリにスコープされ、Swift ではモジュールにスコープされます。Dart では、ファイルをライブラリとして定義できますが、Swift ではモジュールに新しいビルドターゲットを作成する必要があります。これは、単一の Dart プロジェクトで n 個のライブラリを定義できるのに対し、Swift では n 個のモジュールを作成する必要があることを意味します。

ライブラリの一部であるすべてのファイルは、そのライブラリ内のすべてのプライベートオブジェクトにアクセスできます。ただし、セキュリティ上の理由から、ファイルは特定のファイルにプライベートオブジェクトへのアクセスを許可する必要があります。そうしないと、(プロジェクト外のファイルでさえも) プロジェクト外のファイルがライブラリに登録して、機密性の高いデータにアクセスする可能性があります。言い換えれば、プライベートオブジェクトはライブラリ間で共有されません。

animal.dart
dart
library animals;

part 'parrot.dart';

class _Animal {
  final String _name;

  _Animal(this._name);
}
parrot.dart
dart
part of animals;

class Parrot extends _Animal {
  Parrot(String name) : super(name);

  // Has access to _name of _Animal
  String introduction() {
    return 'Hello my name is $_name';
  }
}

詳細については、パッケージの作成 を参照してください。

次のステップ

#

このガイドでは、Dart と Swift の間の主な違いを紹介しました。この時点で、Dart または Flutter (単一のコードベースから美しく、ネイティブにコンパイルされた、マルチプラットフォーム アプリケーションを構築するために Dart を使用するオープンソース フレームワーク) の一般的なドキュメントに移行することを検討してください。そこでは、言語に関する詳細情報と、開始するための実際的な方法を見つけることができます。