Dart vs Swift: srovnání

Logo Dart je licencováno na základě licence Creative Commons Attribution 3.0 Unported.

Dart a Swift jsou mé dva oblíbené programovací jazyky. Použil jsem je značně v komerčním a open-source kódu.

Tento článek nabízí vzájemné srovnání mezi šipkami a Swift a jeho cílem je:

  • Zvýrazněte rozdíly mezi nimi.
  • Buďte referencí pro vývojáře přecházející z jednoho jazyka do druhého (nebo pomocí obou).

Nějaký kontext:

  • Dart power Flutter, framework společnosti Google pro vytváření krásných nativních aplikací z jediné kódové základny.
  • Swift pohání sady SDK společnosti Apple napříč systémy iOS, macOS, tvOS a watchOS.

Následující srovnání je provedeno napříč hlavními rysy obou jazyků (od Dart 2.1 a Swift 4.2). Vzhledem k tomu, že diskuse o každém prvku do hloubky přesahuje rámec tohoto článku, přidávám případně odkazy na další čtení.

Obsah

  • Srovnávací tabulka
  • Proměnné
  • Inference typu
  • Proměnné / neměnné proměnné
  • Funkce
  • Pojmenované a nepojmenované parametry
  • Volitelné a výchozí parametry
  • Uzávěry
  • Tuples
  • Řídicí tok
  • Sbírky (sady, sady, mapy)
  • Nullability a volitelné doplňky
  • Třídy
  • Dědictví
  • Vlastnosti
  • Protokoly / abstraktní třídy
  • Mixiny
  • Rozšíření
  • Výčty
  • Struktury
  • Vypořádání se s chybou
  • Generics
  • Řízení přístupu
  • Asynchronní programování: budoucnost
  • Asynchronní programování: Streamy
  • Správa paměti
  • Kompilace a provedení
  • Další funkce nejsou pokryty
  • Moje oblíbené funkce Swift v Dart chybí
  • Moje oblíbené funkce Dart chybí ve službě Swift
  • Závěr
  • Reference a úvěry

Srovnávací tabulka

Proměnné

Syntaxe proměnné deklarace vypadá v Dart takto:

Název řetězce;
int věk;
dvojitá výška;

A jako v Swift:

název var: String
var věk: Int
výška var: Double

Variabilní inicializace vypadá v Dart takto:

var name = 'Andrea';
var věk = 34;
var výška = 1,84;

A jako v Swift:

var name = "Andrea"
var věk = 34
var výška = 1,84

V tomto příkladu nejsou potřebné anotace typu. Je to proto, že oba jazyky mohou odvodit typy z výrazu na pravé straně přiřazení.

Když o tom mluvíme…

Inference typu

Inference typu znamená, že do Dart můžeme napsat následující:

var argumenty = {'argA': 'ahoj', 'argB': 42}; // Mapa 

A typ argumentů je překladačem automaticky vyřešen.

V Swift lze psát to samé jako:

var argumenty = ["argA": "ahoj", "argB": 42] // [řetězec: libovolný]

Některé další podrobnosti

Citace dokumentace pro Dart:

Analyzátor může odvodit typy pro pole, metody, lokální proměnné a nejobecnější argumenty typu. Pokud analyzátor nemá dostatek informací k odvození konkrétního typu, použije dynamický typ.

A pro Swift:

Funkce Swift značně používá odvozování typů, což vám umožňuje vynechat typ nebo část typu mnoha proměnných a výrazů v kódu. Například namísto zápisu var x: Int = 0 můžete psát var x = 0 a zcela vynechat typ - kompilátor správně vyvodí, že x pojmenuje hodnotu typu Int.

Dynamické typy

Proměnná, která může být jakéhokoli typu, je deklarována pomocí dynamického klíčového slova v Dart a klíčového slova Any v Swift.

Dynamické typy se běžně používají při čtení dat, jako je JSON.

Proměnné / neměnné proměnné

Proměnné lze prohlásit za proměnlivé nebo neměnné.

K deklarování proměnných proměnných používají oba jazyky klíčové slovo var.

var a = 10; // int (Šipka)
a = 20; // OK
var a = 10 // Int (Swift)
a = 20 // ok

Pro deklarování neměnných proměnných používá Dart finální a Swift používá let.

finální a = 10;
a = 20; // 'a': konečná proměnná, kterou lze nastavit pouze jednou.
nechť a = 10
a = 20 // Nelze přiřadit hodnotě: 'a' je konstanta 'let'

Poznámka: Dokumentace Dart definuje dvě klíčová slova, final a const, která fungují následovně:

Pokud jste nikdy neměli v úmyslu změnit proměnnou, použijte final nebo const, namísto var nebo vedle typu. Konečná proměnná může být nastavena pouze jednou; konstanta proměnná je konstanta kompilačního času. (Konstantní proměnné jsou implicitně konečné.) Konečná proměnná nejvyšší úrovně nebo třídy se inicializuje při prvním použití.

Další vysvětlení naleznete v tomto příspěvku na webu Dart:

finální znamená jedno-přiřazení. Konečná proměnná nebo pole musí mít inicializátor. Po přiřazení hodnoty nelze hodnotu konečné proměnné změnit. konečná úprava proměnných.

TL; DR: použijte final k definování neměnných proměnných v Dart.

Ve Swift deklarujeme konstanty s let. Citace:

