KA0715 — Kotlin: Учебное пособие для разработчиков Swift

  • Запись изменена:08.12.2022
  • Post category:Публикации
  • Reading time:26 минут чтения

Kotlin — это язык, разработанный JetBrains, который приобрел популярность и широкое распространение, когда Google объявил, что с этого момента все их библиотеки Android больше не будут писаться на Java. Он приобрел широкую популярность, и на момент написания, по оценкам, его используют более 60% разработчиков Android по всему миру .

Если вы откроете его официальный сайт , вы сразу увидите современный , лаконичный , безопасный , мощный , совместимый (с разработкой Java для Android) и структурированный параллелизм . Все эти ключевые слова — это функциональные возможности, которые разработчики ищут в любом языке программирования, и все они есть в Kotlin.

Что еще более важно, Kotlin предназначен не только для Android. Он также поддерживает веб-интерфейс, серверную часть и — на чем вы будете работать в этой книге — многоплатформенность.

Kotlin и Swift: сравнение обоих языков

Синтаксис обоих языков очень похож. Если вы разработчик Swift, вы можете легко программировать на Kotlin. В этом приложении показано, как начать.

Примеры, показанные в этом приложении, являются фрагментами кода из Learn . Окончательная версия проекта доступна в репозитории материалов .

Основы

В этом разделе вы изучите основы Kotlin — или, как знакомы разработчики Swift, его основы. :]

Одна хорошая вещь в Android Studio заключается в том, что в большинстве случаев, если вы пропустили импорт или используете неправильный тип для переменной, она автоматически предупредит вас и предложит исправление.

Примечание . Каждый раз, когда Android Studio подчеркивает ваш код или показывает всплывающую подсказку, вы можете автоматически принять его предложение, нажав Alt-Enter .

Декларация пакета

Расширение файла Kotlin — .kt . Древовидная иерархия многоплатформенного проекта обычно соответствует соглашению об именах пакетов Android — у вас есть три уровня папок. В Learn это com/raywenderlich/learn , и они обычно соответствуют:

  • ком , домен
  • Райвендерлих , название компании
  • узнать , название приложения

Поскольку имя пакета уникально — в Google Play Store не может быть двух приложений с одним и тем же — это соглашение гарантирует отсутствие конфликтов между приложениями от разных компаний.

Каждый раз, когда вы объявляете новый класс или объект, вы должны определить packageобъявление. Это должна быть первая инструкция нового файла. Или, если у вас есть заголовок об авторских правах, сразу после него.

Если вы откроете FeedPresenter.kt внутри папки презентации из общего модуля, вы увидите, что импорт:

package com.raywenderlich.learn.presentation

В данном случае presentationэто подпапка, в которой находится этот класс. Определение пакета должно соответствовать той же древовидной иерархии, иначе вы можете импортировать неправильные файлы.

В Swift нет объявления пакета для добавления.

Импорт

Как правило, когда импорт отсутствует, Android Studio показывает вам подсказку с одним или несколькими предложениями, поэтому у вас не должно возникнуть никаких проблем. В любом случае, если вы хотите добавить его вручную, вам нужно добавить его после packageобъявления:

import com.raywenderlich.learn.data.model.GravatarEntry

Это отличается от Swift. Нет необходимости добавлять классы — вам просто нужно импортировать фреймворк, который вы собираетесь использовать.

Комментарии

Как и в Swift, вы можете добавлять три типа комментариев:

  • Строка , куда нужно просто добавить //перед кодом или текстом, который вы хотите прокомментировать. В этом примере Loggerне будет выполняться:

public fun fetchMyGravatar(cb: FeedData) { //Logger.d(TAG, "fetchMyGravatar") //Update the current listener with the new one. listener = cb fetchMyGravatar() }

  • Block , где вам нужно окружить свой код или текст /* */. Это также используется для добавления раздела об авторских правах в начале файла:

`/*

  • Copyright (c) 2021 Razeware LLC
  • */`
  • KDoc , который соответствует документации, которая будет создана для вашего проекта. Вы можете использовать такие теги, как@property,@param,@return,@constructorи т. д., чтобы предоставить дополнительную информацию о функции:

`/**

  • This method fetches your Gravatar profile.
  • @property cb, the callback used to notify the UI that the
  • profile was successfully fetched or not. */ public fun fetchMyGravatar(cb: FeedData) { //Your code goes here }`

Примечание . Эквивалентной версией Kdoc для Swift является Jazzy .

Переменные

Как и в Swift, в Kotlin у вас также есть два типа переменных:

  • val , что соответствует let Swift . Это переменная только для чтения (неизменяемая), которую можно установить только один раз — в ее объявлении. В этом случае scopeпри объявлении инициализируется определенным значением. Это значение никогда не может измениться во время выполнения приложения:

private val scope = PresenterCoroutineScope(defaultDispatcher)

  • var — то же ключевое слово, что и в Swift. Это изменяемая переменная, поэтому вы можете устанавливать ее столько раз, сколько вам нужно. В этом примере начальное значение listenerравно null. Когда пользовательский интерфейс делает новый запрос данных, он будет обновлен новой ссылкой обратного вызова:

private var listener: FeedData? = null

В Swift приведенное выше объявление:

private var listener: FeedData? = nil

Вы можете иметь необязательные значения на обоих языках. Единственная разница в том, что Kotlin использует null, тогда как Swift использует nilдля представления отсутствия значения.

Ленивая инициализация

Kotlin поддерживает ленивую инициализацию с помощью lazyключевого слова. Эта переменная должна быть неизменной — другими словами, вам нужно объявить ее как val.

Значение этой переменной будет вычислено только при первом доступе к ней. Вы должны определить переменную как ленивую только в том случае, если вам не нужно сразу обращаться к ней, и переменная выполняет некоторую тяжелую работу.

Откройте файл FeedPresenter.kt внутри общего доступа/презентации и найдите contentобъявление:

val content: List<RWContent> by lazy { json.decodeFromString(RW_CONTENT) }

Как видите, он определяется как lazy. Сделайте это, чтобы избежать декодирования RW_CONTENTсразу после запуска приложения. На одну операцию меньше.

Если ваше приложение запускается тяжело, следующий подход обеспечит более быструю и плавную инициализацию приложения. Значение будет установлено только при вызове content.

Поздняя инициализация

Вы можете отложить инициализацию переменной до тех пор, пока она не понадобится вашему приложению. Для этого вам нужно установить его как lateinit, и его нельзя установить как неизменяемый или нулевой.

Измените FeedPresenter.ktlistener на :

private lateinit var listener: FeedData

Удаление ?и nullопределяет этот объект как ненулевой. Вы сразу увидите пару предупреждений в этом файле:

onSuccess = { listener?.onNewDataAvailable(it, platform, null) }, onFailure = { listener?.onNewDataAvailable(emptyList(), platform, it) }

В частности, вы увидите предупреждения ?в приведенных выше лямбда-выражениях. Поскольку listenerне равно нулю, вы можете вызвать обратный вызов напрямую, так как значение не будет равным нулю. Это не должно быть проблемой, так как все функции, которые можно вызывать из пользовательского интерфейса, любят fetchAllFeedsи fetchMyGravatarполучают ненулевое значение FeedData, которое обновляет listenerперед любым сетевым вызовом.

Вы должны быть осторожны при использовании lateinit; если вы попытаетесь получить доступ к его значению без его инициализации, ваше приложение выйдет из строя с исключением:

UninitializedPropertyAccessException: свойство lateinit не было инициализировано

Кроме того, вы можете проверить, инициализирован ли он:

if (::listener.isInitialized) { //Do something }

Но это считается вонючим кодом и не рекомендуется.

В Swift нет lateinitключевого слова для инициализации. Вместо этого вам нужно использовать оператор !:

private var listener: FeedData!

Под капотом listenerопределяется как необязательный . Будьте осторожны — прежде чем получить доступ к его значению, вам нужно его определить. В противном случае ваше приложение выйдет из строя.

Эквивалент, чтобы увидеть, инициализировано ли оно:

if listener != nil { //Do something }

Обнуляемость

Возможно, самая известная черта Kotlin — его обнуляемость. В идеале исключений NullPointerException больше нет — другими словами, исключений, вызванных вызовами объектов, которых не существует. Здесь необходимо слово «в идеале», поскольку последнее слово остается за разработчиками, и они всегда могут пойти против того, что советует язык.

Чтобы определить, может ли переменная быть нулевой, вам нужно использовать ?оператор.

Вы можете видеть, что listenerон имеет тип FeedData, но его значение может быть null. Теперь попробуйте выполнить любую операцию над этим объектом. На fetchAllFeeds, перед Loggerвызовом добавить:

listener.onMyGravatarData(GravatarEntry())

Вы отправляете пустое значение, GravatarEntryпоскольку этот параметр не может быть нулевым. Глядя на эту инструкцию, вы можете увидеть красную подчеркивание под . с сообщением:

Только безопасные (?.) или ненулевые утвержденные (!!.) вызовы разрешены для приемника, допускающего значение NULL, типа FeedData?

Так как эта переменная может быть нулевой, вы не должны выполнять никаких действий, пока не проверите ее значение. Здесь есть две разные возможности:

  1. Явно скажите, что он не будет нулевым. Вы можете использовать этот символ !!, чтобы сообщить компилятору, что это значение никогда не будет равно null, поэтому вы можете сделать любой вызов, который вам нужен:

listener!!.onMyGravatarData(GravatarEntry())

Идти против языковых правил никогда не бывает хорошей идеей, поэтому постарайтесь убежать от этой реализации.

Эквивалентом этой аннотации в Swift является использование одного файла !.

  1. Вызывайте метод только в том случае, если значение не равно null. В этом случае listeneris mutable, поэтому вы не можете просто добавить ifусловие, чтобы увидеть, не является ли оно нулевым (поскольку оно может быть изменено другим потоком). Решение состоит в том, чтобы ?снова использовать оператор. В этом сценарии он будет вызываться только в том случае, onMyGravatarDataесли listenerне равен нулю:

listener?.onMyGravatarData(GravatarEntry())

Ну, тут может быть и третья возможность. Во-первых, не делайте listenerего обнуляемым. :]

Кроме того, вы также можете использовать *?.let { ... }в качестве проверки только запуск кода между скобками, если переменная, к которой вы обращаетесь, не равна нулю:

listener?.let { it.onMyGravatarData(GravatarEntry()) }

itсоответствует listener.

Точно так же в Swift вы можете сделать это:

if let listener = listener { listener.onMyGravatarData(GravatarEntry()) }

Интерполяция строк

С помощью интерполяции строк вы можете легко объединять строки и переменные вместе. fetchAllFeedsВы будете повторять contentи вызывать с fetchFeedплатформой и URL-адресом фида. Перед этим блоком кода добавьте:

Logger.d(TAG, "Fetching feed: ${feed.platform}")

Чтобы напечатать результат feed.platform, вам нужно добавить скобки к инструкции, которую вы хотите выполнить.

Что произойдет, если вы не добавите эти скобки, а вместо этого у вас будет:

Logger.d(TAG, "Fetching feed: $feed.platform")

Скомпилируйте свое приложение и переключитесь в представление Logcat, чтобы убедиться, что этот журнал покажет вам feedобъект, за которым следует «.platform».

Вывод типа

Если вы объявляете переменную и присваиваете ей конкретное значение, вам не нужно определять ее тип. Kotlin способен сделать вывод об этом в большинстве случаев. Если вы посмотрите на переменные, объявленные в FeedPresenter.kt , вы увидите, что они jsonиспользуют определение типа, но contentне делают этого.

Попробуйте удалить тип из contentобъявления. Android Studio сразу подчеркивает это выражение, и если вы проверите ошибку, она говорит:

Недостаточно информации для вывода переменной типа T

Это потому , что decodeFromStringон не знает, какой тип объекта он должен возвращать. Когда вы определяете тип на уровне переменной, decodeFromStringиспользует его, чтобы знать, какие объекты он должен возвращать. Вы можете определить этот тип непосредственно в функции, если хотите использовать вывод типа в объявлении переменной:

val content by lazy { json.decodeFromString<List<RWContent>>(RW_CONTENT) }

Проверки типов

Оба языка используют isдля проверки, относится ли объект к определенному типу.

Бросать

Приведение переменной одинаково в обоих языках. Вам просто нужно использовать ключевое слово as, за которым следует тип класса, который вы хотите привести.

Преобразование между различными типами

Вы можете легко преобразовать примитивные типы, вызвав нужный .to*()тип:

// Convert String to Integer "raywenderlich".toInt() // Convert String to Long "raywenderlich".toLong() // Convert String to Float "raywenderlich".toFloat() // Convert Int to String 42.toString() // Convert Int to Long 42.toLong() // Convert Int to Float 42.toFloat()

Если вы имеете дело с пользовательскими объектами, вы всегда можете создать для них функцию расширения.

Функции расширения

Как следует из названия, функции расширения позволяют создавать дополнительные варианты поведения для существующих классов. Представьте, что вы хотите добавить метод, который должен быть доступен для всех объектов String , и при вызове он должен возвращать «Ray Wenderlich»:

fun String.toRW(): String { return "Ray Wenderlich" }

Это оно. Вы используете тип, который хотите расширить, за которым следует имя метода. Теперь эта функция доступна для всех объектов String .

Вы можете попробовать это, добавив предыдущую функцию и новый журнал в переменную String на FeedPresenter.kt — например, в RW_CONTENT:

init { Logger.d(TAG, "content=${RW_CONTENT.toRW()}") }

Вы можете подтвердить в Logcat, что вывод этого вызова будет похож на:

FeedPresenter | content=Ray Wenderlich

Сравнение объектов

Вы можете сравнивать объекты по ссылке с помощью === или по содержимому == .

Поток управления

Хотя синтаксис обоих языков очень похож, вы обнаружите, что Kotlin дает вам мощные выражения, которые вы можете использовать.

ifelse

Эта проверка условия одинакова для обоих языков. Если вы откроете GetFeedData.kt , вы увидите различные функции, которые используют if … else .

Только invokeFetchRWEntryдобавляет parsedобъект, если он не равен нулю:

if (parsed != null) { feed += parsed }

Более того, вам не нужно добавлять скобки, когда это одна инструкция.

В качестве альтернативы вы можете просто написать:

if (parsed != null) feed += parsed

Или даже встроенный:

if (parsed != null) feed += parsed

switch

Его нет в Котлине. Кроме того, вы можете использовать whenаналогичный.

when

whenявляется выражением условия, которое поддерживает несколько различных выражений. Вы можете увидеть пример того, как его использовать, в файле ImagePreview.kt , который находится внутри папки компонентов приложения androidApp :

when (painter.state) { is ImagePainter.State.Loading -> { AddImagePreviewEmpty(modifier) } is ImagePainter.State.Error -> { AddImagePreviewError(modifier) } else -> { // Do nothing } }

В этом случае вы проверяете текущее stateизображение, которое загружается из Интернета, и добавляете различные компонуемые в зависимости от того, является ли его значение либо , Loadingлибо Error.

Поскольку все эти выражения однострочные, скобки можно опустить.

for

Вернитесь к файлу FeedPresenter.kt из общего модуля. Вы можете найти forпетлю на fetchAllFeeds:

for (feed in content) { fetchFeed(feed.platform, feed.url) }

Здесь вы перебираете все значения content. Начиная с первого элемента в списке, на каждой итерации вы будете получать разные элементы, к которым можно получить доступ через feed.

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

for (index in content.indices) { val feed = content[index] fetchFeed(feed.platform, feed.url) }

Это использует indexдля просмотра всех элементов. Или вы можете получить indexи feedнапрямую через:

for ((index, feed) in content.withIndex()) { fetchFeed(feed.platform, feed.url) }

Или вы даже можете получить feedот:

for (index in 0..content.size) { val feed = content[index] fetchFeed(feed.platform, feed.url) }

Это все возможности, которые перебирают список всех элементов из contentдля получения одного и того же результата.

while

Циклы whileи do… whileпохожи на Swift. Вам просто нужно добавить условие, которое должно завершить цикл, и код, который должен выполняться, пока оно не выполняется.

while (condition) { //Do something } do { //Something } while (condition)

Разница между ними такая же, как и в Swift: если условие ложно while, блок кода никогда не запустится, а do… whileзапустится один раз.

В Swift это похоже на цикл whileand :repeat-while

while condition { //Do something } repeat { //Something } while condition

Тернарный оператор

Его нет в Котлине. Это то, что обсуждается уже пару лет, и результат всегда был одним и тем же: вы можете получить одно и то же решение, используя встроенное условие if … else .

Коллекции

Kotlin поддерживает различные типы коллекций: массивы, списки и карты. По умолчанию они неизменяемы, но вы можете использовать их изменяемый аналог, используя: mutableList и mutableMap .

Хотя в Swift вы можете изменить изменчивость списка или словаря, если вы объявите его с помощью let (неизменяемый) или var (изменяемый), то же самое недействительно для Kotlin. Как упоминалось выше, у вас есть список и карта для неизменяемых переменных, а также mutableList и mutableMap для изменяемых.

Списки

Вы можете легко создать список в Kotlin из исходного набора, вызвав listOfи добавив элементы в качестве параметров. Вы можете увидеть пример, где это делается в файле MainScreen.kt внутри папки androidApp/main :

val bottomNavigationItems = listOf( BottomNavigationScreens.Home, BottomNavigationScreens.Bookmark, BottomNavigationScreens.Latest, BottomNavigationScreens.Search )

В данном случае это список элементов на панели навигации.

Представьте, что вы хотите добавить новый элемент в этот список. Вы не можете. Метода добавления или удаления нет, так как listобъект неизменяем. Что вы можете сделать, так это создать изменяемый список:

val bottomNavigationItems = mutableListOf( BottomNavigationScreens.Home, BottomNavigationScreens.Bookmark, BottomNavigationScreens.Latest, BottomNavigationScreens.Search )

Или вы можете преобразовать существующий listв mutableList:

val bottomNavigationItems = listOf( BottomNavigationScreens.Home, BottomNavigationScreens.Bookmark, BottomNavigationScreens.Latest, BottomNavigationScreens.Search ).toMutableList()

Теперь вы можете добавлять или удалять элементы в списке. Попробуйте удалить Searchопцию:

bottomNavigationItems.remove(BottomNavigationScreens.Search)

Или вы можете просто использовать минус и знак равенства:

bottomNavigationItems -= BottomNavigationScreens.Search

Массивы

Массивы изменяемы, но имеют фиксированный размер. Создав его, вы не сможете добавлять или удалять элементы. Вместо этого вы меняете его содержимое. Используя предыдущий пример, вы можете создать arrayOfс начальным количеством элементов:

val bottomNavigationItems = arrayOf( BottomNavigationScreens.Home, BottomNavigationScreens.Bookmark, BottomNavigationScreens.Latest, BottomNavigationScreens.Search )

И затем, если вы хотите изменить значение одного из его индексов:

bottomNavigationItems[0] = BottomNavigationScreens.Bookmark bottomNavigationItems[1] = BottomNavigationScreens.Home

Карты (Словари Swift)

Подобно тому, что вы прочитали в приведенных выше примерах, вы можете создать карту, используя mapOfфункцию. Он получает Pairобъекты, которые вы можете добавить или удалить.

Измените предыдущий пример, чтобы создать карту, содержащую индекс в качестве ключа и экран в качестве значения:

val bottomNavigationItems = mapOf( 0 to BottomNavigationScreens.Home, 1 to BottomNavigationScreens.Bookmark, 2 to BottomNavigationScreens.Latest, 3 to BottomNavigationScreens.Search )

Вы можете получить любое значение на карте, используя его ключ:

// Returns BottomNavigationScreens.HOME bottomNavigationItems[0] // Returns BottomNavigationScreens.HOME bottomNavigationItems.get(0)

Или вы можете создать изменяемую карту:

val bottomNavigationItems = mutableMapOf( 0 to BottomNavigationScreens.Home, 1 to BottomNavigationScreens.Bookmark, 2 to BottomNavigationScreens.Latest, 3 to BottomNavigationScreens.Search )

Или путем преобразования toMutableMap:

val bottomNavigationItems = mapOf( 0 to BottomNavigationScreens.Home, 1 to BottomNavigationScreens.Bookmark, 2 to BottomNavigationScreens.Latest, 3 to BottomNavigationScreens.Search ).toMutableMap()

Карты эквивалентны словарям Swift.

Дополнительные функции

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

  • *.isEmpty()возвращает true, если коллекция пуста, falseиначе. Наоборот, у вас также есть *. isNotEmpty(), что возвращает противоположные значения.
  • *.filter { ... }позволяет фильтровать вашу коллекцию по определенному предикату.
  • *.first { ... }возвращает первый объект, который удовлетворяет условию в квадратных скобках. Кроме того, *.firstOrNull { }это возвращает, nullесли нет объекта, соответствующего предикату.
  • *.forEach { ... }перебирает коллекцию.
  • *.last { ... }похож на first, но на этот раз возвращается последний найденный объект.
  • *.sortBy { ... }возвращает новый упорядоченный список в соответствии с определенным предикатом. Вы также можете получить список в порядке убывания, вызвав: *.sortByDescending { ... }.

Классы и объекты

Вы можете использовать разные подходы к определению классов и объектов в Kotlin в зависимости от вашего варианта использования.

Классы

Вы можете создать класс, используя ключевое слово class, за которым следует его имя и любые параметры, которые он может получить. Если вы откроете файл FeedPresenter.kt , вы увидите:

class FeedPresenter(private val feed: GetFeedData)

Как правило, каждое слово класса имеет заглавную букву. В данном случае feedустановлен val, поэтому к нему можно получить доступ из любой функции в FeedPresenterобласти видимости.

Классы данных

Вы можете создать класс данных, используя ключевое слово dataперед объявлением класса. Как следует из названия, они были созданы с целью хранения данных и предоставления вам возможности создать краткий объект данных. Вам не нужно переопределять хэш -код или функции равенства — этот тип класса уже обрабатывает все внутри.

Вы можете увидеть пример класса данных, если откроете RWContent.kt из папки data/model в общем модуле:

data class RWContent( val platform: PLATFORM, val url: String, val image: String )

Однако у них есть несколько отличий по сравнению с универсальным классом: вы не можете наследовать класс данных или определить его как абстрактный.

Запечатанные классы

Если вы определяете класс или интерфейс как sealed, вы не можете расширить его за пределы пакета. Это особенно полезно для контроля того, что может и не может быть унаследовано. Откройте файл BottomNavigationScreens.kt внутри ui/main в androidApp :

sealed class BottomNavigationScreens( val route: String, @StringRes val stringResId: Int, @DrawableRes val drawResId: Int )

Если вы попытаетесь расширить этот класс в любом другом классе проекта, вы увидите ошибку, подобную следующей:

Наследник закрытого класса или интерфейса, объявленного в пакете com.raywenderlich.learn.ui.home, но он должен находиться в пакете com.raywenderlich.learn.ui.main, где объявлен базовый класс.

Чтобы создать объект этого sealedкласса:

object Home : BottomNavigationScreens("Home", R.string.navigation_home, R.drawable.ic_home) object Search : BottomNavigationScreens("Search", R.string.navigation_search, R.drawable.ic_search)

В данном случае они представляют вкладки навигации.

Хотя в Swift не существует запечатанныхenum классов, вы можете создать аналогичную концепцию с помощью :

enum BottomNavigationScreens { struct Content { let route: String let stringResId: Int let drawResId: Int } }

Там нет @StringResили @DrawableRes, так как эти аннотации специфичны для Android.

Кроме того, для создания соответствующих объектов вы можете сделать что-то вроде:

enum BottomNavigationScreens { ... case home(route: String, stringResId: Int, drawResId: Int) case search(route: String, stringResId: Int, drawResId: Int) }

Аргументы по умолчанию

Kotlin позволяет вам определять аргументы по умолчанию для свойств класса или аргументов функции. Например, вы можете определить значение по умолчанию для platformвсегда PLATFORM.ALL. При этом вам не обязательно определять platformзначение при создании RWContentобъекта. В этих сценариях система будет использовать значение по умолчанию.

data class RWContent( val platform: PLATFORM = PLATFORM.ALL, val url: String, val image: String = "" )

И чтобы создать этот объект:

val content = RWContent( url = "https://www.raywenderlich.com" )

Теперь, если вы распечатаете его содержимое:

// > ALL Logger.d(TAG, "platform=${content.platform}") // > https://www.raywenderlich.com Logger.d(TAG, "url=${content.url}") // > "" Logger.d(TAG, "image=${content.image}")

Одиночки

Чтобы создать синглтон в Kotlin, нужно использовать ключевое слово object. Файл ServiceLocator.kt — поскольку он связан с инициализацией объекта — является одним из таких примеров:

public object ServiceLocator

Это гарантирует, что у вас всегда будет только одна ссылка на ServiceLocatorвсю область вашего приложения.

Интерфейсы

Интерфейсы аналогичны протоколам Swift. Они определяют набор функций, которые должен объявить любой класс или переменная, которые их используют.

Откройте FeedData.kt из домена/cb в общем модуле:

public interface FeedData { public fun onNewDataAvailable(items: List<RWEntry>, platform: PLATFORM, e: Exception?) public fun onNewImageUrlAvailable(id: String, url: String, platform: PLATFORM, e: Exception?) public fun onMyGravatarData(item: GravatarEntry) }

FeedDataопределяет три разные функции, которые будут вызываться при наличии ответа сети. Они объявлены в FeedViewModel.kt (внутри androidMain/home ) и используются для уведомления пользовательского интерфейса о наличии новых доступных данных.

Функции

Kotlin поддерживает различные типы функций:

(нелинейные) функции

Эти функции чаще всего встречаются в любой исходной базе. Они очень похожи на Swift func, но в Kotlin это ключевое слово теряет букву, потому что это fun. :]

Вы можете увидеть различные примеры функций в FeedPresenter.kt :

public fun fetchAllFeeds(cb: FeedData) { listener = cb for (feed in content) { fetchFeed(feed.platform, feed.url) } }

Функция также может возвращать объект. Если вы откроете FeedAPI.kt и посмотрите на fetchRWEntry, вы увидите, что он возвращает HttpResponseобъект. Более того, поскольку инструкция всего одна, скобки добавлять не нужно, а возврат можно записать в той же строке. Вам просто нужно добавить =знак:

public suspend fun fetchRWEntry(feedUrl: String): HttpResponse = client.get(feedUrl)

Лямбда-выражения

Лямбда-выражения позволяют выполнять определенные блоки кода как функции. Они могут получать параметры и даже возвращать объект определенного типа. Вы можете увидеть два из них fetchFeedonSuccessи onFailureпараметры на FeedPresenter.kt .

private fun fetchFeed(platform: PLATFORM, feedUrl: String) { GlobalScope.apply { MainScope().launch { feed.invokeFetchRWEntry( platform = platform, feedUrl = feedUrl, onSuccess = { listener?.onNewDataAvailable(it, platform, null) }, onFailure = { listener?.onNewDataAvailable(emptyList(), platform, it) } ) } } }

Оба этих выражения получают itпараметр. В первом случае это список RWEntry, а во втором — это . В качестве Exception.альтернативы вы можете определить это выражение следующим образом, чтобы лучше определить, что itесть на самом деле:

onSuccess = { list -> listener?.onNewDataAvailable(list, platform, null) }

Функции высшего порядка

Функции высшего порядка поддерживают получение функции в качестве аргумента.

Хорошим примером функции такого типа являются аргументы onSuccessи в . Если вы проанализируете эти инструкции, вы увидите это и получите разные предметы. Откройте FeedData.kt и найдите :onFailure``fetchFeed``onSuccess``onFailure``it``onDataAvailable

public fun onNewDataAvailable(items: List<RWEntry>, platform: PLATFORM, e: Exception?)

itin — это onSuccessсписок RWEntry, а . Перейдите в GetFeedData.kt и найдите функцию:onFailure``Exception``invokeFetchRWEntry

public suspend fun invokeFetchRWEntry( platform: PLATFORM, feedUrl: String, onSuccess: (List<RWEntry>) -> Unit, onFailure: (Exception) -> Unit )

Вы видите, что оба параметра получают функцию, но в одном тип — это список, RWEntryа в другом — исключение.

Встроенные функции

Если ваше приложение вызывает высокоуровневую функцию несколько раз, это может привести к снижению производительности. Вкратце, каждую функцию необходимо преобразовать в объект с определенной областью действия. Каждый раз, когда они вызываются, создаются дополнительные затраты на создание ссылки на этот объект. Если вы определите эти функции как inline, содержимое функции высокого уровня будет скопировано путем добавления этого ключевого слова перед объявлением, и нет необходимости разрешать исходную ссылку.

Приостановить функции

Чтобы использовать suspendфункцию, вам нужно добавить библиотеку Coroutines в свой проект. Вы можете запускать, останавливать, возобновлять и приостанавливать приостановленную функцию. Вот почему они идеально подходят для асинхронных операций и почему их используют сетевые запросы приложений:

public suspend fun fetchRWEntry(feedUrl: String): HttpResponse = client.get(feedUrl)

Это похоже на async… awaitфункции Swift.

Таблица синтаксиса Kotlin и Swift

Вы можете найти сравнительную таблицу между обоими языками в репозитории материалов .

Куда пойти отсюда?

Вы хотите написать код на Kotlin без IDE, просто чтобы проверить его мощность? У JetBrains есть площадка Kotlin Playground , на которой можно протестировать некоторые базовые функции.

Если вы хотите узнать больше об обоих языках, у вас есть книги Kotlin и Swift , которые подробно расскажут вам все, что вам нужно знать об обоих языках.