Дартс срещу Суифт: сравнение

Логото на Dart е лицензирано под лиценз Creative Commons Attribution 3.0 Unported.

Dart и Swift са ми два любими езика за програмиране. Използвах ги широко в търговски и отворен код.

Тази статия предлага паралелно сравнение между Dart и Swift и има за цел:

  • Маркирайте разликите между двете.
  • Бъдете референция за разработчиците, които преминават от един език на другия (или използват и двете).

Някой контекст:

  • Dart power Flutter, рамката на Google за изграждане на красиви местни приложения от една кодова база.
  • Swift захранва SDK-та на Apple в iOS, macOS, tvOS и watchOS.

Следното сравнение е направено в основните характеристики на двата езика (от Dart 2.1 и Swift 4.2). Тъй като задълбоченото обсъждане на всяка функция е извън обхвата на тази статия, включвам препратки за допълнително четене, когато е подходящо.

Съдържание

  • Таблица за сравнение
  • Променливи
  • Тип извод
  • Променливи / неизменни променливи
  • Функции
  • Наименовани и неименувани параметри
  • Незадължителни и стандартни параметри
  • Приключване
  • кортежи
  • Контролен поток
  • Колекции (масиви, набори, карти)
  • Променливост и по избор
  • класове
  • наследяване
  • Имоти
  • Протоколи / Абстрактни часове
  • Mixins
  • Разширения
  • Enums
  • Structs
  • Грешка при работа
  • Generics
  • Контрол на достъпа
  • Асинхронно програмиране: Фючърси
  • Асинхронно програмиране: Потоци
  • Управление на паметта
  • Съставяне и изпълнение
  • Други функции не са обхванати
  • Любимите ми функции Swift липсват от Dart
  • Любимите ми функции на Dart липсват от Swift
  • заключение
  • Позовавания и кредити

Таблица за сравнение

Променливи

Синтаксисът на променлива декларация изглежда така в Dart:

Име на струна;
вътрешна възраст;
двойна височина;

И така в Swift:

var име: String
вар възраст: Int
вар височина: Двойна

Променливата инициализация изглежда така в Dart:

var name = 'Андреа';
вар възраст = 34;
вар височина = 1,84;

И така в Swift:

var name = "Андреа"
вар възраст = 34
вар височина = 1,84

В този пример поясненията за типа не са необходими. Това е така, защото и двата езика могат да изведат типовете от израза от дясната страна на заданието.

Говорейки за което ...

Тип извод

Тип извод означава, че можем да напишем следното в Dart:

var argument = {'argA': 'здравей', 'argB': 42}; // Карта 

И типът аргументи се решава автоматично от компилатора.

В Swift може да бъде написано същото като:

var argument = ["argA": "здравей", "argB": 42] // [String: Any]

Още малко подробности

Цитиране на документацията за Dart:

Анализаторът може да изведе типове за полета, методи, локални променливи и повечето общи аргументи от тип. Когато анализаторът няма достатъчно информация за извеждане на конкретен тип, той използва динамичния тип.

А за Swift:

Swift използва широко заключение за тип, което ви позволява да пропуснете типа или част от типа на много променливи и изрази във вашия код. Например, вместо да напишете var x: Int = 0, можете да напишете var x = 0, като пропуснете напълно типа - компилаторът правилно прави извода, че x изписва стойност от тип Int.

Динамични типове

Променлива, която може да бъде от всякакъв тип, се декларира с динамичната ключова дума в Dart и ключовата дума Any в Swift.

Динамичните типове обикновено се използват при четене на данни като JSON.

Променливи / неизменни променливи

Променливите могат да бъдат обявени за променливи или неизменни.

За деклариране на променливи променливи и двата езика използват ключовата дума var.

var a = 10; // int (Dart)
a = 20; // Добре
var a = 10 // Int (бързо)
a = 20 // ок

За да декларира неизменни променливи, Dart използва окончателни, а Swift използва let.

крайна a = 10;
a = 20; // 'a': крайна променлива, може да бъде зададена само веднъж.
нека a = 10
a = 20 // Не мога да присвоя стойност: „a“ е константа „let“

Забележка: Документацията на Dart определя две ключови думи, final и const, които работят както следва:

Ако никога не възнамерявате да променяте променлива, използвайте final или const, или вместо var или като допълнение. Крайната променлива може да бъде зададена само веднъж; const променлива е константа на време на компилиране. (Const променливите са имплицитно окончателни.) Крайната променлива от най-високо ниво или клас се инициализира при първото й използване.

Допълнителни обяснения ще намерите в тази публикация на уебсайта на Дарт:

окончателно означава едно назначение. Крайната променлива или поле трябва да има инициализатор. След като бъде присвоена стойност, стойността на крайната променлива не може да бъде променена. окончателно променя променливите.

TL; DR: използвайте final за определяне на неизменни променливи в Dart.

В Swift декларираме константи с let. Цитирайки:

Постоянна декларация въвежда постоянна именувана стойност във вашата програма. Постоянните декларации се декларират с помощта на ключовата дума let и имат следната форма:
нека постоянно име: type = израз
Константната декларация определя неизменна връзка между константното име и стойността на израза на инициализатора; след като стойността на константа е зададена, тя не може да бъде променена.

