| author: Andrea Bizzotto

| link: the original medium.com/coding-with…

Dart and Swift are my favorite programming languages. I use them extensively both commercially and in open source.

This article provides a comparison between Dart and Swift to:

  • Highlight the differences between the two;
  • As a reference for developers moving from one language to another (or using both).

Some background:

  • Dart supports Flutter, Google’s framework for building beautiful native applications from a single code base.
  • Swift supports Apple’s SDK through iOS, macOS, tvOS and watchOS.

Here’s a comparison of the main features of the two languages (Dart 2.1 and Swift 4.2). Since it is beyond the scope of this article to discuss each feature in depth, you can refer to the respective documentation for more information.

directory

  • table
  • variable
  • Type inference
  • Variable/immutable variable
  • function
  • Named and unnamed parameters
  • Optional and default parameters
  • closure
  • tuples
  • The control flow
  • A collection of
  • Nullability & Optionals
  • class
  • inheritance
  • attribute
  • Protocol/Abstract class
  • Mixins
  • extension
  • The enumeration
  • The structure of the body
  • Error handling
  • The generic
  • Access control
  • Asynchronous programming: Future
  • Asynchronous programming: Stream
  • Memory management
  • Compile and execute
  • Other features not covered

table

variable

Dart variable declaration syntax is as follows:

String name;
int age;
double height;
Copy the code

In Swift it is as follows:

var name: String
var age: Int
var height: Double
Copy the code

Dart variable initialization syntax is as follows:

var name = 'Andrea';
var age = 34;
var height = 1.84;
Copy the code

In Swift it is as follows:

var name = "Andrea"
var age = 34
var height = 1.84
Copy the code

In this example, type annotations are not required. This is because both languages can infer the type from the expression to the right of the assignment.

Type inference

Type inference means that we can write the following code in Dart:

var arguments = {'argA': 'hello'.'argB': 42}; // Map<String, Object>
Copy the code

The compiler automatically resolves arguments’ types.

In Swift, it can also be written as:

var arguments = [ "argA": "hello"."argB": 42 ] // [ String : Any ]
Copy the code

For more details

The Dart documentation is described as follows:

The parser can infer the types of fields, methods, local variables, and most generic type parameters. Dynamic typing is used when the parser does not have enough information to infer a particular type.

Swift documents have the following description:

Swift makes extensive use of type inference, allowing you to omit the types or partial types of many variables and expressions in your code. For example, instead of writing var x:Int = 0, you can write var x = 0, omitting the type entirely – the compiler correctly infer that x is a value of type Int.

Dynamic type

You can declare variables of Any type using the dynamic keyword in Dart and the Any keyword in Swift.

Dynamic typing is typically used when reading data such as JSON.

Variable/immutable variable

Variables can be declared mutable or immutable.

To declare mutable variables, both languages use the var keyword.

var a = 10; // int (Dart)
a = 20; // ok

var a = 10 // Int (Swift)
a = 20 // ok
Copy the code

Dart uses final and Swift uses let to declare immutable variables.

final a = 10;
a = 20; // 'a': a final variable, can only be set once.

let a = 10
a = 20 // Cannot assign to value: 'a' is a 'let' constant
Copy the code

Note: The Dart document defines two keywords, final and const, which work as follows:

If you don’t want to change the magnitude more, use final or const instead of var or type. Final variables can only be set once; Const variables are compile-time constants. (Const variables are implicitly final.) Final Top-level type variables or class variables are initialized the first time they are used.

Further explanation can be found in this article on the Dart website:

Final means an assignment. Final variables or fields must have Initializer. Once assigned, the value of a final variable cannot be changed.

In Swift, we declare constants with let.

Constant declarations introduce constant named values into the program. Constants are declared using the let keyword and have the following form:

let constant name: type = expression
Copy the code

Constant declarations define immutable bindings between constant names and initialized expression values; A constant value cannot be changed after it is set.

function

Functions are first-class citizens in both Swift and Dart.

This means that just like objects, functions can be passed as arguments, saved as properties, or returned as a result.

As an initial comparison, we can see how to declare functions that take no arguments.

In Dart, the return type precedes the method name:

void foo();
int bar();
Copy the code

