Библиотека мобильного разработчика | Android, iOS, Swift, Retrofit, Moshi, Chuck
9.59K subscribers
1.65K photos
81 videos
52 files
4.47K links
Все самое полезное для мобильного разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4adec1b17b35b6c0d8389
Download Telegram
Предложите небольшую доработку для следующего кода

if age >= 18 {
driveCar()
} else {
doNotDrive()
}


Этот код хорошо работает - но можете ли вы предложить небольшое улучшение рефакторинга, чтобы сделать его еще лучше?

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

age >= 18 ? driveCar() : doNotDrive()


🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱62😢2🤔1
Что такое полиморфизм

Полиморфизм представлять возможность взаимозаменяемости типов, которые находятся в одной иерархии классов. Например, возьмем следующую иерархию классов:

class Person{
var name: String
var age: Int

init(name: String, age: Int){
self.name = name
self.age = age
}
func display(){
print("Имя: \(name) Возраст: \(age)")
}
}

class Employee : Person{
var company: String
init(name: String, age: Int, company: String) {
self.company = company
super.init(name:name, age: age)
}
override func display(){
print("Имя: \(name) Возраст: \(age) Сотрудник компании: \(company)")
}
}

class Manager : Employee{
override func display(){
print("Имя: \(name) Возраст: \(age) Менеджер компании: \(company)")
}
}


В данном случае класс Manager (менеджер компании) наследуется от класса Employee (сотрудник компании), а класс Employee - от класса Person (человек). Тем самым класс Manager ненапрямую тоже наследуется от Person.

Поскольку и сотрудник компании и менеджер компании в то же время являются людьми, то есть объектами класса Person, то мы можем написать следующим образом:

let tom: Person = Person(name:"Tom", age: 23)
let bob: Person = Employee(name: "Bob", age: 28, company: "Apple")
let alice: Person = Manager(name: "Alice", age: 31, company: "Microsoft")


Все три константы представляют тип Person, однако первая хранит ссылку на объект Person, вторая - на объект Employee, а третья - на объект Manager. Таким образом, переменная или константа одного типа может принимать многообразные формы в зависимости от конкретного объекта, на который она указывает.

Но что будет, если мы вызовем метод display() для всех трех объектов:

let tom: Person = Person(name:"Tom", age: 23)
let bob: Person = Employee(name: "Bob", age: 28, company: "Apple")
let alice: Person = Manager(name: "Alice", age: 31, company: "Microsoft")

tom.display() // Имя: Tom Возраст: 23
bob.display() // Имя: Bob Возраст: 28 Сотрудник компании: Apple
alice.display() // Имя: Alice Возраст: 31 Менеджер компании: Microsoft


Несмотря на то, что все три константы представляют тип Person, при вызове метода display будет вызываться реализация метода именно того класса, ссылку на объект которого хранит константа. Данный примем называется динамической диспетчеризацией - во время выполнения программы на основании типа объекта система решает, какую именно реализацию метода вызывать.

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

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
5🥱1
Тень под карточкой в Jetpack Compose

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

💡Реализация

@Composable
fun ShadowCard(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit
) {
Box(
modifier = modifier
.drawBehind {
drawRoundRect(
color = Color(0x1A000000), // мягкая полупрозрачная тень
cornerRadius = CornerRadius(16.dp.toPx()),
topLeft = Offset(0f, 6.dp.toPx())
)
}
.background(Color.White, RoundedCornerShape(16.dp))
.padding(16.dp)
) {
content()
}
}


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

📌 Применение

ShadowCard(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
) {
Text("PixelPerfect ", modifier = Modifier.align(Alignment.Center))
}


Такой подход легко адаптировать под любые формы и цвета. Главное — держать параметры тени (смещение, прозрачность, радиус) в синхронизации с макетами из Figma.

🐸 Библиотека мобильного разработчика

#PixelPerfect #JuniorKit #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
Как функции расширения работают под капотом

По своей задумке, функция расширения Kotlin — это дополнительный метод для любого объекта, даже для потенциально несуществующего (нуллабельного). Этот инструмент является прямой реализацией переопределения методов паттерна проектирования Декоратор.

ВАЖНО: В функциях расширения мы можем обращаться к любым общедоступным свойствам и методам объекта, однако не можем обращаться к свойствам и методам с модификаторами private и protected.

ВАЖНО: Функции расширения не переопределяют функции, которые уже определены в классе. Если функция расширения имеет ту же сигнатуру, что и уже имеющаяся функция класса, то компилятор просто будет игнорировать подобную функцию расширения.

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

fun main() {

val hello: String = "hello world"
println(hello.wordCount('l')) // 3
println(hello.wordCount('o')) // 2
println(4.square()) // 16
println(6.square()) // 36
}

fun String.wordCount(c: Char) : Int {
var count = 0
for(n in this) {
if(n == c) count++
}
return count
}
fun Int.square(): Int {
return this * this
}


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

fun Int.square(): Int {
return this * this
}


Через this обращаемся к тому объекту, для которого будет вызываться функция. И затем вы можем вызвать ее следующим образом:

4.square()      // 16


Для типа String определена функция wordCount, которая подсчитывает, сколько встречается определенный символ в строке.

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
Вложенные типы в Swift

Типы могут быть вложенными (nested). Класс или структура может содержать определение другого класса или структуры. Например:

class User{
var name: String
var age: Int
var profile: UserProfile

struct UserProfile{
var login: String
var password: String

func authenticate(_ login: String, _ password: String) -> Bool{
return self.login == login && self.password == password
}
}

init(name: String, age: Int, login: String, password: String){
self.name = name
self.age = age
self.profile = UserProfile(login: login, password: password)
}
}