Прочетете още: Бързи декларации.

Функции

Функциите са първокласни граждани в Суифт и Дарт.

Това означава, че точно като обекти, функциите могат да бъдат предадени като аргументи, запаметени като свойства или върнати в резултат.

Като първоначално сравнение можем да видим как да декларираме функции, без аргументи.

В Dart типът връщане предхожда името на метода:

void foo ();
int bar ();

В Swift използваме нотация -> T като наставка. Това не е задължително, ако няма възвратна стойност (Void):

func foo ()
func bar () -> Int

Прочетете още:

  • Dart функции
  • Функции Swift

Наименовани и неименувани параметри

И двата езика поддържат имена и неименувани параметри.

В Swift параметрите са посочени по подразбиране:

func foo (име: Струна, възраст: Int, височина: Двойна)
foo (име: "Andrea", възраст: 34, височина: 1,84)

В Dart дефинираме име на параметри с къдрави скоби ({}):

void foo ({String name, int age, двойна височина});
foo (име: 'Andrea', възраст: 34, височина: 1,84);

В Swift дефинираме неименувани параметри, като използваме подчертаване (_) като външен параметър:

func foo (_ име: String, _ age: Int, _ height: Double)
foo ("Андреа", 34, 1.84)

В Dart дефинираме un-name параметри, като пропускаме къдравите скоби ({}):

void foo (име на струна, int възраст, двойна височина);
foo ('Андреа', 34, 1.84);

Прочетете още: Етикети на аргументи на функции и имена на параметри в Swift.

Незадължителни и стандартни параметри

И двата езика поддържат параметри по подразбиране.

В Swift можете да определите стойност по подразбиране за всеки параметър във функция, като присвоите стойност на параметъра след типа на този параметър. Ако е дефинирана стойност по подразбиране, можете да пропуснете този параметър, когато извиквате функцията.
func foo (име: Струна, възраст: Int = 0, височина: Двойно = 0.0)
foo (име: "Andrea", възраст: 34) // име: "Andrea", възраст: 34, височина: 0.0

Прочетете още: Стойност на параметри по подразбиране в Swift.

В Dart опционалните параметри могат да бъдат позиционни или имена, но не и двете.

// позиционни незадължителни параметри
void foo (име на низ, [int age = 0, двойна височина = 0.0]);
foo ('Андреа', 34); // име: 'Andrea', възраст: 34, височина: 0.0
// посочени незадължителни параметри
void foo ({Име на низ, int age = 0, двойна височина = 0.0});
foo (име: 'Andrea', възраст: 34); // име: 'Andrea', възраст: 34, височина: 0.0

Прочетете още: Незадължителни параметри в Dart.

Приключване

Бидейки първокласни обекти, функциите могат да се предават като аргументи на други функции или да бъдат присвоени на променливи.

В този контекст функциите са известни още като затваряния.

Ето пример на Dart за функция, която повтаря списък от елементи, като използва затваряне, за да отпечатате индекса и съдържанието на всеки елемент:

окончателен списък = ['ябълки', 'банани', 'портокали'];
list.forEach ((item) => print ('$ {list.indexOf (item)}: $ item'));

Затварянето взема един аргумент (елемент), отпечатва индекса и стойността на този елемент и не връща никаква стойност.

Обърнете внимание на използването на стрелката (=>). Това може да се използва вместо едно изявление за връщане вътре в къдрави скоби:

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

Същият код в Swift изглежда така:

нека списък = ["ябълки", "банани", "портокали"]
list.forEach ({print ("\ (String (описва: list.firstIndex (от: $ 0))) \ ($ 0)")})

В този случай ние не посочваме име за аргумента, предаден на затварянето, и използваме вместо $ 0, за да означаваме първия аргумент. Това е изцяло по избор и можем да използваме посочен параметър, ако предпочитаме:

list.forEach ({item in print ("\ (String (описва: list.firstIndex (of: item))) \ (item)")})

Затварянията често се използват като блокове за завършване на асинхронен код в Swift (вижте раздел по-долу за асинхронното програмиране).

Прочетете още:

  • Dart Anonymous функции
  • Бързо затваряне

кортежи

От документите на Swift:

Tuples групират множество стойности в една единствена стойност на съединението. Стойностите в един кортеж могат да бъдат от всякакъв тип и не трябва да бъдат от един и същи тип.

Те могат да се използват като малки типове с леко тегло и са полезни при дефиниране на функции с множество стойности на връщане.

Ето как да използвате кортежи в Swift:

нека t = ("Андреа", 34, 1.84)
print (t.0) // отпечатва "Andrea"
печат (т.1) // отпечатъци 34
печат (t.2) // отпечатъци 1.84

Кортежите се поддържат с отделен пакет в Dart:

const t = const Tuple3  ('Andrea', 34, 1.84);
отпечатате (t.item1); // отпечатва „Andrea“
отпечатате (t.item2); // отпечатва 34
отпечатате (t.item3); // отпечатъци 1.84

Контролен поток

И двата езика предоставят разнообразни извлечения от контролния поток.

Примери за това са, ако условни, за и докато цикли, превключват операции.