In Swift, we use the -> T notation as a suffix. If there is no return value (Void), there is no need to do this:

func foo(a)
func bar(a) -> Int
Copy the code

Named and unnamed (un-named) parameters

Both languages support named and unnamed parameters.

In Swift, parameters default to named parameters:

func foo(name: String, age: Int, height: Double)
foo(name: "Andrea", age: 34, height: 1.84)
Copy the code

In Dart, we use curly braces ({}) to define named parameters:

void foo({String name, int age, double height});
foo(name: 'Andrea', age: 34, height: 1.84);
Copy the code

In Swift, we use the underscore (_) as an external argument to define unnamed arguments:

func foo(_ name: String, _ age: Int, _ height: Double)
foo("Andrea".34.1.84)
Copy the code

In Dart, we define unnamed parameters by omitting curly braces ({}) :

void foo(String name, int age, double height);
foo('Andrea'.34.1.84);
Copy the code

Optional and default parameters

Both languages support default parameters.

In Swift, you can define a default value for any parameter in a function by assigning the parameter after its type. If a default value is defined, the parameter can be omitted when the function is called.

func foo(name: String, age: Int = 0, height: Double = 0.0) 
foo(name: "Andrea", age: 34) // name: "Andrea", age: 34, height: 0.0
Copy the code

In Dart, optional parameters can be positional or named, but not both.

// positional optional parameters
void foo(String name, [int age = 0.double height = 0.0]);
foo('Andrea'.34); // name: 'Andrea', age: 34, height: 0.0
// named optional parameters
void foo({String name, int age = 0.double height = 0.0});
foo(name: 'Andrea', age: 34); // name: 'Andrea', age: 34, height: 0.0
Copy the code

closure

As first-class objects, functions can be passed as arguments to other functions or assigned to variables.

In this context, functions are also called closures.

Here is a Dart example of a function that iterates over a list of items, using closures to print the index and contents of each item:

final list = ['apples'.'bananas'.'oranges'];
list.forEach((item) => print('${list.indexOf(item)}: $item'));
Copy the code

The closure takes an argument (item), prints the index and value of that item, and returns no value.

Note the use of the arrow symbol (=>). This can be used instead of a single return statement inside curly braces:

list.forEach((item) { print('${list.indexOf(item)}: $item'); });
Copy the code

The same code in Swift looks like this:

let list = ["apples"."bananas"."oranges"]
list.forEach({print("\(String(describing: list.firstIndex(of: $0))) \ [$0)")})
Copy the code

In this case, instead of specifying a name for the argument passed to the closure, we use $0 instead of the first argument. This is completely optional, we can still use named arguments:

list.forEach({ item in print("\(String(describing: list.firstIndex(of: item))) \(item)")})
Copy the code

Closures are typically used as completion blocks for asynchronous code in Swift (see the section on asynchronous programming below).

tuples

The Swift document describes it as follows:

Tuples group multiple values into a single compound value. The values in a tuple can be of any type and do not have to be of the same type.

These can be used as small, lightweight types and are useful when defining functions with multiple return values.

Here’s how to use tuples in Swift:

let t = ("Andrea".34.1.84)
print(t.0) // prints "Andrea"
print(t.1) // prints 34
print(t.2) / / prints 1.84
Copy the code

Dart has a single tripartite package support tuple:

const t = const Tuple3<String.int.double> ('Andrea'.34.1.84);
print(t.item1); // prints 'Andrea'
print(t.item2); // prints 34
print(t.item3); / / prints 1.84
Copy the code

The control flow

Both languages provide multiple control flow statements.

For example, if, for, while, switch statements.

It will be fairly lengthy to cover these here, so please refer to the official documentation.

Arrays, sets, maps

Arrays / Lists

An array is an ordered group of objects.

In Dart, the List object is used to represent arrays:

var emptyList = <int> [];// empty list
var list = [1.2.3]; // list literal
list.length; / / 3
list[1]; / / 2
Copy the code

Arrays in Swift are built-in types:

var emptyArray = [Int] ()// empty array
var array = [1.2.3] // array literal
array.count / / 3
array[1] / / 2
Copy the code

Sets

Description in Swift documentation:

A Set stores different values of the same type in a collection, in no defined order. You can use collections instead of arrays when the order of items is not important, or when you need to ensure that elements appear only once.

