コンテンツ

Swift開発者向けのDart学習

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

SwiftとDartはどちらも健全なNull安全性をサポートしています。どちらの言語も、デフォルトでは変数をnullにすることはできません。

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

ミックスインは、Swift開発者にとって新しいかもしれないDartのもう1つの概念です。Swiftと同様に、DartはAOT(事前コンパイル)コンパイルを提供します。ただし、Dartはインクリメンタルな再コンパイルやデバッグなどのさまざまな開発を支援するために、JIT(ジャストインタイム)コンパイルモードもサポートしています。詳細については、Dartの概要をご確認ください。

規約とリンティング

#

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

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

Dartの規約とリンティングの詳細については、効果的なDartリンター規則をご確認ください。

変数

#

Dartでの変数の宣言と初期化は、Swiftと比較すると少し異なります。変数の宣言は、常に変数の型、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キーワードは、変数を1回だけ設定できることを示します。これはSwiftのletキーワードに似ています。

DartとSwiftの両方で、final変数を初期化できるのは、宣言ステートメントまたは初期化子リストのいずれか1回のみです。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のクラスでもあります。int型とdouble型は両方とも、親クラスとしてnumを共有しています。

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

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

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では、以下に示すように、等価演算子(==)を使用して整数値をdoubleと比較できます。

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 + '!';

文字列補間

#

${<式>}構文を使用して、式を文字列リテラルに挿入します。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安全性 を強制します。デフォルトでは、型はnull許容とマークされていない限り、null値を許可しません。Dartは、型の最後に疑問符(?)を付けてこれを示します。これは、Swiftのオプショナルと同様に機能します。

Null対応演算子

#

Dartは、null値を処理するためのいくつかの演算子をサポートしています。null合体演算子(??)とオプションの連鎖演算子(?.)はDartで使用でき、Swiftと同じように動作します。

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

さらに、Dartはカスケード演算子のnull安全バージョン(?..)を提供しています。この演算子は、ターゲット式がnullに解決された場合、すべての操作を無視します。Dartは、Swiftにはないnull代入演算子(??=)も提供しています。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許容変数または式が実際には非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キーワードは、クラスフィールドに割り当てて、後で初期化されるが、非null許容のままにすることを示すことができます。これは、Swiftの「暗黙的にアンラップされたオプショナル」に似ています。これは、変数が初期化される前に観測されない場合に便利で、後で初期化できます。非null許容のlateフィールドには、後でnullを代入することはできません。また、非null許容の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にはならないことに注意してください。

初期化子と組み合わせると、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はタプルをサポートしていません(ただし、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)")
}

同期および非同期のDartコードの両方でtry-catchブロックを使用できます。詳細については、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
}

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

  • デフォルト値
  • 型をnull許容として設定するための型の末尾にある?
  • 変数型の前のキーワードrequired

null許容型の詳細については、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
}

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

ファーストクラス関数

#

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つの方法があります。1つ目は中かっこを使用する方法で、他の関数と同様に機能します。複数行を使用でき、値を返すには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].

これは、同期ジェネレーター関数の例です。非同期ジェネレーター関数を定義することもできます。これらは、反復可能コレクションの代わりにストリームを返します。詳細については、並行性のセクションを参照してください。

ステートメント

#

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

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

#

Dart のすべての制御フロー文は、構文にいくつかの違いがある点を除いて、Swift の対応する文とほぼ同様に動作します。

if

#

Swift とは異なり、Dart の if 文では、条件を括弧で囲む必要があります。Dart のスタイルガイドでは、制御フロー文を波括弧で囲むことを推奨していますが (下記参照)、else 句がなく、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

型テスト演算子

#

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

意味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 です。

オブジェクトが特定の型であると確信している場合にのみ、型キャスト演算子を使用してオブジェクトを特定の型にキャストします。例:

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 で同じように定義されます。2 つの言語間の構文は非常によく似ていますが、次の例に示すように、微妙な違いがいくつかあります。

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 フィールドに代入します。これは、リスト自体がコンパイル時定数である必要はなく、フィールドを別のリストで上書きできないようにすることを意味します。ただし、リストのサイズまたは内容を変更することはできます。
    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"]