Обхващането им тук би било доста продължително, затова се позовавам на официалните документи:

  • Бърз контрол на потока
  • Извлечения за контрол на дартса

Колекции (масиви, набори, карти)

Масиви / списъци

Масивите са подредени групи от обекти.

Масивите могат да бъдат създадени като списъци в Dart:

var emptyList =  []; // празен списък
вар списък = [1, 2, 3]; // списък буквален
list.length; // 3
списък [1]; // 2

Масивите имат вграден тип в Swift:

var emptyArray = [Int] () // празен масив
var array = [1, 2, 3] // масив от масив
array.count // 3
масив [1] // 2

Комплекти

Цитиране на документите на Swift:

Набор съхранява различни стойности от един и същ тип в колекция без определена подредба. Можете да използвате набор вместо масив, когато редът на елементите не е важен или когато трябва да гарантирате, че елемент се появява само веднъж.

Това е дефинирано с класа Set в Dart.

var emptyFruits =  {}; // празен набор буквал
вар плодове = {'ябълка', 'банан'}; // задайте буквално

По същия начин в Swift:

var emptyFruits = Задаване  ()
вар плодове = Set  ([„ябълка“, „банан“])

Карти / речници

Документите Swift имат добра дефиниция за карта / речник:

Речник съхранява асоциации между ключове от същия тип и стойности от същия тип в колекция без определена подреденост. Всяка стойност е свързана с уникален ключ, който действа като идентификатор за тази стойност в речника.

Картите са определени в Дарт така:

var namesOfIntegers = Map  (); // празна карта
вар летища = {'YYZ': 'Торонто Пиърсън', 'DUB': 'Дъблин'}; // карта буквално

Картите се наричат ​​речници в Swift:

var namesOfIntegers = [Int: String] () // празен речник
var aerodports = ["YYZ": "Торонто Пирсън", "DUB": "Дъблин"] // речник буквално

Прочетете още:

  • Дартски колекции
  • Бързи колекции (по-специално препоръчвам раздела за комплекти).

Променливост и по избор

В Dart всеки обект може да бъде нулев. И опитът за достъп до методи или променливи нулеви обекти води до изключение от нулев указател. Това е един от най-често срещаните източници на грешки (ако не и най-често срещаните) в компютърните програми.

От самото начало Swift имаше опции, вградена езикова функция за деклариране дали обектите могат или не могат да имат стойност. Цитиране на документите:

Използвате незадължителни в ситуации, в които може да липсва стойност. Незадължително представлява две възможности: Или има стойност, и можете да разгърнете опцията за достъп до тази стойност, или изобщо няма стойност.

За разлика от това, можем да използваме незадължителни променливи, за да гарантираме, че те винаги ще имат стойност:

var x: Int? // незадължително
var y: Int = 1 // незадължително, трябва да се инициализира

Забележка: казвайки, че променлива Swift е незадължителна, е приблизително същото като казаното, че променлива Dart може да бъде нулева.

Без поддръжка на езиково ниво за опции, можем да проверяваме по време на изпълнение само дали променлива е нула.

С опция ние вместо това кодираме тази информация по време на компилиране. Ние можем да отменим незадължителните опции, за да проверим дали притежават стойност:

func showOptions (x: Int?) {
  // използвайте „охрана нека“, а не „ако позволя“ като най-добра практика
  ако нека x = x {// разгънете по избор
    печат (х)
  } else {
    печат ("без стойност")
  }
}
showOptions (x: nil) // отпечатва „без стойност“
showOptions (x: 5) // отпечатва "5"

И ако знаем, че променлива трябва да има стойност, можем да използваме незадължителна:

func showNonO optional (x: Int) {
  печат (х)
}
showNonOtional (x: nil) // [грешка при компилиране] Nil не е съвместим с очаквания тип аргумент „Int“
showNonOtional (x: 5) // отпечатва "5"

Първият пример по-горе може да бъде приложен така в Dart:

void showOptions (int x) {
  ако (x! = null) {
    отпечатване (х);
  } else {
    печат („без стойност“);
  }
}
showOptions (null) // отпечатва "без стойност"
showOptions (5) // отпечатъци "5"

И вторият като този:

void showNonO optional (int x) {
  assert (x! = null);
  отпечатване (х);
}
showNonOtional (null) // [runtime error] Uncaught изключение: Утвърждаването не е успешно
showNonOtional (5) // отпечатъци "5"

Наличието на опция означава, че можем да хващаме грешки по време на компилиране, а не по време на изпълнение. И улавянето на грешки в началото води до по-безопасен код с по-малко грешки.

Липсата на поддръжка на Dart за опции по някакъв начин се смекчава чрез използването на твърдения (и @потребна анотация за посочените параметри).

Те се използват широко в Flutter SDK, но водят до допълнителен код на котлона.

За протокола има предложение за добавяне на ненулируеми типове към Dart.

Има много повече за опциите, отколкото съм разгледал тук. За добър преглед вижте: Незадължителни в Swift.

класове

Класовете са основният градивен елемент за писане на програми на обектно-ориентирани езици.

Класовете се поддържат от Dart и Swift, с някои разлики.

Синтаксис

Ето клас с инициализатор и три променливи в Swift:

клас човек {
  нека име: String
  нека възраст: Int
  нека височина: двойна
  init (име: Низ, възраст: Int, височина: Double) {
    self.name = име
    self.age = възраст
    self.height = височина
  }
}

И същото в Дарт:

клас човек {
  Лице ({this.name, this.age, this.height});
  окончателно име на низ;
  окончателна цялостна възраст;
  крайна двойна височина;
}

Обърнете внимание на използването на това. [PropertyName] в конструктора на Dart. Това е синтактична захар за задаване на променливите на член-инстанция, преди конструкторът да работи.

Фабрични конструктори

В Dart е възможно да се създадат фабрични конструктори. Цитирайки:

Използвайте фабричната ключова дума, когато внедрявате конструктор, който не винаги създава нов екземпляр от своя клас.

Един случай за практическо използване на фабричните конструктори е когато създавате моделен клас от JSON:

клас човек {
  Лице ({this.name, this.age, this.height});
  окончателно име на низ;
  окончателна цялостна възраст;
  крайна двойна височина;
  factory Person.fromJSON (Карта <динамичен, динамичен> json) {
    Име на низ = json ['име'];
    int age = json ['age'];
    двойна височина = json ['височина'];
    лице за връщане (име: име, възраст: възраст, височина: височина);
  }
}
var p = Person.fromJSON ({
  'име': 'Andrea',
  'възраст': 34,
  'височина': 1,84,
});

Прочетете още:

  • Дарт класове
  • Бързи структури и класове

наследяване

Swift използва модел с едно наследяване, което означава, че всеки клас може да има само един суперклас. Класовете Swift могат да реализират множество интерфейси (известни също като протоколи).

Дартсовите класове имат наследяване на базата на миксин. Цитиране на документите:

Всеки обект е екземпляр от клас и всички класове се спускат от Object. Наследяване, основано на Mixin, означава, че въпреки че всеки клас (с изключение на Object) има точно един суперклас, класният клас може да бъде използван повторно в множество йерархии на класа.

Ето едно едно наследяване в действие в Swift:

клас превозно средство {
  нека wheelCount: Int
  init (wheelCount: Int) {
    self.wheelCount = wheelCount
  }
}
клас велосипед: превозно средство {
  в него() {
    super.init (брой на колелата: 2)
  }
}

И в Дарт:

клас превозно средство {
  Превозното средство ({this.wheelCount});
  final int wheelCount;
}
клас Велосипед удължава превозното средство {
  Велосипед (): супер (брой колела: 2);
}

Имоти

Те се наричат ​​променливи инстанции в Dart и просто свойства в Swift.

В Swift има разлика между съхранени и изчислени свойства:

клас кръг {
  init (радиус: двойно) {
    self.radius = радиус
  }
  нека радиус: двойно // съхранено свойство
  вар диаметър: Двоен {// изчислено свойство само за четене
    връщане радиус * 2.0
  }
}

В Дарт имаме същото различие:

клас кръг {
  Кръг ({this.radius});
  краен двоен радиус; // запазена собственост
  двоен диаметър на получаване => радиус * 2,0; // изчислено свойство
}

В допълнение към getters за изчислени свойства, можем да определим и setters.

Използвайки горния пример, можем да пренапишем свойството диаметър, за да включим сетер:

вар диаметър: Double {// изчислено свойство
  получи {
    връщане радиус * 2.0
  }
  комплект {
    радиус = newValue / 2.0
  }
}

В Dart можем да добавим отделен сетер така:

зададен диаметър (двойна стойност) => радиус = стойност / 2.0;

Наблюдатели на собствеността

Това е особеност на Swift. Цитирайки:

Наблюдателите на собствеността наблюдават и реагират на промените в стойността на собствеността. Наблюдателите на собствеността се извикват всеки път, когато се зададе стойността на собствеността, дори ако новата стойност е същата като текущата стойност на собствеността.

Ето как могат да се използват:

вар диаметър: Двоен {// изчислено свойство само за четене
  willSet (newDiameter) {
    печат ("стара стойност: \ (диаметър), нова стойност: \ (нов диаметър)")
  }
  didSet {
    печат ("стара стойност: \ (oldValue), нова стойност: \ (диаметър)")
  }
}

Прочетете още:

  • Променливи, променливи и настройки за дартс
  • Swift Properties

Протоколи / Абстрактни часове

Тук говорим за конструкция, използвана за дефиниране на методи и свойства, без да се уточнява как се прилагат. Това е известно като интерфейс на други езици.

В Swift интерфейсите се наричат ​​протоколи.

протокол Shape {
  функционална зона () -> Двойна
}
клас Square: форма {
  нека страна: Двойна
  init (отстрани: Double) {
    self.side = страна
  }
  func area () -> Double {
    връщане страна * страна
  }
}

Dart има подобна конструкция, известна като абстрактна класа. Абстрактните часове не могат да бъдат инстанцирани. Те обаче могат да определят методи, които имат изпълнение.

Примерът по-горе може да бъде написан така в Dart:

абстрактен клас Shape {
  двойна площ ();
}
клас Square разширява форма {
  Квадрат ({this.side});
  последна двойна страна;
  двойна площ () => страна * страна;
}