Dart Set class definition:

var emptyFruits = Set<String> ();var fruits = Set<String>.from(['apple'.'banana']); // set from Iterable
Copy the code

Examples in Swift:

var emptyFruits = Set<String> ()var fruits = Set<String> (["apple"."banana"])
Copy the code

Maps / Dictionaries

The Swift documentation has a good definition of map/dictionary:

Dictionaries store associations between keys of the same type and values of the same type in a collection, without a particular ordering. Each value is associated with a unique key that acts as an identifier for that value in the dictionary.

A map in Dart is defined as follows:

var namesOfIntegers = Map<Int,String> ();// empty map
var airports = { 'YYZ': 'Toronto Pearson'.'DUB': 'Dublin' }; // map literal
Copy the code

In Swift a map is called a dictionary:

var namesOfIntegers = [Int: String] ()// empty dictionary
var airports = ["YYZ": "Toronto Pearson"."DUB": "Dublin"] // dictionary literal
Copy the code

Nullability & Optionals

In Dart, any object can be NULL. And trying to access a method or variable of a null object causes a null pointer exception. This is the most common source of errors in computer programs.

From the beginning, Swift has had one more option, a built-in language feature that declares whether objects can have values. Take a look at the documentation:

You can use Optional in cases where a value might be missing. Optional represents two possibilities: either there is a value, which you can unlock to access, or there is no value at all.

Instead, we can use non-optional variables to ensure that they always have values:

var x: Int? // optional
var y: Int = 1 // non-optional, must be initialized
Copy the code

Note: Saying Swift variables are optional is much the same as saying Dart variables can be null.

Without language-level support for options, we can only check for null variables at run time.

With Optional, we encode this information at compile time. We can untangle Optional to safely check if they contain values:

func showOptional(x: Int?) {
  // use `guard let` rather than `if let` as best practice
  if let x = x { // unwrap optional
    print(x)
  } else {
    print("no value")
  }
}

showOptional(x: nil) // prints "no value"
showOptional(x: 5) // prints "5"
Copy the code

If we know that the variable must have a value, we can use a non-optional value:

func showNonOptional(x: Int) {
  print(x)
}
showNonOptional(x: nil) // [compile error] Nil is not compatible with expected argument type 'Int'
showNonOptional(x: 5) // prints "5"
Copy the code

The first example above is implemented in Dart as follows:

void showOptional(int x) {
  if(x ! =null) {
    print(x);
  } else {
    print('no value');
  }
}
showOptional(null) // prints "no value"
showOptional(5) // prints "5"
Copy the code

The second implementation is as follows:

void showNonOptional(int x) {
  assert(x ! =null);
  print(x); 	
}
showNonOptional(null) // [runtime error] Uncaught exception: Assertion failed
showNonOptional(5) // prints "5"
Copy the code

Having optional means we can catch errors at compile time rather than at run time. Catching bugs early makes your code safer and has fewer bugs.

Dart’s lack of support for Optional is mitigated to some extent by the use of assertions (and the @required annotation for naming parameters).

These are widely used in the Flutter SDK, but generate additional boilerplate code.

class

Classes are the main building blocks for writing programs in object-oriented languages.

Dart and Swift both support classes, but there are some differences.

grammar

Here is a Swift class with initializer and three member variables:

class Person {
  let name: String
  let age: Int
  let height: Double
  init(name: String, age: Int, height: Double) {
    self.name = name
    self.age = age
    self.height = height
  }
}
Copy the code

In a Dart:

class Person {
  Person({this.name, this.age, this.height});
  final String name;
  final int age;
  final double height;
}
Copy the code

Notice the use of this.[propertyName] in the Dart constructor. This is the syntactic sugar used to set instance member variables before the constructor runs.

Factory constructor

In Dart, you can use the factory constructor.

Use the factory keyword when implementing a constructor that does not always create a new instance of its class.

A practical use case for the factory constructor is when creating a model class from JSON:

class Person {
  Person({this.name, this.age, this.height});
  final String name;
  final int age;
  final double height;
  factory Person.fromJSON(Map<dynamic.dynamic> json) {
    String name = json['name'];
    int age = json['age'];
    double height = json['height'];
    returnPerson(name: name, age: age, height: height); }}var p = Person.fromJSON({
  'name': 'Andrea'.'age': 34.'height': 1.84});Copy the code

inheritance

Swift uses a single inheritance model, which means that any class can have only one superclass. The Swift class can implement multiple interfaces (also called protocols).

The Dart class has mixin-based inheritance. As described in the document:

Each Object is an instance of a class, and all classes come from Object. Mixin-based inheritance means that while there is only one superclass per class (except Object), the class body can be reused across multiple class hierarchies.

Here is single inheritance in Swift:

class Vehicle {
  let wheelCount: Int
  init(wheelCount: Int) {
    self.wheelCount = wheelCount
  }
}
class Bicycle: Vehicle {
  init() {
    super.init(wheelCount: 2)}}Copy the code

In a Dart:

class Vehicle {
  Vehicle({this.wheelCount});
  final int wheelCount;
}
class Bicycle extends Vehicle {
  Bicycle() : super(wheelCount: 2);
}
Copy the code

attribute

These are called instance variables in Dart and just attributes in Swift.

In Swift, there is a difference between storing and calculating attributes:

class Circle {
  init(radius: Double) {
    self.radius = radius
  }
  let radius: Double // stored property
  var diameter: Double { // read-only computed property
    return radius * 2.0}}Copy the code

In Dart, we have the same distinction:

class Circle {
  Circle({this.radius});
  final double radius; // stored property
  double get diameter => radius * 2.0; // computed property
}
Copy the code

In addition to computing getters for properties, we can define setters.

Using the example above, we can rewrite the diameter property to include a setter:

var diameter: Double { // computed property
  get {
    return radius * 2.0
  }
  set {
    radius = newValue / 2.0}}Copy the code

In Dart, we can add a separate setter like this:

set diameter(double value) => radius = value / 2.0;
Copy the code

Attribute observer

This is a Swift specific feature. As described in the document:

The attribute observer is responsible for observing and responding to changes in the attribute value. The property observer is called each time the property value is set, even if the new value is the same as the property’s current value.

Here’s how they use it:

var diameter: Double { // read-only computed property
  willSet(newDiameter) {
    print("old value: \(diameter), new value: \(newDiameter)")}didSet {
    print("old value: \(oldValue), new value: \(diameter)")}}Copy the code

Protocol/Abstract class

Here we discuss structures for defining methods and properties without specifying how they are implemented. This is called an interface in other languages.

In Swift, an interface is called a protocol.

protocol Shape {
  func area(a) -> Double
}
class Square: Shape {
  let side: Double
  init(side: Double) {
    self.side = side
  }
  func area(a) -> Double {
    return side * side
  }
}
Copy the code

Dart has a similar structure called an abstract class. The abstract class cannot be instantiated. However, they can define methods that have implementations.

The above example could be written in Dart like this:

abstract class Shape {
  double area();
}
class Square extends Shape {
  Square({this.side});
  final double side;
  double area() => side * side;
}
Copy the code

Mixins

In Dart, a mixin is just a regular class that can be reused across multiple class hierarchies.

The following code shows how we can use the NameExtension mixin to extend the Person class we defined earlier:

abstract class NameExtension {
  String get name;
  String get uppercaseName => name.toUpperCase();
  String get lowercaseName => name.toLowerCase();
}
class Person with NameExtension {
  Person({this.name, this.age, this.height});
  final String name;
  final int age;
  final double height;	
}
var person = Person(name: 'Andrea', age: 34, height: 1.84);
print(person.uppercaseName); // 'ANDREA'
Copy the code

extension

Extensions are a feature of the Swift language. As described in the document:

Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types that do not have access to the original source code (called traceability modeling).

You can’t do that with mixins in Dart.

Using the example above, we can extend the Person class like this:

extension Person {
  var uppercaseName: String {
    return name.uppercased()
  }
  var lowercaseName: String {
    return name.lowercased()
  }
}
var person = Person(name: "Andrea", age: 34, height: 1.84)
print(person.uppercaseName) // "ANDREA"
Copy the code

Extensions are much more than I’ve covered here, especially when they are used with protocols and generics.

A very common use case for extensions is to add protocol conformance to an existing type. For example, we can use extensions to add serialization functionality to existing model classes.