スプレッド演算子

#

Dart のもう 1 つの便利な機能は、スプレッド演算子 (...) と null 対応スプレッド演算子 (...?) であり、これにより、複数の値をコレクションに簡潔に挿入できます。

たとえば、スプレッド演算子 (...) を使用して、リストのすべての値を別のリストに挿入できます。以下に示すように。

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

Swift にはスプレッド演算子はありませんが、上記の 2 行目と同等のものは次のようになります。

swift
let list2 = [0] + list

スプレッド演算子の右側の式が null になる可能性がある場合は、null 対応スプレッド演算子 (...?) を使用して例外を回避できます。

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 プロトコルを適用する必要があります。

次に、リテラルを使用して作成された、いくつかの簡単な MapDictionary の例を示します。

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'

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

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と呼ばれます)は、固定された数の定数値を表すために使用される特殊なクラスです。enumは長い間Dart言語の一部でしたが、Dart 2.17ではメンバーの拡張enumサポートが追加されました。つまり、状態を保持するフィールド、その状態を設定するコンストラクター、機能を持つメソッドを追加したり、既存のメンバーをオーバーライドしたりすることができます。詳細については、Dart言語ツアーの拡張enumの宣言を参照してください。

コンストラクター

#

Dartのクラスコンストラクターは、Swiftのクラスイニシャライザーと似たように動作します。ただし、Dartでは、クラスプロパティを設定するための機能がさらに提供されています。

標準コンストラクター

#

標準クラスコンストラクターは、宣言と呼び出しの両方において、Swiftのイニシャライザーと非常によく似ています。Dartでは、initキーワードの代わりにクラス名全体を使用します。かつて新しいクラスインスタンスの作成に必須だった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);

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

#

コンストラクター内のすべてのクラスフィールドに値を代入するコードを記述するのは冗長であることが多いため、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として定義すると、クラスは変更不可になります。クラス内のすべての非静的フィールドには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);
}

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

#

新しいクラスインスタンスを作成する必要がない場合は、ファクトリーコンストラクターを使用できます。たとえば、キャッシュされたインスタンスを代わりに返すことができる場合です。

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キーワードを付けることで、ゲッターとセッターを定義できます。各インスタンスフィールドには暗黙的なゲッターがあり、適切であればセッターもあることを思い出してください。Swiftでは、構文が少し異なります。getおよびsetキーワードはプロパティステートメント内で定義する必要があり、式ではなくステートメントとしてのみ定義できます。

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言語では、すべてのクラスが、クラスのすべてのインスタンスメンバーと、実装するすべてのインターフェースを含むインターフェースを暗黙的に定義します。クラスBの実装を継承せずにクラスBのAPIをサポートするクラスAを作成したい場合は、クラス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を使用できます。これは、複数のクラスが互いに継承したり、共通の祖先を共有したりする必要なしに、同じ機能を共有する場合に役立ちます。

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は他の実装よりも優先されます。Birdに実装がない場合、DartはFlyer、次にConsumerと、実装が見つかるまでスタックを上に移動し続けます。実装が見つからない場合、最後に親クラスであるAnimalがチェックされます。

拡張メソッド

#

Swiftと同様に、Dartは既存の型に機能(具体的にはメソッド、ゲッター、セッター、および演算子)を追加できる拡張メソッドを提供します。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');

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

#

インスタンスメソッド(演算子、ゲッター、セッターを含む)のオーバーライドも、2つの言語間で非常によく似ています。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>();

リテラルでのジェネリクス

#

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

たとえば、List クラスにはジェネリックな定義 class List<E> があります。ジェネリック型 E はリストの内容の型を指します。通常、この型は自動的に推論され、List クラスのいくつかのメンバ型で使用されます。(たとえば、最初のゲッターは型 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 でも同様です。Map もジェネリックを使用して keyvalue の型を定義します (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 は分離体をサポートしています。分離体は軽量スレッドに似ており、ここでは説明しません。各分離体には独自のイベントループがあります。詳細については、「分離体の仕組み」を参照してください。

Futures

#

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

簡単に言えば、Future は非同期操作の結果を表し、後で利用可能になります。String ではなく StringFuture (Future<String>) を返す関数がある場合、基本的には後で、つまり将来、存在する可能性のある値を受け取ることになります。

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

たとえば、HTTP リクエストを行った場合、応答としてすぐに Future を受け取るとします。結果が届くと、Future はその値で完了します。しかし、インターネット接続が中断したなどの理由で HTTP リクエストが失敗した場合、Future はエラーで完了します。

Future は手動で作成することもできます。Future を作成する最も簡単な方法は、async 関数を定義して呼び出すことです。これについては次のセクションで説明します。Future である必要のある値がある場合は、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 を返す場合、この関数の非同期版は Future<String> を返します。

Swift の async キーワードの後 (関数がスロー可能な場合に限る) に配置される throws キーワードは、Dart の構文には存在しません。これは、Dart の例外とエラーはコンパイラーによってチェックされないためです。代わりに、非同期関数で例外が発生した場合、返された 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';
}

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

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

Swift での同等の非同期関数

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 関数で発生した例外は、失敗した Future を処理するのと同じ方法で、catchError メソッドを使用して処理できます。

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

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

Future を待機すると、現在の関数の実行が中断され、イベントループに制御が戻ります。イベントループは、Future が値またはエラーで完了するまで、他の処理を行うことができます。その後、await 式は、その値に評価されるか、そのエラーをスローします。

完了すると、Future の値が返されます。Swift の場合と同様に、awaitasync コンテキストでのみ使用できます。

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);
}

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

Streams

#

Dart の非同期ツールボックスのもう 1 つのツールは Stream クラスです。Swift には独自のストリームの概念がありますが、Dart のストリームは Swift の AsyncSequence に似ています。同様に、Observables (RxSwift 内) または Publishers (Apple の Combine フレームワーク内) をご存知であれば、Dart のストリームはなじみ深く感じられるはずです。

StreamsAsyncSequencePublishers、または Observables になじみのない方のために、概念は次のとおりです。Stream は基本的に Future のように機能しますが、イベントバスのように、時間が経過するにつれて複数の値が分散されます。ストリームは、値またはエラーイベントを受信するためにリッスンでき、それ以上のイベントが送信されない場合は閉じることができます。

リスニング

#

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

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;
}

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

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

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

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

listen メソッドには、エラー処理用、またはストリームが完了したときに使用されるオプションのコールバックがいくつかあります。

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

listen メソッドは、StreamSubscription のインスタンスを返します。これを使用して、ストリームのリッスンを停止できます。

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

ストリームの作成

#

Future の場合と同様に、ストリームを作成するにはいくつかの異なる方法があります。最も一般的な 2 つの方法は、非同期ジェネレーターまたは SteamController を使用することです。

非同期ジェネレーター
#

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

非同期ジェネレーター関数では、yield キーワードは指定された値をストリームに放出します。ただし、yield* キーワードは、他の反復可能オブジェクトではなくストリームで動作します。これにより、他のストリームからのイベントをこのストリームに放出できます。次の例では、関数は新しく生成されたストリームが完了すると初めて続行されます。

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

Stream<int> stream = asynchronousNaturalsTo(5);

StreamController API を使用してストリームを作成することもできます。詳細については、「StreamController の使用」を参照してください。

ドキュメントコメント

#

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

通常のコメントに加えて、Dart には ドキュメントコメントもあり、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 を使用するオープンソースフレームワーク) の一般的なドキュメントに進むことを検討してください。そこでは、言語に関する詳細情報と、始めるための実際的な方法が見つかります。