Прочетете още:

  • Бързи протоколи
  • Дарт абстрактни часове

Mixins

В Dart, mixin е просто обикновен клас, който може да се използва повторно в йерархии от множество класове.

Ето как бихме могли да разширим класа Person, който дефинирахме преди това с MixE NameExtension:

абстрактен клас NameExtension {
  Име на строката;
  String get uppercaseName => name.toUpperCase ();
  Низът се сдобива с малки букви => name.toLowerCase ();
}
клас човек с имеExtension {
  Лице ({this.name, this.age, this.height});
  окончателно име на низ;
  окончателна цялостна възраст;
  крайна двойна височина;
}
var person = Лице (име: 'Andrea', възраст: 34, височина: 1,84);
отпечатате (person.uppercaseName); // „ANDREA“

Прочетете още: Dart Mixins

Разширения

Разширенията са характеристика на езика Swift. Цитиране на документите:

Разширенията добавят нова функционалност към съществуващ клас, структура, изброяване или тип протокол. Това включва възможността за разширяване на типове, за които нямате достъп до оригиналния изходен код (известен като ретроактивно моделиране).

Това не е възможно при миксините в Дарт.

Заемете примера по-горе, можем да разширим класа Person така:

Лице за удължаване {
  var uppercaseName: Низ {
    връщане name.uppercased ()
  }
  var строчен регистърName: Низ {
    връщане name.lowercased ()
  }
}
var person = Лице (име: "Andrea", възраст: 34, ръст: 1,84)
печат (person.uppercaseName) // "ANDREA"

Разширенията има много повече от представените тук, особено когато се използват в съчетание с протоколи и генерични устройства.

Един много често използван случай за разширения е добавянето на съответствие на протокола към съществуващите типове. Например, можем да използваме разширение за добавяне на възможности за сериализация към съществуващ модел модел.

Прочетете още: Swift Extensions

Enums

Dart има някои много основни поддръжка на ентусиасти.

Enums в Swift са много мощни, защото поддържат свързани видове:

enum NetworkResponse {
  успех на делото (орган: данни)
  случай неуспех (грешка: грешка)
}

Това дава възможност да се пише логика така:

превключвател (отговор) {
  случай .успех (нека данни):
    // направете нещо с (незадължителни) данни
  case .failure (нека грешка):
    // направете нещо с (незадължителна) грешка
}

Обърнете внимание как параметрите на данни и грешки взаимно се изключват.

В Dart не можем да свържем допълнителни стойности с enums и кодът по-горе може да бъде реализиран по тези редове:

клас NetworkResponse {
  NetworkResponse ({this.data, this.error})
  // твърдение за взаимно изключване на данни и грешки
  : assert (данни! = null && error == null || data == null && error! = null);
  окончателни данни на Uint8List;
  последна грешка на струните;
}
var response = NetworkResponse (данни: Uint8List (0), грешка: null);
ако (response.data! = null) {
  // използвайте данни
} else {
  // използва грешка
}

Няколко бележки:

  • Тук използваме твърденията, за да компенсираме факта, че нямаме опции.
  • Компилаторът не може да ни помогне да проверим за всички възможни случаи. Това е така, защото не използваме превключвател за обработка на отговора.

В обобщение, Swift enums са много по-мощни и изразителни, отколкото в Dart.

Библиотеките на трети страни като Dart Sealed Unions осигуряват подобна функционалност на тази, която се предлага от Swift enums и могат да помогнат за запълването на празнината.

Прочетете още: Swift Enums.

Structs

В Swift можем да определим структури и класове.

И двете конструкции имат много общи неща и някои разлики.

Основната разлика е, че:

Класовете са референтни типове, а структурите са стойностни типове

Цитиране на документацията:

Тип стойност е тип, чиято стойност се копира, когато е присвоена на променлива или константа или когато е предадена на функция.
Всички структури и изброявания са стойностни типове в Swift. Това означава, че всички създадени от вас структури и изброяване - и всички типове стойности, които имат като свойства - винаги се копират, когато се прехвърлят във вашия код.
За разлика от типовете стойности, референтните типове не се копират, когато са присвоени на променлива или константа или когато са предадени на функция. Вместо копие се използва препратка към същия съществуващ екземпляр.

За да видите какво означава това, помислете за следния пример, където пренасочваме класа Person, за да го направим изменяем:

клас човек {
  var име: String
  вар възраст: Int
  вар височина: Двойна
  init (име: Низ, възраст: Int, височина: Double) {
    self.name = име
    self.age = възраст
    self.height = височина
  }
}
var a = Лице (име: "Andrea", възраст: 34, височина: 1,84)
var b = a
b.age = 35
print (a.age) // отпечатъци 35

Ако преопределим Person за структура, ние имаме това:

Stru Person {
  var име: String
  вар възраст: Int
  вар височина: Двойна
  init (име: Низ, възраст: Int, височина: Double) {
    self.name = име
    self.age = възраст
    self.height = височина
  }
}
var a = Лице (име: "Andrea", възраст: 34, височина: 1,84)
var b = a
b.age = 35
print (a.age) // отпечатъци 34

Има много повече структури, отколкото аз съм покрил тук.

Структурите могат да се използват с голям ефект за обработка на данни и модели в Swift, което води до надежден код с по-малко грешки.

За по-добър преглед прочетете: Структури и класове в Swift.

Грешка при работа

Използване на дефиниция от документите на Swift:

Обработката на грешки е процесът на реагиране и възстановяване от условията за грешка във вашата програма.

И Dart и Swift използват try / catch като техника за справяне с грешки, с някои разлики.

В Dart всеки метод може да хвърли изключение от всякакъв тип.

клас BankAccount {
  BankAccount ({this.balance});
  двоен баланс;
  невалидно теглене (двойна сума) {
    ако (сума> баланс) {
      хвърлят изключение („Недостатъчни средства“);
    }
    баланс - = сума;
  }
}

Изключения могат да бъдат уловени с блок за изпробване / улов:

var сметка = BankAccount (баланс: 100);
опитвам {
  account.withdraw (50); // Добре
  account.withdraw (200); // хвърля
} улов (д) {
  отпечатате (д); // отпечатва „Изключение: Недостатъчни средства“
}

В Swift изрично заявяваме кога даден метод може да хвърли изключение. Това се прави с ключовата дума хвърляне и всички грешки трябва да съответстват на протокола за грешка:

enum AccountError: Грешка {
  случай недостатъчни фондове
}
клас BankAccount {
  вар баланс: Двоен
  init (баланс: Double) {
    self.balance = баланс
  }
  func изтегляне (сума: Double) хвърля {
    ако сума> баланс {
      хвърлете AccountError.insufficientFunds
    }
    баланс - = сума
  }
}

Когато обработваме грешки, използваме опитна ключова дума в блок do / catch.

var сметка = BankAccount (баланс: 100)
направете {
  опитайте account.withdraw (сума: 50) // ок
  пробвайте account.withdraw (сума: 200) // хвърля
} хващайте AccountError.insufficientFunds {
  печат ("Недостатъчни средства")
}

Обърнете внимание как ключовата дума опита е задължителна при извикване на методи, които могат да хвърлят.

И самите грешки са силно въведени, така че можем да имаме няколко блока за улавяне, за да покрием всички възможни случаи.

пробвам, опитам ?, опитай!

Swift предлага по-малко подробни начини за справяне с грешки.

Можем ли да използваме опита? без блок / улов. И това ще доведе до игнориране на всякакви изключения:

var сметка = BankAccount (баланс: 100)
опитвам? account.withdraw (сума: 50) // ок
опитвам? account.withdraw (сума: 200) // отпада безшумно

Или ако сме сигурни, че метод няма да хвърлим, можем да използваме опит !:

var сметка = BankAccount (баланс: 100)
опитвам! account.withdraw (сума: 50) // ок
опитвам! account.withdraw (сума: 200) // срив

Примерът по-горе ще доведе до срив на програмата. Следователно, опитайте! не се препоръчва в производствения код и е по-подходящ при писане на тестове.

Като цяло изричният характер на обработката на грешки в Swift е много полезен при проектирането на API, тъй като е лесно да се разбере дали метод може или не може да хвърли.

По същия начин използването на опита в методовите обаждания обръща внимание на кода, който може да бъде хвърлен, принуждавайки ни да разглеждаме случаи на грешки.

В това отношение обработката на грешки се чувства по-сигурна и по-здрава, отколкото в Dart.

Прочетете още:

  • Дартс изключения
  • Бързо боравене с грешки

Generics

Цитиране на документите на Swift:

Генеричният код ви позволява да пишете гъвкави функции за многократна употреба и видове, които могат да работят с всеки тип, при спазване на дефинираните от вас изисквания. Можете да напишете код, който избягва дублирането и изразява намерението си по ясен, абстрахиран начин.

Генеричните материали се поддържат от двата езика.

Един от най-често срещаните случаи на използване на генерични продукти са колекции, като масиви, набори и карти.

И можем да ги използваме, за да определим собствените си типове. Ето как бихме дефинирали общ тип стек в Swift:

struct Stack  {
  var items = [елемент] ()
  мутиращ функционален бутон (_ елемент: елемент) {
    items.append (т)
  }
  мутиращ func pop () -> Element {
    връщане на елементи.removeLast ()
  }
}

По същия начин в Dart бихме написали:

стек клас  {
  var items =  []
  невалиден тласък (елемент от елемента) {
    items.add (т)
  }
  void pop () -> Елемент {
    връщане на елементи.removeLast ()
  }
}

Generics са много полезни и мощни в Swift, където могат да се използват за определяне на ограничения на типа и асоциирани типове в протоколи.

Препоръчвам да прочетете документацията за повече информация:

  • Swift Generics
  • Dart Generics

Контрол на достъпа

Цитиране на документацията на Swift:

Контролът на достъп ограничава достъпа до части от вашия код от код в други изходни файлове и модули. Тази функция ви позволява да скриете подробностите за внедряването на вашия код и да посочите предпочитан интерфейс, чрез който този код може да бъде достъпен и използван.

Swift има пет нива на достъп: отворени, обществени, вътрешни, файлови и частни.

Те се използват в контекста на работа с модули и изходни файлове. Цитирайки:

Модулът е единична единица за разпределение на код - рамка или приложение, което е изградено и доставено като единица и които могат да бъдат импортирани от друг модул с ключовата дума за импортиране на Swift.

Отворените и публичните нива на достъп могат да се използват за да се направи код достъпен извън модулите.

Нивото на частен и частен файл може да се използва, за да направи кода недостъпен извън файла, в който е дефиниран.

Примери:

обществена класа SomePublicClass {}
вътрешен клас SomeInternalClass {}
fileprivate клас SomeFilePrivateClass {}
частен клас SomePrivateClass {}

Нивата на достъп са по-прости в Dart и се ограничават до публични и частни. Цитирайки:

За разлика от Java, Dart няма ключовите думи публични, защитени и частни. Ако идентификаторът започне с подчертаване _, той е частен за неговата библиотека.

Примери:

клас HomePage разширява StatefulWidget {// public
  @override
  _HomePageState createState () => _HomePageState ();
}
клас _HomePageState разширява състояние  {...} // частно

Контролът на достъпа е проектиран с различни цели за Dart и Swift. В резултат нивата на достъп са много различни.

Прочетете още:

  • Бърз контрол на достъпа
  • Дарт Библиотеки и видимост

Асинхронно програмиране: Фючърси

Асинхронното програмиране е област, в която Дарт наистина свети.

Необходима е някаква форма на асинхронно програмиране, когато се работи със случаи на използване като:

  • Изтегляне на съдържание от мрежата
  • Разговор с бекенд услуга
  • Извършете продължителни операции

В тези случаи е най-добре да не блокираме основната нишка на изпълнение, което може да накара програмите ни да се замразят.

Цитиране на документацията на Dart:

Асинхронните операции позволяват на програмата да завърши друга работа, докато чакате да приключи операцията. Dart използва Future обекти (фючърси), за да представи резултатите от асинхронните операции. За да работите с фючърси, можете да използвате или async и чакате или API на бъдещето.

Като пример, нека да видим как можем да използваме асинхронно програмиране за:

  • удостоверяване на потребител със сървър
  • съхранявайте маркера за достъп, за да защитите съхранението
  • получете информация за потребителския профил

В Dart това може да стане с асинхронизация / изчакване в комбинация с Futures:

Бъдещо  getUserProfile (идентификационни данни на UserCredentials) async {
  окончателен accessToken = изчакайте networkService.signIn (идентификационни данни);
  изчакайте secureStorage.storeToken (accessToken, forUserCredentials: идентификационни данни);
  върнете изчакайте networkService.getProfile (accessToken);
}

В Swift няма поддръжка за async / чакаме и можем да постигнем това само със затваряния (блокове за завършване):

func getUserProfile (идентификационни данни: UserCredentials, завършване: (_ резултат: UserProfile) -> void) {
  networkService.signIn (идентификационни данни) {accessToken in
    secureStorage.storeToken (accessToken) {
      networkService.getProfile (accessToken, завършване: завършване)
    }
  }
}

Това води до "пирамида на обреченост" поради вложени блокове за завършване. И обработката на грешки става много трудна при този сценарий.

В Dart обработката на грешки в кода по-горе се извършва просто чрез добавяне на пробно / хващащ блок около кода към метода getUserProfile.

За справка има предложение за добавяне на асинхрон / изчакване към Swift в бъдеще. Това е подробно документирано тук:

  • Предложение за Async / Очакай за Swift

Докато това не бъде приложено, разработчиците могат да използват библиотеки на трети страни, като тази библиотека с обещания от Google.

Що се отнася до Dart, отличната документация може да намерите тук:

  • Асинхронно програмиране на Дартс: Фючърси

Асинхронно програмиране: Потоци

Потоците се реализират като част от основните библиотеки на Dart, но не и в Swift.

Цитиране на документацията на Dart:

Потокът е последователност от асинхронни събития.

Потоците са в основата на реактивните приложения, където те играят важна роля с държавното управление.

Например потоците са чудесен избор за търсене на съдържание, при което се излъчва нов набор от резултати всеки път, когато потребителят актуализира текста в поле за търсене.

Потоците не са включени в основните библиотеки на Swift. Библиотеките на трети страни като RxSwift предлагат поддръжка на потоци и много други.

Потоците са широка тема, която не се обсъжда тук.

Прочетете още: Асинхронно програмиране на Dart: Потоци

Управление на паметта

Dart управлява паметта с усъвършенствана схема за събиране на боклука.

Swift управлява паметта чрез автоматично броене на референт (ARC).

Това гарантира страхотна производителност, тъй като паметта се освобождава веднага, когато вече не се използва.

Той обаче прехвърля тежестта частично от компилатора към разработчика.

В Swift трябва да мислим за жизнения цикъл и собствеността на обектите и да използваме правилно подходящите ключови думи (слаби, силни, неизвестни), за да избегнем задържане на цикли.

Прочетете още: Бързо автоматично броене на справки.

Съставяне и изпълнение

На първо място, важно разграничение между компилаторите точно навреме (JIT) и предсрочните (AOT):

JIT компилатори

JIT компилатор работи по време на изпълнение на програмата, компилиране в движение.

JIT компилаторите обикновено се използват с динамични езици, където типовете не са фиксирани предварително. JIT програмите се изпълняват чрез преводач или виртуална машина (VM).