The enumeration

Dart has some very basic support for enums.

Enumerations in Swift are powerful because they support association types:

enum NetworkResponse {
  case success(body: Data) 
  case failure(error: Error)}Copy the code

This makes it possible to write logic like this:

switch (response) {
  case .success(let data):
    // do something with (non-optional) data
  case .failure(let error):
    // do something with (non-optional) error
}
Copy the code

Notice how the data and error parameters are mutually exclusive.

There is no way to associate other values with enumerations in Dart, and the code above can be done as follows:

class NetworkResponse {
  NetworkResponse({this.data, this.error})
  // assertion to make data and error mutually exclusive
  : assert(data ! =null && error == null || data == null&& error ! =null);
  final Uint8List data;
  final String error;
}
var response = NetworkResponse(data: Uint8List(0), error: null);
if(response.data ! =null) {
  // use data
} else {
  // use error
}
Copy the code

A few notes:

  • Here, we use assertions to compensate for the fact that we don’t have optional.
  • The compiler cannot help us examine all possible cases. That’s because we don’t use itswitchTo process the response.

In summary, Swift enumeration is more powerful and expressive than Dart.

Third-party libraries such as Dart Sealed Unions provide functionality similar to the Swift enumeration to help fill in the gaps.

The structure of the body

In Swift, we can define structures and classes.

Both structures have a lot in common and a few differences.

The main differences are:

Classes are reference types and structs are value types

The description in the document is as follows:

A value type is a type whose value is copied when assigned to a variable or constant, or when passed to a function. All structures and enumerations in Swift are value types. This means that any structure and enumeration instances you create — and all of their value type attributes — will always be copied as they pass through the code. Unlike value types, reference types are not copied when assigned to variables or constants or when passed to functions. Instead, references to the same existing instance are used.

To see what this means, consider the following example, where we reuse the Person class to make it mutable:

class Person {
  var name: String
  var age: Int
  var height: Double
  init(name: String, age: Int, height: Double) {
    self.name = name
    self.age = age
    self.height = height
  }
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 35
Copy the code

If we redefine Person as a struct, we have:

struct Person {
  var name: String
  var age: Int
  var height: Double
  init(name: String, age: Int, height: Double) {
    self.name = name
    self.age = age
    self.height = height
  }
}
var a = Person(name: "Andrea", age: 34, height: 1.84)
var b = a
b.age = 35
print(a.age) // prints 34
Copy the code

There’s a lot more to the structure than I’ve covered here.

Constructs can be used to process data and models in Swift, resulting in powerful code with fewer errors.

Error handling

Use the definition in the Swift documentation:

Error handling is the process of responding to and recovering from an error condition in a program.

Dart and Swift both use try/catch as a technique for handling errors, but there are some differences.

In Dart, any method can throw any type of exception.

class BankAccount {
  BankAccount({this.balance});
  double balance;
  void withdraw(double amount) {
    if (amount > balance) {
      throw Exception('Insufficient funds'); } balance -= amount; }}Copy the code

Exceptions can be caught using try/catch blocks:

var account = BankAccount(balance: 100);
try {
  account.withdraw(50); // ok
  account.withdraw(200); // throws
} catch (e) {
  print(e); // prints 'Exception: Insufficient funds'
}
Copy the code

In Swift, we explicitly declare when a method can throw an exception. This is done through the throws keyword, and any errors must conform to the error protocol:

enum AccountError: Error {
  case insufficientFunds
}
class BankAccount {
  var balance: Double
  init(balance: Double) {
    self.balance = balance
  }
  func withdraw(amount: Double) throws {
    if amount > balance {
      throw AccountError.insufficientFunds
    }
    balance -= amount
  }
}
Copy the code

When handling errors, we use the try keyword inside the do/catch block.

var account = BankAccount(balance: 100)
do {
  try account.withdraw(amount: 50) // ok
  try account.withdraw(amount: 200) // throws
} catch AccountError.insufficientFunds {
  print("Insufficient Funds")}Copy the code

Notice how the try keyword is used when calling a method that throws an exception.

The error itself is strongly typed, so we can have multiple catch blocks to cover all possible cases.

try, try? , try!

Swift provides a less cumbersome way to handle errors.

We can use a try without a do/catch block, right? . This will ignore any exceptions:

var account = BankAccount(balance: 100)
try? account.withdraw(amount: 50) // ok
try? account.withdraw(amount: 200) // fails silently
Copy the code

Alternatively, if we are certain that a method does not throw an exception, we can use try! :

var account = BankAccount(balance: 100)
try! account.withdraw(amount: 50) // ok
try! account.withdraw(amount: 200) // crash
Copy the code

The example above will cause the program to crash. Therefore, try! Is not recommended in production code. , which is better for writing tests.

In summary, the explicit nature of error handling in Swift is very beneficial in API design because it is easy to know if a method can be thrown.

Similarly, using a try on a method call allows us to focus on code that might throw an error, forcing us to consider error cases.

In this respect, error handling makes Swift more secure and reliable than Dart.

The generic

Swift Document Description:

Generic code enables you to write flexible, reusable functions and types that can use any type on demand. You can write code that avoids duplication and expresses intent in a clear, abstract way.

Both languages support generics.

One of the most common use cases for generics is collections, such as arrays, collections, and maps.

We can use them to define our own types. Here’s how we define the generic Stack type in Swift:

struct Stack<Element> {
  var items = [Element] ()mutating func push(_ item: Element) {
    items.append(item)
  }
  mutating func pop(a) -> Element {
    return items.removeLast()
  }
}
Copy the code

Similarly, in Dart you can write:

class Stack<Element> {
  var items = <Element> []void push(Element item) {
    items.add(item)
  }
  void pop() -> Element {
    return items.removeLast()
  }
}
Copy the code

Generics are very useful and powerful in Swift, where they can be used to define type constraints and associated types in the protocol.

Access control

Swift document description is as follows:

Access control restricts access to your code from code in other source files and modules. This feature hides the implementation details of the code and specifies a preferred interface through which the code can be accessed and used.

Swift has five access levels: Open, public, internal, file-private, and private.

These keywords are used to work in the context of modules and source files. The document description is as follows:

A module is a code distribution unit – a framework or application that is built and published as a unit that can be imported in another module using Swift’s import keyword.

The Open and public access levels allow code to be accessed outside the module.

The private and file-private access levels make code inaccessible outside of the files it defines.

Such as:

public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
Copy the code

Access levels in Dart are simpler, limited to public and private. The document description is as follows:

Unlike Java, Dart does not have the keywords public, protected, and private. An identifier is private if it begins with an underscore _.

Such as:

class HomePage extends StatefulWidget { // public
  @override
  _HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {... }// private
Copy the code

Dart and Swift have different design goals for access control. Therefore, the level of access is very different.

Asynchronous programming: Future

Asynchronous programming is where Dart really shines.

Some form of asynchronous programming is required when processing tasks, such as:

  • Download content from the Web
  • Communicate with the back-end service
  • An operation that runs for a long time

In these cases, it is best not to block the main thread of execution, which could cause our program to get stuck.

The Dart documentation is described as follows:

Asynchronous operations allow your program to perform other operations while waiting for one task to complete. Dart uses Future objects to represent the results of asynchronous operations. To use a Future, you can use async/await or the Future API.

As an example, let’s look at how we use asynchronous programming:

  • Authenticate users using the server
  • Store access tokens to protect storage
  • Get user profile information

In Dart, this can be done using a combination of Future and async/await:

Future<UserProfile> getUserProfile(UserCredentials credentials) async {
  final accessToken = await networkService.signIn(credentials);
  await secureStorage.storeToken(accessToken, forUserCredentials: credentials);
  return await networkService.getProfile(accessToken);
}
Copy the code

In Swift, async/await is not supported and we can only do this with closures:

func getUserProfile(credentials: UserCredentials, completion: (_ result: UserProfile) -> Void) {
  networkService.signIn(credentials) { accessToken in
    secureStorage.storeToken(accessToken) {
      networkService.getProfile(accessToken, completion: completion)
    }
  }
}
Copy the code

This results in a “pyramid of doom” due to nested completion blocks. In this case, error handling becomes very difficult.

In Dart, handling the error in the code above simply adds a try/catch block to the getUserProfile method around the code.

For reference, it has been suggested to add async/await to Swift in the future. It is described in detail in the following proposal:

  • Async/Await proposal for Swift

Before implementing it, developers can use third-party libraries, such as Google’s Promises library.

Asynchronous programming: Stream

Dart implements Stream as part of the core library, but Swift does not.

The Dart documentation is described as follows:

A Stream is an asynchronous sequence of events.

Streams are the foundation of reactive programs, and they play an important role in state management.

For example, Stream is an excellent choice for searching for content, and every time a user updates the text in the search field, a new set of results is issued.

Stream is not included in the Swift core library. However, third-party libraries such as RxSwift provide support for convection.

Stream is a broad topic that will not be discussed in detail here.

Memory management

Dart manages memory using an advanced garbage collection scheme.

Swift manages memory through automatic reference counting (ARC).

This ensures good performance because memory is released immediately when it is no longer in use.

However, it does shift some of the burden from the compiler to the developer.

In Swift, we need to consider the life cycle and ownership of objects and use appropriate keywords (weak, strong, unowned) to avoid circular references.

Compile and execute

First, let’s look at the important differences between JIT and AOT compilers:

JIT

JIT compilers run during program execution, which is just-in-time compilation.

JIT compilers are often used with dynamic languages where types are not determined in advance. JIT programs run through an interpreter or virtual machine (VM).

AOT

Before running, the AOT compiler runs during program creation.

AOT compilers are often used with static languages that know the type of data. AOT programs are compiled into native machine code and executed directly by the hardware at run time.

Here’s a quote from Wm Leler:

When AOT compilation is done during development, it always results in a longer development cycle (the time between making changes to the program and being able to execute the program to see the results of the changes). But AOT compilation lets programs run more predictably without pausing for analysis and compilation at run time. Aot-compiled programs can also start quickly (because they have already been compiled). JIT compilation, on the other hand, provides a faster development cycle but can result in slower or clumsier execution. In particular, JIT compilers have slower startup times because when the program starts running, the JIT compiler must analyze and compile the code before executing it. Studies show that many people give up if it takes more than a few seconds to start.

As a static language, Swift is compiled ahead of time.

Dart supports both AOT and JIT. This provides significant advantages when used with the Flutter. Look at the following description:

JIT compilation during development, using a faster compiler. Then, when the application is ready to be published, it is compiled into AOT. As a result, with advanced tools and compilers, Dart offers the best of both worlds: extremely fast development cycles and fast execution and startup times. – Wm Leler

With Dart, you get the best of both worlds.

Swift has the major drawback of AOT compilation. That is, compilation time increases with the size of the code base.

For medium-sized applications (between 10K and 100K lines), compiling the application can easily take a few minutes.

This is not the case for Flutter applications, where subsecond hot loading is constant regardless of the size of the code base.

Other features not covered

The following features are not covered in this article because they are very similar in Dart and Swift:

  • The operator
  • string
  • Optional chains in Swift (called conditional member access in Dart).

concurrent

  • Concurrent programming passes in DartisolateTo provide.
  • Swift usedGrand Central Dispatch (GCD)And the distribution queue.

Those Swift features that I love that are missing from Dart

  • Structs
  • Enums with associated type
  • Optionals

Those Dart features I love that are missing from Swift

  • The JIT compiler
  • The Future andawait/async
  • Stream andyield/async*

conclusion

Dart and Swift are excellent languages for building modern mobile applications as well as other applications.

Both languages have their own unique advantages.

In comparing mobile application development and tools in both languages, I feel Dart has the upper hand. This is due to the JIT compiler, which is the basis for stateful hot loading in Flutter.

When building an application, hot loading can greatly increase productivity because it can speed up the development cycle from seconds or minutes to less than a second.

Development time is more resource-intensive than computing time.

Therefore, optimizing developer time is a very smart move.

On the other hand, I feel Swift has a very strong type system. The integration of type safety into all of Swift’s language features makes it more natural to develop robust programs.

Once we put aside our personal preferences, programming languages are tools. As developers, it’s our job to choose the best tool for our job.

In any case, we can hope that the two languages will learn from each other’s best ideas as they develop.

Pay attention to our

Please follow our official account ios-tips and join our group to discuss issues (add coldlight_hh on wechat).