По мере того как набирает обороты 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.
Читайте также:
- Миграция UI-ориентированной библиотеки Android на Compose Multiplatform (Android/iOS)
- Подходы к созданию линейных графиков для iOS-приложений на базе фреймворка SwiftUI
- React Native: полное руководство по созданию виджета для домашнего экрана для iOS и Android
Читайте нас в Telegram, VK и Дзен
Перевод статьи Rohit Kumar: iOS Roadmap for Android Devs: Basics