AOT компилатори

AOT компилатор работи по време на създаването на програмата, преди изпълнение.

AOT компилаторите обикновено се използват със статични езици, които познават типовете данни. AOT програмите се компилират в родния машинен код, който се изпълнява директно от хардуера по време на изпълнение.

Цитирайки тази страхотна статия на Wm Leler:

Когато компилирането на AOT се извършва по време на разработката, тя неизменно води до много по-бавни цикли на развитие (времето между извършване на промяна в програма и възможност за изпълнение на програмата, за да се види резултатът от промяната). Но AOT компилирането води до програми, които могат да се изпълняват по-предсказуемо и без паузи за анализ и компилация по време на изпълнение. AOT компилираните програми също започват да се изпълняват по-бързо (защото те вече са компилирани).
Обратно, компилацията на JIT осигурява много по-бързи цикли на развитие, но може да доведе до по-бавно или по-банално изпълнение. По-специално, JIT компилаторите имат по-бавни времена на стартиране, защото когато програмата започне да работи, компилаторът JIT трябва да направи анализ и компилация, преди кодът да може да бъде изпълнен. Проучванията показват, че много хора ще изоставят приложение, ако са необходими повече от няколко секунди, за да започнат да изпълняват.

Като статичен език, Swift се компилира предварително.

Dart може да се компилира както AOT, така и JIT. Това осигурява значителни предимства, когато се използва с Flutter. Цитирам отново:

JIT компилация се използва по време на разработка, като се използва компилатор, който е особено бърз. След това, когато приложението е готово за пускане, то се съставя AOT. Следователно, с помощта на усъвършенствани инструменти и компилатори, Dart може да достави най-доброто от двата свята: изключително бързи цикли на развитие и бързо изпълнение и време за стартиране. - Wm Leler

С Dart получаваме най-доброто от двата свята.

Swift страда от основния недостатък на AOT компилация. Тоест, времето за компилация се увеличава с размера на кодовата база.

За средно голямо приложение (между 10K и 100K линии) лесно може да отнеме минути, за да състави приложение.

Не е така с приложенията Flutter, при които постоянно получаваме под секунда горещо презареждане, независимо от размера на кодовата база.

Други функции не са обхванати

Следните характеристики не са обхванати, тъй като са доста сходни в Dart и Swift:

  • Оператори (вижте справка за Swift и Dart)
  • Струни (вижте справка за Swift и Dart)
  • Незадължително веригиране в Swift (известно като условен достъп на член в Dart).

Concurrency

  • Едновременно програмиране това се осигурява с изолати в Dart.
  • Swift използва Grand Central Dispatch (GCD) и опашки за изпращане.

Любимите ми функции Swift липсват от Dart

  • Structs
  • Enums с свързани видове
  • ЕКСТРИ

Любимите ми функции на Dart липсват от Swift

  • Точно навреме компилатор
  • Фючърси с изчакване / async (вижте предложението за асинхронизация / изчакване от Крис Летнър)
  • Потоци с добив / асинхрон * (RxSwift предлага набор от потоци за реактивни приложения)

заключение

И Dart и Swift са отлични езици, добре пригодени за изграждане на модерни мобилни приложения и извън тях.

Нито един от езиците не е по-добър, тъй като и двамата имат свои уникални силни точки.

Когато гледам развитието на мобилните приложения и инструментариума за двата езика, усещам, че Dart има горната си ръка. Това се дължи на компилатора JIT, който е в основата на състоятелното горещо презареждане във Flutter.

И горещото презареждане осигурява огромна печалба от производителност при изграждане на приложения, тъй като ускорява цикъла на развитие от секунди или минути до по-малко от секунда.

Времето за разработчици е по-ограничен ресурс от времето за изчисляване.

Така че оптимизирането за времето на разработчиците е много интелигентен ход.

От друга страна, усещам, че Суифт има много силна тип система. Безопасността на типа е включена във всички езикови функции и води по-естествено до стабилни програми.

След като оставим настрана личните предпочитания, езиците за програмиране са само инструменти. И наша работа като разработчици е да изберем най-подходящия инструмент за работата.

Във всеки случай можем да се надяваме, че и двата езика ще взаимстват най-добрите идеи един от друг, докато те се развиват.

Позовавания и кредити

И Dart и Swift имат богат набор от функции и тук не са обхванати напълно.

Подготвих тази статия, заемайки информация от официалната документация на Swift and Dart, която можете да намерите тук:

  • Езикът за програмиране на Swift
  • Обиколка на езика на дартса

В допълнение, разделът за компилаторите на JIT и AOT е силно вдъхновен от тази страхотна статия на Wm Leler:

  • Защо Flutter използва Dart

Пропуснах ли нещо? Уведомете ме в коментарите.

Честито кодиране!

Актуализация: Моят курс Flutter & Firebase Udemy вече е достъпен за ранен достъп. Използвайте тази връзка за записване (включен код за отстъпка):

  • Flutter & Firebase: Създайте пълно приложение за iOS и Android

За още статии и видео уроци вижте Кодиране с трептене.

Аз съм @ biz84 в Twitter. Можете също да видите моята страница на GitHub.