Konstantní deklarace zavádí do vašeho programu konstantní pojmenovanou hodnotu. Konstantní deklarace se deklarují pomocí klíčového slova let a mají následující podobu:
nechat konstantní jméno: type = expression
Konstantní deklarace definuje neměnnou vazbu mezi konstantním názvem a hodnotou výrazu inicializátoru; po nastavení hodnoty konstanty ji nelze změnit.

Číst dále: Prohlášení Swift.

Funkce

Funkce jsou prvotřídní občané ve Swift a Dart.

To znamená, že stejně jako objekty, funkce mohou být předávány jako argumenty, ukládány jako vlastnosti nebo výsledkem.

Jako počáteční srovnání můžeme vidět, jak deklarovat funkce, které nepřijímají žádné argumenty.

V Dart před návratem před název metody je typ návratu:

void foo ();
int bar ();

V aplikaci Swift používáme jako příponu notaci -> T. To není vyžadováno, pokud není vrácená hodnota (Void):

func foo ()
funkční lišta () -> Int

Přečtěte si více:

  • Funkce šipek
  • Rychlé funkce

Pojmenované a nepojmenované parametry

Oba jazyky podporují pojmenované i nepojmenované parametry.

Ve Swift jsou parametry standardně pojmenovány:

func foo (jméno: String, věk: Int, výška: Double)
foo (jméno: "Andrea", věk: 34, výška: 1,84)

V Dartu definujeme pojmenované parametry pomocí složených závorek ({}):

void foo ({String name, int age, double height});
foo (jméno: 'Andrea', věk: 34, výška: 1,84);

V aplikaci Swift definujeme nepojmenované parametry pomocí podtržítka (_) jako externího parametru:

func foo (_ name: String, _ age: Int, _ height: Double)
foo ("Andrea", 34, 1,84)

V Dartu definujeme nepojmenované parametry vynecháním složených závorek ({}):

void foo (String name, int age, double height);
foo ('Andrea', 34, 1,84);

Další informace: Štítky argumentů funkcí a názvy parametrů v aplikaci Swift.

Volitelné a výchozí parametry

Oba jazyky podporují výchozí parametry.

V aplikaci Swift můžete definovat výchozí hodnotu pro libovolný parametr ve funkci přiřazením hodnoty k parametru za typem tohoto parametru. Pokud je definována výchozí hodnota, můžete při volání funkce tento parametr vynechat.
func foo (jméno: String, věk: Int = 0, výška: Double = 0.0)
foo (jméno: "Andrea", věk: 34) // jméno: "Andrea", věk: 34, výška: 0.0

Čtěte více: Výchozí hodnoty parametrů v aplikaci Swift.

V šipce mohou být volitelné parametry buď polohové, nebo pojmenované, ale nikoli oba.

// poziční volitelné parametry
void foo (String name, [int age = 0, double height = 0.0]);
foo („Andrea“, 34); // jméno: 'Andrea', věk: 34, výška: 0.0
// pojmenované volitelné parametry
void foo ({String name, int age = 0, double height = 0.0});
foo (jméno: 'Andrea', věk: 34); // jméno: 'Andrea', věk: 34, výška: 0.0

Další informace: Volitelné parametry v šipce.

Uzávěry

Jako objekty první třídy mohou být funkce předávány jako argumenty jiným funkcím nebo přiřazeny k proměnným.

V této souvislosti jsou funkce známé také jako uzávěry.

Zde je příklad funkce Dart, která iteruje přes seznam položek, pomocí uzávěru vytiskne index a obsah každé položky:

konečný seznam = ['jablka', 'banány', 'pomeranče'];
list.forEach ((item) => print ('$ {list.indexOf (item)}: $ item'));

Uzavření bere jeden argument (položku), vytiskne index a hodnotu této položky a nevrací žádnou hodnotu.

Všimněte si použití zápisu se šipkou (=>). To lze použít namísto jediného návratového příkazu uvnitř složených závorek:

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

Stejný kód ve Swift vypadá takto:

let list = ["jablka", "banány", "pomeranče"]
list.forEach ({print ("\ (String (description: list.firstIndex (of: $ 0))) \ ($ 0)")})

V tomto případě nespecifikujeme název argumentu předaného do uzavření a místo prvního argumentu použijeme $ 0. Toto je zcela volitelné a pokud chceme, můžeme použít pojmenovaný parametr:

list.forEach ({item in print ("\ (String (description: list.firstIndex (of: item)))) \ (item)")})

Uzávěry se často používají jako dokončovací bloky pro asynchronní kód v aplikaci Swift (viz část o asynchronním programování níže).

Přečtěte si více:

  • Dart Anonymní funkce
  • Swiftové uzávěry

Tuples

Z dokumentů Swift:

Tuples seskupí více hodnot do jedné složené hodnoty. Hodnoty v n-tici mohou být jakéhokoli typu a nemusí být stejného typu jako ostatní.

Lze je použít jako malé typy s nízkou hmotností a jsou užitečné při definování funkcí s více návratovými hodnotami.

Zde je návod, jak používat n-tice ve Swift:

nechť t = ("Andrea", 34, 1,84)
tisk (t.0) // vytiskne "Andrea"
tisk (t.1) // tiskne 34
tisk (t.2) // tiskne 1.84

Tuples jsou podporovány samostatným balíčkem v Dart:

const t = const Tuple3  ('Andrea', 34, 1,84);
tisk (t.item1); // tiskne 'Andrea'
tisk (t.item2); // tiskne 34
tisk (t.item3); // tiskne 1.84

Řídicí tok

Oba jazyky poskytují řadu příkazů toku řízení.

Příkladem jsou podmíněné příkazy pro a během smyček.

Jejich pokrytí by bylo poměrně zdlouhavé, takže odkazuji na oficiální dokumenty:

  • Swift Control Flow
  • Výkazy toku šipky

Sbírky (sady, sady, mapy)

Pole / seznamy

Pole jsou uspořádané skupiny objektů.

Pole lze vytvořit jako seznamy v Dart:

var emptyList =  []; // prázdný seznam
var list = [1, 2, 3]; // doslovný seznam
list.length; // 3
seznam [1]; // 2

Pole mají vestavěný typ ve Swift:

var emptyArray = [Int] () // prázdné pole
var array = [1, 2, 3] // literál literálu
array.count // 3
pole [1] // 2

Sady

Citace dokumentů Swift:

Sada ukládá odlišné hodnoty stejného typu do kolekce bez definovaného řazení. Můžete použít sadu namísto pole, když není pořadí položek důležité nebo pokud potřebujete zajistit, aby se položka objevila pouze jednou.

Toto je definováno třídou Set v Dart.

var emptyFruits =  {}; // prázdný soubor doslovný
var fruits = {'apple', 'banana'}; // set literál

Stejně tak ve Swift:

var emptyFruits = Set  ()
var fruits = Set  (["apple", "banana"])

Mapy / slovníky

Dokumenty Swift mají dobrou definici pro mapu / slovník:

Slovník ukládá přidružení mezi klíči stejného typu a hodnotami stejného typu do kolekce bez definovaného řazení. Každá hodnota je spojena s jedinečným klíčem, který funguje jako identifikátor této hodnoty ve slovníku.

Mapy jsou v Dart definovány takto:

var namesOfIntegers = Mapa  (); // prázdná mapa
var airport = {'YYZ': 'Toronto Pearson', 'DUB': 'Dublin'}; // doslovná mapa

Mapy se v Swift nazývají slovníky:

var namesOfIntegers = [Int: String] () // prázdný slovník
var airport = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] // slovníček doslovný

Přečtěte si více:

  • Kolekce šipek
  • Swiftové sbírky (zejména doporučuji sekci o sadách).

Nullability a volitelné doplňky

V Dart může být libovolný objekt nulový. A pokus o přístup k metodám nebo proměnným nulových objektů vede k výjimce nulového ukazatele. Toto je jeden z nejčastějších zdrojů chyb (pokud ne nejčastějších) v počítačových programech.

Od začátku měl Swift volitelné doplňky, vestavěnou jazykovou funkci pro deklarování, zda objekty mohou nebo nemohou mít hodnotu. Citace dokumentů:

Doplňky používáte v situacích, kdy hodnota může chybět. Volitelný doplněk představuje dvě možnosti: Buď existuje nějaká hodnota a volitelnou volbou můžete zrušit přístup k této hodnotě, nebo neexistuje vůbec žádná hodnota.

Na rozdíl od toho můžeme pomocí nepovinných proměnných zaručit, že budou mít vždy hodnotu:

var x: Int? // volitelné
var y: Int = 1 // nepovinné, musí být inicializováno

Poznámka: Říká se, že proměnná Swift je volitelná, je zhruba stejná jako říká, že proměnná Dart může být null.

Bez jazykové podpory doplňků můžeme zkontrolovat za běhu, pouze pokud je proměnná null.

Pomocí doplňků kódujeme tyto informace místo kompilace. Můžeme rozbalit doplňky, abychom mohli bezpečně zkontrolovat, zda mají hodnotu:

func showOptional (x: Int?) {
  // Jako nejlepší postup použijte raději `guard let` než` if nechte`
  pokud necháme x = x {// rozbalit volitelné
    tisk (x)
  } jinde {
    print ("no value")
  }
}
showOptional (x: nil) // vytiskne "no value"
showOptional (x: 5) // vytiskne "5"

A pokud víme, že proměnná musí mít hodnotu, můžeme použít nepovinné:

func showNonOptional (x: Int) {
  tisk (x)
}
showNonOptional (x: nil) // [compile error] Nil není kompatibilní s očekávaným typem argumentu 'Int'
showNonOptional (x: 5) // vytiskne "5"

První příklad výše může být implementován takto v Dart:

void showOptional (int x) {
  if (x! = null) {
    tisk (x);
  } jinde {
    print ('no value');
  }
}
showOptional (null) // vytiskne "no value"
showOptional (5) // vytiskne "5"

A druhý, jako je tento:

void showNonOptional (int x) {
  tvrzení (x! = null);
  tisk (x);
}
showNonOptional (null) // [runtime error] Nevýhodná výjimka: Assertion failed
showNonOptional (5) // vytiskne "5"

Mít volitelné znamená, že můžeme zachytit chyby spíše při kompilaci než za běhu. A včasné zachycení chyb vede k bezpečnějšímu kódu s menším počtem chyb.

Dartova nedostatečná podpora doplňků je nějak zmírněna použitím tvrzení (a @ požadovaná anotace pro pojmenované parametry).

Ty se hojně používají v Flutter SDK, ale mají za následek další kód kotlové desky.

Pro záznam existuje návrh na přidání nullových typů do Dart.

O volitelném příslušenství je mnohem více, než jsem zde uvedl. Pro dobrý přehled viz: Doplňky v aplikaci Swift.

Třídy

Třídy jsou hlavním stavebním kamenem pro psaní programů v objektově orientovaných jazycích.

Třídy jsou podporovány Dart a Swift, s určitými rozdíly.

Syntax

Tady je třída s inicializátorem a třemi členskými proměnnými ve Swift:

třída osoba {
  nechte jméno: String
  let age: Int
  výška letu: Double
  init (jméno: String, věk: Int, výška: Double) {
    self.name = jméno
    self.age = věk
    self.height = výška
  }
}

A to samé v Dart:

třída osoba {
  Osoba ({this.name, this.age, this.height});
  finále String name;
  konečný int věk;
  konečná dvojitá výška;
}

Všimněte si použití tohoto. [PropertyName] ve konstruktoru Dart. Toto je syntaktický cukr pro nastavení proměnných členů instance před spuštěním konstruktoru.

Tovární stavitelé

V Dartu je možné vytvářet tovární konstruktéry. Citace:

Při implementaci konstruktoru, který ne vždy vytvoří novou instanci své třídy, použijte klíčové slovo factory.

Jeden praktický případ použití továrních konstruktérů je při vytváření modelové třídy od JSON:

třída osoba {
  Osoba ({this.name, this.age, this.height});
  finále String name;
  konečný int věk;
  konečná dvojitá výška;
  továrna Person.fromJSON (mapa  json) {
    String name = json ['name'];
    int age = json ['age'];
    double height = json ['height'];
    návratová osoba (jméno: jméno, věk: věk, výška: výška);
  }
}
var p = Person.fromJSON ({
  'name': 'Andrea',
  'věk': 34,
  'výška': 1,84,
});

Přečtěte si více:

  • Třídy šipek
  • Swift struktury a třídy

Dědictví

Swift používá model s jednou dědičností, což znamená, že každá třída může mít pouze jednu nadřazenou třídu. Třídy Swift mohou implementovat více rozhraní (známých také jako protokoly).

Třídy šipek mají dědičnost na bázi mixinu. Citace dokumentů:

Každý objekt je instancí třídy a všechny třídy pocházejí z Object. Dědičnost založená na mixinu znamená, že ačkoli každá třída (kromě Object) má přesně jednu nadtriedu, tělo třídy lze znovu použít ve více hierarchiích třídy.

Zde je jedna dědičnost v akci ve Swift:

třída Vozidlo {
  let wheelCount: Int
  init (wheelCount: Int) {
    self.wheelCount = wheelCount
  }
}
třída Bicycle: Vehicle {
  init () {
    super.init (wheelCount: 2)
  }
}

A v Dart:

třída Vozidlo {
  Vozidlo ({this.wheelCount});
  finální int wheelCount;
}
třída Bicycle rozšiřuje vozidlo {
  Bicycle (): super (wheelCount: 2);
}

Vlastnosti

V Dartu se nazývají proměnné instance a jednoduše vlastnosti ve Swift.

V aplikaci Swift existuje rozdíl mezi uloženými a vypočítanými vlastnostmi:

třída Circle {
  init (poloměr: Double) {
    self.radius = radius
  }
  let radius: Double // uložená vlastnost
  var průměr: Dvojitá {// vlastnost jen pro výpočet
    návratový poloměr * 2.0
  }
}

Ve hře Dart máme stejné rozlišení:

třída Circle {
  Circle ({this.radius});
  konečný dvojitý poloměr; // uložená vlastnost
  dvojnásobný průměr = = poloměr * 2,0; // vypočtená vlastnost
}

Kromě getters pro vypočítané vlastnosti, můžeme také definovat setters.

Pomocí výše uvedeného příkladu můžeme přepsat vlastnost průměru tak, aby zahrnovala setter:

var průměr: Dvojitá {// vypočtená vlastnost
  dostat {
    návratový poloměr * 2.0
  }
  set {
    radius = newValue / 2.0
  }
}

V Dart můžeme přidat samostatný setter jako je tento:

nastavený průměr (dvojitá hodnota) => poloměr = hodnota / 2,0;

Pozorovatelé nemovitostí

To je zvláštní rys Swift. Citace:

Pozorovatelé nemovitostí sledují změny hodnoty nemovitosti a reagují na ně. Pozorovatelé nemovitosti se nazývají pokaždé, když je nastavena hodnota vlastnosti, i když nová hodnota je stejná jako aktuální hodnota vlastnosti.

Takto je lze použít:

var průměr: Dvojitá {// vlastnost jen pro výpočet
  willSet (newDiameter) {
    print ("stará hodnota: \ (průměr), nová hodnota: \ (newDiameter)")
  }
  didSet {
    print ("old value: \ (oldValue), nová hodnota: \ (průměr)")
  }
}

Přečtěte si více:

  • Proměnné instance oštěpu, getery a setters
  • Vlastnosti Swift

Protokoly / abstraktní třídy

Zde hovoříme o konstruktu použitém k definování metod a vlastností, aniž by bylo specifikováno, jak jsou implementovány. Toto je známé jako rozhraní v jiných jazycích.

V aplikaci Swift se rozhraní nazývají protokoly.

tvar protokolu {
  func area () -> Double
}
třída Square: Shape {
  nechat stranu: Double
  init (strana: Double) {
    self.side = side
  }
  func area () -> Double {
    zpětná strana * strana
  }
}

Šipka má podobný konstrukt známý jako abstraktní třída. Abstraktní třídy nelze konkretizovat. Mohou však definovat metody, které mají implementaci.

Výše uvedený příklad lze napsat jako Dart:

abstraktní třída tvar {
  dvojitá oblast ();
}
třída Square rozšiřuje tvar {
  Square ({this.side});
  poslední dvojitá strana;
  dvojitá oblast () => strana * strana;
}

Přečtěte si více:

  • Protokoly Swift
  • Šipky abstraktní třídy

Mixiny

Ve hře Dart je mixin pouze běžná třída, kterou lze znovu použít ve více hierarchiích tříd.

Zde je návod, jak bychom mohli rozšířit třídu Person, kterou jsme definovali dříve, pomocí mixu NameExtension:

abstraktní třída NameExtension {
  String get name;
  String get uppercaseName => name.toUpperCase ();
  String get LowercaseName => name.toLowerCase ();
}
class Person with NameExtension {
  Osoba ({this.name, this.age, this.height});
  finále String name;
  konečný int věk;
  konečná dvojitá výška;
}
var person = Osoba (jméno: 'Andrea', věk: 34, výška: 1,84);
print (person.uppercaseName); // 'ANDREA'

Číst dál: Dart Mixins

Rozšíření

Rozšíření jsou funkcí jazyka Swift. Citace dokumentů:

Rozšíření přidávají nové funkce do existující třídy, struktury, výčtu nebo typu protokolu. To zahrnuje možnost rozšířit typy, pro které nemáte přístup k původnímu zdrojovému kódu (známý jako retroaktivní modelování).

To není možné u mixinů v Dart.

Půjčením výše uvedeného příkladu můžeme třídu Person rozšířit takto:

osoba rozšíření {
  var uppercaseName: String {
    návrat name.uppercased ()
  }
  var LowercaseName: String {
    návrat name.lowercased ()
  }
}
var person = Osoba (jméno: "Andrea", věk: 34, výška: 1,84)
print (person.uppercaseName) // "ANDREA"

Rozšíření je mnohem více, než jsem zde uvedl, zejména pokud se používají ve spojení s protokoly a generiky.

Jedním z velmi běžných případů použití rozšíření je přidání souladu se stávajícími typy protokolů. Například můžeme pomocí rozšíření přidat možnosti serializace do existující třídy modelů.

Číst dál: Swift Extensions

Výčty

Dart má nějakou velmi základní podporu pro výčty.

Výčty ve Swift jsou velmi silné, protože podporují přidružené typy:

enum NetworkResponse {
  případ úspěch (tělo: Data)
  selhání případu (chyba: chyba)
}

To umožňuje psát logiku takto:

přepínač (odpověď) {
  case .success (let data):
    // udělejte něco s (nepovinnými) daty
  case .failure (nechat chybu):
    // udělej něco s (nepovinnou) chybou
}

Všimněte si, jak se data a chybové parametry vzájemně vylučují.

V Dart nemůžeme přiřadit enumům další hodnoty a výše uvedený kód může být implementován podle těchto řádků:

class NetworkResponse {
  NetworkResponse ({this.data, this.error})
  // tvrzení, aby se data a chyby vzájemně vylučovaly
  : assert (data! = null && error == null || data == null && error! = null);
  konečná data Uint8List;
  konečná chyba řetězce;
}
var response = NetworkResponse (data: Uint8List (0), chyba: null);
if (response.data! = null) {
  // použití dat
} jinde {
  // použití chyby
}

Pár poznámek:

  • Zde používáme tvrzení, abychom kompenzovali skutečnost, že nemáme opce.
  • Kompilátor nám nemůže pomoci zkontrolovat všechny možné případy. Důvodem je, že ke zpracování odpovědi nepoužíváme přepínač.

Stručně řečeno, výčty Swift jsou mnohem silnější a výraznější než ve hře Dart.

Knihovny třetích stran, například Dart Sealed Unions, poskytují podobné funkce, jaké nabízejí výčty Swift, a mohou pomoci zaplnit mezeru.

Číst dál: Swift Enums.

Struktury

V Swift můžeme definovat struktury a třídy.

Oba konstrukty mají mnoho společných věcí a některé rozdíly.

Hlavní rozdíl spočívá v tom, že:

Třídy jsou referenční typy a struktury jsou hodnotové typy

Citace dokumentace:

Typ hodnoty je typ, jehož hodnota se zkopíruje, když je přiřazena k proměnné nebo konstantě nebo když je předána funkci.
Všechny struktury a výčty jsou typy hodnot v aplikaci Swift. To znamená, že všechny vytvořené instance struktury a výčtu - a všechny typy hodnot, které mají jako vlastnosti - budou vždy zkopírovány, když jsou předány v kódu.
Na rozdíl od typů hodnot se referenční typy nekopírují, jsou-li přiřazeny k proměnné nebo konstantě nebo když jsou předány funkci. Spíše než kopie se používá odkaz na stejnou existující instanci.

Chcete-li zjistit, co to znamená, zvažte následující příklad, ve kterém změníme třídu Person tak, aby byla proměnlivá:

třída osoba {
  název var: String
  var věk: Int
  výška var: Double
  init (jméno: String, věk: Int, výška: Double) {
    self.name = jméno
    self.age = věk
    self.height = výška
  }
}
var a = Osoba (jméno: "Andrea", věk: 34, výška: 1,84)
var b = a
b.age = 35
tisk (a.age) // tiskne 35

Pokud znovu definujeme osobu jako strukturu, máme toto:

struct Person {
  název var: String
  var věk: Int
  výška var: Double
  init (jméno: String, věk: Int, výška: Double) {
    self.name = jméno
    self.age = věk
    self.height = výška
  }
}
var a = Osoba (jméno: "Andrea", věk: 34, výška: 1,84)
var b = a
b.age = 35
tisk (a.age) // tiskne 34

Struktury je mnohem více, než jsem zde popsal.

Struktury mohou být použity ke zpracování efektivních dat a modelů ve Swift, což vede k robustnímu kódu s menším počtem chyb.

Pro lepší přehled si přečtěte: Struktury a třídy v Swift.

Vypořádání se s chybou

Použití definice z dokumentů Swift:

Zpracování chyb je proces reakce a zotavení z chybových podmínek ve vašem programu.

Dart i Swift používají metodu try / catch jako techniku ​​pro řešení chyb, s určitými rozdíly.

V Dartu může jakákoli metoda vyvolat výjimku jakéhokoli typu.

třída BankAccount {
  BankAccount ({this.balance});
  dvojitá rovnováha;
  neplatný výběr (dvojnásobná částka) {
    if (částka> zůstatek) {
      vyvolání výjimky („nedostatečné prostředky“);
    }
    zůstatek - = částka;
  }
}

Výjimky lze zachytit pomocí bloku try / catch:

var účet = BankAccount (zůstatek: 100);
Snaž se {
  account.withdraw (50); // OK
  account.withdraw (200); // vyvolá
} úlovek (e) {
  tisk (e); // tiskne 'Výjimka: nedostatečné prostředky'
}

V aplikaci Swift výslovně deklarujeme, kdy může metoda vyvolat výjimku. To se provádí pomocí klíčového slova vyvolání a všechny chyby musí odpovídat protokolu Error:

enum AccountError: Chyba {
  případ nedostatečné fondy
}
třída BankAccount {
  vyvážení var: Double
  init (zůstatek: Double) {
    self.balance = zůstatek
  }
  func výběr (částka: Double) vyvolá {
    pokud částka> zůstatek {
      vyvolejte AccountError.insufficientFunds
    }
    zůstatek - = částka
  }
}

Při manipulaci s chybami používáme klíčové slovo try uvnitř bloku do / catch.

var účet = BankAccount (zůstatek: 100)
dělat {
  zkuste account.withdraw (množství: 50) // ok
  zkuste account.withdraw (množství: 200) // vyvolá
} chytit AccountError.insufficientFunds {
  tisk („Nedostatek prostředků“)
}

Všimněte si, jak je klíčové slovo try povinné při volání metod, které mohou házet.

A samotná chyba je silně napsaná, takže můžeme mít více bloků odchytu, které pokryjí všechny možné případy.

zkoušet zkoušet zkoušet!

Swift nabízí méně podrobný způsob řešení chyb.

Můžeme použít vyzkoušet? bez bloku do / catch. A to způsobí ignorování výjimek:

var účet = BankAccount (zůstatek: 100)
Snaž se? account.withdraw (množství: 50) // ok
Snaž se? account.withdraw (počet: 200) // tiše selže

Nebo pokud jsme si jisti, že metoda nevyhází, můžeme použít zkuste !:

var účet = BankAccount (zůstatek: 100)
Snaž se! account.withdraw (množství: 50) // ok
Snaž se! account.withdraw (množství: 200) // pád

Výše uvedený příklad způsobí selhání programu. Proto vyzkoušejte! se ve výrobním kódu nedoporučuje a je vhodnější při psaní testů.

Celkově lze říci, že explicitní povaha zpracování chyb ve službě Swift je v návrhu rozhraní API velmi prospěšná, protože usnadňuje zjištění, zda metoda může nebo nemůže hodit.

Podobně použití volání metod vyzkoušet upozorňuje na kód, který může házet, což nás nutí zvážit chyby.

V tomto ohledu se zpracování chyb cítí bezpečněji a robustněji než v Dartu.

Přečtěte si více:

  • Výjimky šipek
  • Swift Error handling

Generics

Citace dokumentů Swift:

Obecný kód umožňuje psát flexibilní, opakovaně použitelné funkce a typy, které mohou pracovat s jakýmkoli typem, v závislosti na definovaných požadavcích. Můžete psát kód, který se vyhne duplicitě a jasně vyjadřuje svůj úmysl.

Generika jsou podporována oběma jazyky.

Jedním z nejčastějších případů použití generik jsou sbírky, jako jsou pole, sady a mapy.

A můžeme je použít k definování vlastních typů. Takto definujeme obecný typ zásobníku v aplikaci Swift:

struct Stack  {
  var items = [Element] ()
  mutating func push (_ item: Element) {
    items.append (item)
  }
  mutující func pop () -> Element {
    návrat items.removeLast ()
  }
}

Podobně bychom v Dart psali:

třída Stack  {
  var items =  []
  void push (položka prvku) {
    items.add (item)
  }
  void pop () -> Element {
    návrat items.removeLast ()
  }
}

Generika jsou velmi užitečná a výkonná ve službě Swift, kde je lze použít k definování typových omezení a souvisejících typů v protokolech.

Doporučuji přečíst si dokumentaci pro více informací:

  • Swift Generics
  • Dart Generics

Řízení přístupu

Citace dokumentace Swift:

Řízení přístupu omezuje přístup k částem kódu z kódu v jiných zdrojových souborech a modulech. Tato funkce umožňuje skrýt podrobnosti implementace vašeho kódu a určit upřednostňované rozhraní, prostřednictvím kterého lze daný kód získat a použít.

Swift má pět úrovní přístupu: otevřený, veřejný, interní, soubor-soukromý a soukromý.

Používají se v souvislosti s prací s moduly a zdrojovými soubory. Citace:

Modul je jedna jednotka distribuce kódu - rámec nebo aplikace, která je vytvořena a dodávána jako jedna jednotka a kterou lze importovat jiným modulem s klíčovým slovem importu Swift.

Úroveň otevřeného a veřejného přístupu lze použít k zpřístupnění kódu mimo moduly.

Úroveň soukromého přístupu a soukromého přístupu k souboru lze použít k tomu, aby kód nebyl přístupný mimo soubor, ve kterém je definován.

Příklady:

veřejná třída SomePublicClass {}
interní třída SomeInternalClass {}
fileprivate třída SomeFilePrivateClass {}
soukromá třída SomePrivateClass {}

Úrovně přístupu jsou v Dart jednodušší a omezeny na veřejné i soukromé. Citace:

Na rozdíl od Java nemá Dart klíčová slova veřejná, chráněná ani soukromá. Pokud identifikátor začíná podtržítkem _, je soukromý pro jeho knihovnu.

Příklady:

class HomePage rozšiřuje StatefulWidget {// public
  @override
  _HomePageState createState () => _HomePageState ();
}
class _HomePageState rozšiřuje State  {...} // private

Řízení přístupu bylo navrženo s různými cíli pro Dart a Swift. V důsledku toho jsou úrovně přístupu velmi odlišné.

Přečtěte si více:

  • Řízení rychlého přístupu
  • Šipky a viditelnost

Asynchronní programování: budoucnost

Asynchronní programování je oblast, kde Dart opravdu svítí.

Při řešení případů použití je třeba nějaká forma asynchronního programování, jako například:

  • Stahování obsahu z webu
  • Mluvíme s backendovou službou
  • Proveďte dlouhodobě probíhající operace

V těchto případech je nejlepší neblokovat hlavní vlákno provádění, což může zamrznout naše programy.

Citace dokumentace Dart:

Asynchronní operace umožní vašemu programu dokončit další práci a čekat na dokončení operace. Šipka používá Future objekty (futures) k reprezentaci výsledků asynchronních operací. Pro práci s futures můžete použít buď asynchronní a čekající nebo Future API.

Ukážeme si například, jak můžeme asynchronní programování použít k:

  • autentizace uživatele pomocí serveru
  • uložte přístupový token do zabezpečeného úložiště
  • získat informace o uživatelském profilu

V Dart to lze provést pomocí async / čekat v kombinaci s Futures:

Budoucí  getUserProfile (pověření UserCredentials) async {
  final accessToken = await networkService.signIn (pověření);
  await secureStorage.storeToken (accessToken, forUserCredentials: pověření);
  návrat await networkService.getProfile (accessToken);
}

V aplikaci Swift neexistuje async / čeká podpora a můžeme toho dosáhnout pouze s uzávěry (dokončovací bloky):

func getUserProfile (pověření: UserCredentials, dokončení: (_ výsledek: UserProfile) -> Void) {
  networkService.signIn (pověření) {accessToken in
    secureStorage.storeToken (accessToken) {
      networkService.getProfile (accessToken, dokončení: dokončení)
    }
  }
}

To vede k „pyramidě zkázy“ kvůli vnořeným dokončovacím blokům. A zpracování chyb se v tomto scénáři stává velmi obtížným.

V Dartu je manipulace s chybami ve výše uvedeném kódu jednoduše provedena přidáním bloku try / catch kolem kódu k metodě getUserProfile.

Pro informaci, existuje návrh přidat async / čekat na Swift v budoucnosti. To je podrobně zdokumentováno zde:

  • Async / Čekejte na návrh Swift

Dokud nebude implementována, mohou vývojáři používat knihovny třetích stran, jako je tato knihovna Promises od společnosti Google.

Pokud jde o Dart, vynikající dokumentaci najdete zde:

  • Asynchronní programování pomocí šipek: budoucnost

Asynchronní programování: Streamy

Proudy jsou implementovány jako součást základních knihoven Dart, ale ne ve Swift.

Citace dokumentace Dart:

Proud je sekvence asynchronních událostí.

Proudy jsou základem reaktivních aplikací, kde hrají při řízení státu důležitou roli.

Například datové proudy jsou skvělou volbou pro prohledávání obsahu, kdy se nová skupina výsledků vydává pokaždé, když uživatel aktualizuje text ve vyhledávacím poli.

Datové toky nejsou zahrnuty v základních knihovnách Swift. Knihovny třetích stran, jako je RxSwift, nabízejí podporu streamů a mnohem více.

Proudy jsou široké téma, které zde není diskutováno.

Další informace: Asynchronní programování pomocí šipek: Streamy

Správa paměti

Dart spravuje paměť pomocí pokročilého schématu sběru odpadu.

Swift spravuje paměť pomocí automatického počítání referencí (ARC).

To zaručuje skvělý výkon, protože paměť se uvolní okamžitě, když se již nepoužívá.

Přesouvá však zátěž částečně z kompilátoru na vývojáře.

Ve službě Swift musíme přemýšlet o životním cyklu a vlastnictví objektů a používat příslušná klíčová slova (slabá, silná, neznámá) správně, aby nedošlo k udržení cyklů.

Další informace: Automatické počítání referencí Swift.

Kompilace a provedení

Zaprvé, důležité rozlišení mezi kompilátory just-in-time (JIT) a pre-time-time (AOT):

Kompilátory JIT

Kompilátor JIT se spustí během provádění programu a kompiluje se za běhu.

Kompilátory JIT se obvykle používají s dynamickými jazyky, kde typy nejsou předem stanoveny. Programy JIT běží přes tlumočník nebo virtuální stroj (VM).

Kompilátory AOT

Kompilátor AOT se spustí během vytváření programu, před spuštěním.

Kompilátory AOT se běžně používají se statickými jazyky, které znají typy dat. Programy AOT jsou kompilovány do nativního strojového kódu, který je spuštěn přímo hardwarem za běhu.

Cituji tento skvělý článek Wm Lelera:

Když je kompilace AOT prováděna během vývoje, vždy to vede k mnohem pomalejším vývojovým cyklům (doba mezi provedením změny v programu a provedením programu, aby se zobrazil výsledek změny). Kompilace AOT však vede k programům, které lze provádět předvídatelněji a bez pozastavení analýzy a kompilace za běhu. Kompilované programy AOT také začnou provádět rychlejší (protože již byly kompilovány).
Naopak kompilace JIT poskytuje mnohem rychlejší vývojové cykly, ale může vést k pomalejšímu nebo škubavějšímu provádění. Zejména kompilátory JIT mají pomalejší spouštěcí časy, protože když se program spustí, kompilátor JIT musí provést analýzu a kompilaci, než může být kód spuštěn. Studie ukázaly, že mnoho lidí aplikaci opustí, pokud spuštění spuštění trvá déle než několik sekund.

Jako statický jazyk je Swift kompilován předem.

Šipka může být sestavena jak AOT, tak JIT. To poskytuje významné výhody při použití s ​​Flutterem. Citace znovu:

Kompilace JIT se používá během vývoje pomocí kompilátoru, který je obzvláště rychlý. Poté, co je aplikace připravena k vydání, je kompilována AOT. V důsledku toho může Dart s pomocí pokročilých nástrojů a kompilátorů poskytnout to nejlepší z obou světů: extrémně rychlé vývojové cykly a rychlé provedení a spuštění. - Wm Leler

Se šipkou získáme to nejlepší z obou světů.

Swift trpí hlavní nevýhodou kompilace AOT. To znamená, že čas kompilace se zvyšuje s velikostí kódové základny.

U středně velké aplikace (mezi 10 a 100 000 řádků) může kompilace aplikace snadno trvat několik minut.

Není tomu tak u aplikací Flutter, kde neustále získáváme sekundové hot-reload, bez ohledu na velikost kódové základny.

Další funkce nejsou pokryty

Následující funkce nebyly pokryty, protože jsou docela podobné v Dart a Swift:

  • Operátoři (viz reference pro Swift a Dart)
  • Řetězce (viz reference pro Swift a Dart)
  • Volitelné řetězení ve službě Swift (známé jako podmíněný přístup členů v Dart).

Konkurence

  • Souběžné programování je v Dartu vybaveno izoláty.
  • Swift používá Grand Central Dispatch (GCD) a expediční fronty.

Moje oblíbené funkce Swift v Dart chybí

  • Struktury
  • Výčty s přidruženými typy
  • Volitelné

Moje oblíbené funkce Dart chybí ve službě Swift

  • Kompilátor just-in-time
  • Budoucnost s čekáním / async (viz async / očekávaná nabídka Chris Lattner)
  • Streamy s výnosem / async * (RxSwift nabízí superset toků pro reaktivní aplikace)

Závěr

Dart i Swift jsou vynikající jazyky, dobře se hodí pro vytváření moderních mobilních aplikací i mimo ni.

Ani jeden jazyk není nadřazený, protože oba mají své vlastní jedinečné silné stránky.

Když se dívám na vývoj mobilních aplikací a nástroje pro dva jazyky, cítím, že Dart má navrch. Je to způsobeno kompilátorem JIT, který je ve Flutteru základem stavového hot-reloadu.

A hot-reload přináší obrovský nárůst produktivity při vytváření aplikací, protože to urychluje vývojový cyklus ze sekund nebo minut na méně než sekundu.

Vývojářský čas je vzácnější zdroj než výpočetní čas.

Optimalizace času vývojáře je tedy velmi chytrý krok.

Na druhou stranu mám pocit, že Swift má velmi silný typ systému. Bezpečnost typu je zapracována do všech jazykových funkcí a přirozeně vede k robustním programům.

Jakmile zrušíme osobní preference, programovací jazyky jsou jen nástroji. A je naší prací jako vývojářů, abychom si pro práci vybrali nejvhodnější nástroj.

V každém případě můžeme doufat, že si oba jazyky půjčují od sebe ty nejlepší nápady, jak se vyvíjejí.

Reference a úvěry

Dart i Swift mají rozsáhlou sadu funkcí a nebyly zde plně pokryty.

Tento článek jsem si vypůjčil z oficiální dokumentace Swift and Dart, kterou najdete zde:

  • Programovací jazyk Swift
  • Prohlídka jazyka šipek

Sekce o kompilátorech JIT a AOT je navíc inspirována tímto skvělým článkem Wm Lelera:

  • Proč Flutter používá Dart

Něco mi chybělo? Dejte mi vědět v komentářích.

Šťastný kódování!

UPDATE: Můj kurz Flutter & Firebase Udemy je nyní k dispozici pro Early Access. Použijte tento odkaz k registraci (včetně slevového kódu):

  • Flutter & Firebase: Vytvořte kompletní aplikaci pro iOS a Android

Další články a videonávody najdete v sekci Coding With Flutter.

Na Twitteru jsem @ biz84. Můžete také vidět moji stránku GitHub.