По мере того как набирает обороты Kotlin Multiplatform Mobile (KMM), растет спрос на специалистов, умеющих писать код как для Android, так и для iOS. В этом руководстве мы рассмотрим сходства и различия между разработкой для этих платформ. Сначала дадим общий обзор по каждой теме, а затем перейдем к частностям.  

Проектирование с учетом специфики системы

Приложения для Android разрабатываются с использованием подхода, основанного на разбиении приложения на Фрагменты и Activity. Activity представляет собой один экран в приложении, а проект с несколькими экранами требует управления множеством Activity. Каждый вид Activity содержит Фрагменты, представляющие собой части пользовательского интерфейса, используемые для перехода между Activity, ввода значений или открытия новых экранов.

Архитектура iOS-приложений основана на контроллерах представления. При разработке приложений используются различные типы контроллеров представления, такие как контроллеры представления страницы, вкладок и раздельного представления. Контроллер представления может управлять всем экраном или его частью.

OOPS

Kotlin и Swift — современные статически типизированные языки программирования, поддерживающие парадигму OOP (объектно-ориентированного программирования), включая классы, объекты, наследование, полиморфизм, инкапсуляцию и абстракцию.

Kotlin:

// Классы и объекты 
class Person(val name: String)
val person = Person("John")

// Наследование
open class Animal
class Dog : Animal()

// Полиморфизм
open class Animal {
open fun makeSound() {
println("Animal sound")
}
}
class Dog : Animal() {
override fun makeSound() {
println("Bark")
}
}

Swift:

// Классы и объекты
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let person = Person(name: "John")

// Наследование
class Animal {}
class Dog: Animal {}

// Полиморфизм
class Animal {
func makeSound() {
print("Animal sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Bark")
}
}

Модификаторы доступа

Примечания:

  • В Swift нет прямого эквивалента модификатора protected (защищенный), который есть в Kotlin.
  • В Kotlin нет прямого эквивалента уровня file-private (ограниченный файлом), который есть в Swift, но уровень internal (внутренний) используется в однофайловых модулях для аналогичного поведения.

Безопасность обработки переменных, допускающих пустое значение

// KOTLIN
var name: String? = null // тип nullable

//SWIFT
var name: String? = nil // опциональный тип
  • В Kotlin для предотвращения исключений, связанных с нулевым указателем, встроена система безопасности, требующая явного указания допустимости значения null с помощью ?.
  • Swift использует опциональные инструменты для обработки допустимости нулевого значения, применяя ? и ! для обозначения опциональных и неявно извлекаемых опциональных типов.

Расширения:

//KOTLIN
fun String.addExclamation() = this + "!"

//SWIFT
extension String {
func addExclamation() -> String {
return self + "!"
}
}

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

Представления:

// SWIFTUI
struct ContentView: View {
@State private var count = 0

var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}

// Jetpack Compose
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }

Column {
Text("Count: $count")
Button(onClick = { count += 1 }) {
Text("Increment")
}
}
}

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

Управление состоянием

Управление состоянием — важнейший аспект создания отзывчивых и интерактивных пользовательских интерфейсов. И Jetpack Compose (Android), и SwiftUI (iOS) предоставляют механизмы для управления состоянием в рамках своих фреймворков. Ниже мы сравним и противопоставим подходы к управлению состоянием на обеих платформах.

В Android ViewModel сохраняет состояние при изменении конфигурации, например при повороте экрана. Однако на платформе iOS и во фреймворке SwiftUI нет прямого эквивалента ViewModel в Android для сохранения состояния при изменении конфигурации. Сохранение состояния можно обеспечить с помощью @StateObject, @ObservedObject, @EnvironmentObject.

Навигация

Навигация в iOS: паттерн Coordinator широко используется для навигации в многомодульных iOS-проектах, обеспечивая чистое разделение задач путем делегирования обязанностей по навигации координаторам.

// Координатор функции A (Feature A)
class FeatureACoordinator: ObservableObject, Coordinator {
@Published var navigationPath = NavigationPath()
func start() -> some View {
NavigationStack(path: $navigationPath) {
FeatureAView()
.environmentObject(self)
}
}

func navigateToFeatureB() {
navigationPath.append(FeatureBView())
}
}

Настройка навигации в представлениях: можно использовать представления SwiftUI и привязывать их к координаторам.

struct MainView: View {
@EnvironmentObject var coordinator: MainCoordinator

var body: some View {
VStack {
Text("Main View")
Button("Go to Feature A") {
coordinator.navigateToFeatureA()
}
}
.navigationTitle("Main")
}
}

Мы также можем использовать Flow Stacks с паттерном Coordinator в iOS для многомодульного проекта; это продвинутая архитектура навигации, которая улучшает организацию и разделение задач.

Навигация в Android: в Android навигация обычно осуществляется с помощью компонента Navigation Component, обеспечивающего фреймворк для обработки всех навигационных взаимодействий в приложении, включая операции с Фрагментами, глубокие ссылки и многое другое.

// Настройка навигации в nav_graph.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/mainFragment">

<fragment
android:id="@+id/mainFragment"
android:name="com.example.MainFragment"
android:label="Main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_featureAFragment"
app:destination="@id/featureAFragment" />
</fragment>

<fragment
android:id="@+id/featureAFragment"
android:name="com.example.FeatureAFragment"
android:label="Feature A"
tools:layout="@layout/fragment_feature_a" />
</navigation>

// MainFragment.kt
class MainFragment : Fragment(R.layout.fragment_main) {
private val navController by lazy { findNavController() }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.buttonNavigateToFeatureA).setOnClickListener {
navController.navigate(R.id.action_mainFragment_to_featureAFragment)
}
}
}

// FeatureAFragment.kt
class FeatureAFragment : Fragment(R.layout.fragment_feature_a)

Типы запуска в Android:

  • Standard: новый экземпляр добавляется в стек перехода назад.
  • SingleTop: повторное использование экземпляра наверху стека, если он существует.
  • SingleTask: выводит существующий экземпляр наверх, очищая остальные.
  • SingleInstance: Activity выполняется только в рамках своей задачи.

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

Многомодульные проекты

Android использует Gradle для управления многомодульными проектами.

// Структура проекта
MyApplication/
├── app/
├── network/
├── featureA/
├── featureB/
└── settings.gradle

// settings.gradle
include ':app', ':network', ':featureA', ':featureB'

// app/build.gradle
dependencies {
implementation project(':network')
implementation project(':featureA')
}

В iOS применяется Xcode для управления проектами, а также есть возможность использования Swift Package Manager (SPM) для работы с зависимостями.

//Структура проекта
MyApplication/
├── MyApp/
├── NetworkFramework/
├── FeatureAFramework/
└── Package.swift

// Package.swift
import PackageDescription

let package = Package(
name: "MyApp",
dependencies: [
.package(path: "./NetworkFramework"),
.package(path: "./FeatureAFramework")
],
targets: [
.target(
name: "MyApp",
dependencies: ["NetworkFramework", "FeatureAFramework"]),
]
)

Помимо SPM, популярными менеджерами зависимостей для iOS являются также CocoaPods и Carthage.

// Подфайл
target 'MyApp' do
pod 'Alamofire', '~> 5.4'
end

Типы тестирования

Android и iOS предлагают различные методы тестирования для обеспечения надежности и производительности приложений. Ниже приведена таблица сравнения основных типов тестирования, используемых для каждой платформы:

Это был краткий обзор начального этапа разработки на iOS.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Rohit Kumar: iOS Roadmap for Android Devs: Basics

Предыдущая статьяМетоды wait(), notify() и notifyAll() в Java