var tom = User(name: "Tom", age: 23, login: "querty", password: "12345")
print(tom.profile.authenticate("sdf", "456")) // false
print(tom.profile.authenticate("querty", "12345")) // true


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

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

var profile = User.UserProfile(login: "ssdf", password: "345")
var isLoged = profile.authenticate("ssdf", "345") // true


🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
5️⃣ вещей о Optional в Swift, которые всех сбивают с толку

Optional (необязательный) означает, что переменная или данные могут быть равны нулю, и вы обрабатываете эти нулевые значения с помощью некоторых ключевых слов Swift.

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

Давайте рассмотрим сложные аспекты optional в Swift, которые всех сбивают с толку.

1️⃣ ? против !

Есть два способа сделать переменную optional с помощью ? и !, но каждый из них имеет разное значение.

?

Когда вы делаете любую переменную необязательной (optional) с помощью ?, это означает, что вам также придётся обрабатывать значение nil.

var name: String?

if let name = name {
print("Hello, \(name)")
} else {
print("No name found.")
}


Здесь мы использовали if-let для обработки имени. Если имя равно nil, выполняется блок else; в противном случае выполняется блок if.

!

Когда вы создаёте переменную с помощью !, это означает, что вам не нужно обрабатывать значение nil.

var userId: String!

print(userId.count)


Код выполняется успешно, но если значение равно нулю, приложение аварийно завершает работу.

Примечание. Используйте !, если вы уверены, что заданное значение не будет равно нулю, даже если оно объявлено как optional.

2️⃣ Цепочка optional переменных не останавливает выполнение

С помощью цепочки необязательных переменных (?) вы можете безопасно выполнить код с необязательной переменной.

let size = user?.name.count


Этот код работает нормально, даже если имя равно нулю. Всё выражение возвращает nil, но не приводит к крешу.

Необязательная цепочка (Optional Chaining) не остановит выполнение программы, если значение равно нулю. Взгляните на этот пример:

user?.logout() 
print("Done!")


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

3️⃣ if let — не всегда лучший выбор

if-let полезен для управления нулевыми значениями, но иногда он может сделать код более сложным и запутанным.

if let name = data?.name {
if let city = data?.address?.city {
print("\(name) lives in \(city)")
}
}


Это затрудняет чтение вложенного кода.

Лучший способ:

guard let name = data?.name,
let city = data?.address?.city else {
return
}

print("\(name) lives in \(city)")


Еще лучший способ:

let city = data?.address?.city ?? "Unknown City"


4️⃣ Двойной optional (??)

Вы уже знакомы с таким кодом:

var name: String? = "Jayant"


Переменная name имеет строковое значение или может быть равна нулю.

Но видели ли вы что-то подобное?

let value: String?? = "Jayant"


Это называется Double Optional (??). Читается как Optional(optional("Jayant")).

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

func getName() -> String? {
return nil
}

let name: String?? = getName()


Здесь функция getName() возвращает String?, а при назначении имени вы пишете String??, что означает optional, содержащую другую optional строку.

5️⃣ Optional коллекции — это не то же самое, что пустые

Взгляните на этот код:

var names: [String]? = []


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

Необязательный массив может находиться в различных состояниях.

var names: [String]? = nil   // it's not an array
var names: [String]? = [] // Array exists, but it’s empty
var names: [String]? = ["Jayant", "Neha"] // Array with values


Давайте посмотрим, что произойдет, если вы это проверите:

var names: [String]? = []

if names != nil {
print("Array exists")
}

// output ---> Array exists


Как видите, массив существует и пуст.

🔹 Курс «Основы IT для непрограммистов»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
Что такое абстрактные классы

Абстрактный класс — это класс, представляющий из себя "заготовку" для целого семейства классов, который описывает для них общий шаблон поведения. Экземпляр такого класса не может быть создан. Абстрактному классу не нужен модификатор open, потому что он "открыт" для наследования по умолчанию.

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

abstract class Tree {
abstract val name: String
abstract val description: String
abstract fun info()
}


Каждый наследник обязан переопределять их все.

class Pine : Tree() {
override val name = "Сосна"
override val description = "Хвойное дерево с длинными иглами и округлыми шишками"
override fun info() = "$name - ${description.toLowerCase()}."
}


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

abstract class Tree {
abstract val name: String
abstract val description: String
fun info(): String = "$name - ${description.toLowerCase()}."
}

...

class Pine : Tree() {
override val name = "Сосна"
override val description = "Хвойное дерево с длинными иглами и округлыми шишками"
}

...

val pine = Pine()
println(pine.info())


Так как этот компонент класса уже не будет абстрактным, наследники не смогут его переопределить.

class Pine : Tree() {
override val name = "Сосна"
override val description = "Хвойное дерево с длинными иглами и округлыми шишками"

// ошибка: функция "info" является "final" и не может быть переопределена
override fun info() = description
}


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

abstract class Tree {
abstract val name: String
abstract val description: String

open fun info(): String = "$name - ${description.toLowerCase()}."
}


У абстрактного класса может быть конструктор.

abstract class Tree(val name: String, val description: String) {
open fun info(): String = "$name - ${description.toLowerCase()}."
}


Тогда каждый наследник должен предоставить для него значения.

class Pine(name: String, description: String) : Tree(name, description)

...

val pine = Pine("Сосна", "Хвойное дерево с длинными иглами и округлыми шишками")
println(pine.info())


🔹 Курс «Основы IT для непрограммистов»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib

🐸 Библиотека мобильного разработчика

#буст #JuniorKit #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM