В Swift словарь (`Dictionary<Key, Value>`) – это неупорядоченная коллекция пар `ключ → значение`, где ключи уникальны.
Swift позволяет сравнивать два словаря по их ключам и значениям, если и ключи, и значения соответствуют протоколу
Equatable. let dict1 = ["name": "Alice", "age": "25"]
let dict2 = ["name": "Alice", "age": "25"]
let dict3 = ["name": "Bob", "age": "30"]
print(dict1 == dict2) // ✅ true (значения одинаковые)
print(dict1 == dict3) // ❌ false (разные значения)
Ошибка при сравнении
Dictionary без EquatableЕсли значения не соответствуют `Equatable`, сравнение не сработает:
struct User {
let name: String
}
let dict1 = ["user": User(name: "Alice")]
let dict2 = ["user": User(name: "Alice")]
// Ошибка: Type 'User' does not conform to protocol 'Equatable'
// print(dict1 == dict2)Решение: Сделать
User Equatable:struct User: Equatable {
let name: String
}
print(dict1 == dict2) // Теперь работает!Можно сравнивать отдельные элементы по ключу.
let dict = ["name": "Alice", "age": "25"]
if dict["name"] == "Alice" {
print("Имя совпадает") // ✅
}
Можно сравнить только ключи или только значения.
let dict1 = ["name": "Alice", "age": "25"]
let dict2 = ["age": "30", "name": "Alice"]
print(dict1.keys == dict2.keys) // ✅ true (ключи одинаковые)
Сравнение значений
print(Set(dict1.values) == Set(dict2.values)) // ✅ true (если порядок неважен)
Если словарь хранит
Any, то прямое сравнение не сработает, и нужно сравнивать элементы вручную.let dict1: [String: Any] = ["name": "Alice", "age": 25]
let dict2: [String: Any] = ["name": "Alice", "age": 25]
// Функция сравнения словарей с `Any`
func areDictionariesEqual(_ dict1: [String: Any], _ dict2: [String: Any]) -> Bool {
return NSDictionary(dictionary: dict1).isEqual(to: dict2)
}
print(areDictionariesEqual(dict1, dict2)) // ✅ true
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Barriers (dispatch_barrier) в GCD основаны на последовательной синхронизации: они позволяют блокировать доступ к ресурсу и выполнять задачу эксклюзивно, прежде чем продолжится параллельное выполнение. Семафоры (DispatchSemaphore) — это счётчики, которые регулируют количество одновременно работающих потоков или доступ к ресурсу. Оба механизма основаны на примитивах синхронизации ядра.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В iOS существует несколько способов вёрстки пользовательского интерфейса. Каждый из них имеет свои плюсы и минусы в зависимости от проекта.
Используется: визуальный редактор в Xcode.
Storyboard
- Позволяет создавать весь UI в одном файле
.storyboard.- Поддерживает Auto Layout и Size Classes для адаптивного дизайна.
- Можно настраивать Segue (переходы между экранами).
Используется: Полностью программная вёрстка без использования Interface Builder.
let button = UIButton()
button.setTitle("Нажми", for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
Используется: Современный способ верстки с декларативным синтаксисом.
struct ContentView: View {
var body: some View {
VStack {
Text("Привет, SwiftUI!")
.font(.largeTitle)
Button("Нажми меня") {
print("Кнопка нажата")
}
}
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
- CALayer — базовый слой.
- CAShapeLayer — для отрисовки векторной графики.
- CATextLayer — для отображения текста.
- CAGradientLayer — для градиентов.
- CAReplicatorLayer — для повторяющихся узоров.
- CATransformLayer — для 3D-трансформаций.
- CAEAGLLayer — для работы с OpenGL.
Каждый тип предоставляет свои возможности визуализации и анимации.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Фреймы (frames) в iOS-разработке используются для задания размеров и расположения элементов интерфейса (UIView) вручную.
Когда нужно анимировать движение элемента, проще всего работать с его
frame, так как он напрямую управляет origin (координаты) и size (размеры). UIView.animate(withDuration: 0.5) {
self.button.frame.origin.x += 100
}Если вы не используете Auto Layout или хотите задать положение элементов программно,
frame позволяет точно указать размеры и координаты. let button = UIButton(type: .system)
button.frame = CGRect(x: 50, y: 100, width: 200, height: 50)
button.setTitle("Нажми меня", for: .normal)
view.addSubview(button)
При динамической подгрузке ячеек в
UITableView или UICollectionView можно вручную вычислять frame для ускорения работы, вместо использования Auto Layout, который может замедлить скроллинг.При рисовании или настройке слоев
CALayer используется frame, чтобы точно определить размеры слоя. let borderLayer = CALayer()
borderLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
borderLayer.borderWidth = 2
borderLayer.borderColor = UIColor.red.cgColor
view.layer.addSublayer(borderLayer)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
Это сообщения, которые отправляются приложением пользователю даже тогда, когда само приложение не активно. Это эффективный способ удержания пользователей и предоставления им важной информации.
Когда приложение устанавливается и запускается на устройстве, оно регистрируется для получения push-уведомлений. Для этого приложение отправляет запрос к Apple Push Notification Service (APNs) с запросом на получение уникального токена устройства (device token).
APNs генерирует уникальный токен для устройства и отправляет его обратно приложению. Приложение затем передает этот токен на свой сервер.
Когда необходимо отправить push-уведомление, сервер приложения формирует сообщение, включающее содержимое уведомления и токен устройства, и отправляет его на APNs.
APNs принимает сообщение от сервера, определяет устройство по токену и отправляет уведомление на это устройство.
Когда устройство получает уведомление, операционная система отображает его пользователю. Если пользователь взаимодействует с уведомлением, приложение может выполнить определенные действия, такие как открытие конкретного экрана.
В AppDelegate
import UIKit
import UserNotifications
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Запрос разрешения на отправку уведомлений
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Преобразуем токен в строку
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
// Отправляем токен на сервер
// serverAPI.registerDeviceToken(token)
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Использовать слабые (weak) или безвладельные (unowned) ссылки в замыканиях и свойствах между объектами, особенно когда один объект владеет другим, и наоборот. Также помогает осознание, кто должен владеть кем и соблюдение архитектурных границ.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
В Swift переменные (свойства), объявленные в протоколе, должны указывать:
Если свойство объявлено как
{ get }, класс или структура, реализующая протокол, должна предоставить как минимум геттерprotocol Animal {
var name: String { get } // Только чтение
}
struct Dog: Animal {
let name = "Барсик" // Реализуем только get
}
let dog = Dog()
print(dog.name) // "Барсик"Можно также использовать вычисляемое свойство:
struct Cat: Animal {
var name: String {
return "Мурзик"
}
}Если свойство
{ get set }, класс или структура обязательно должны предоставить и get, и set.protocol Vehicle {
var speed: Int { get set } // Чтение и запись
}
class Car: Vehicle {
var speed: Int = 100 // Реализуем и get, и set
}
let car = Car()
car.speed = 120 // Можно изменить значение
print(car.speed) // 120Вычисляемое свойство тоже подойдёт, если оно имеет
get и set:class Bike: Vehicle {
private var internalSpeed = 50
var speed: Int {
get { return internalSpeed }
set { internalSpeed = newValue }
}
}Если свойство должно быть общим для всех экземпляров (не индивидуальным), то оно объявляется
static.protocol Config {
static var appVersion: String { get }
}
struct AppSettings: Config {
static let appVersion = "1.0.0"
}
print(AppSettings.appVersion) // "1.0.0"Класс может использовать
class var, если свойство можно переопределять в подклассах:class AppInfo: Config {
class var appVersion: String {
return "2.0.0"
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
1. Счётчик ссылок увеличивается при создании ссылки и уменьшается при её удалении.
2. Когда счётчик достигает нуля, память освобождается.
3. ARC предотвращает утечки памяти, но требует избегать циклических ссылок с помощью weak и unowned.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Свойства "Content Hugging Priority" и "Content Compression Resistance Priority" играют ключевую роль в системе Auto Layout. Эти свойства помогают определить, как вьюшки (views) должны быть отформатированы и как они реагируют на изменения в доступном пространстве в интерфейсе пользователя. Рассмотрим подробнее, что означает каждое из этих свойств и как они используются в разработке интерфейсов.
Определяет, насколько сильно вьюшка должна "обнимать" своё содержимое. Это свойство указывает на желательность вьюшки быть как можно ближе к своим внутренним размерам, основанным на своем содержимом.
Определяет, насколько сильно вьюшка должна противостоять сжатию размеров меньше, чем размеры её содержимого.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Escaping closure — это замыкание, которое используется после выхода из функции, в которую его передали.
Например, если замыкание сохраняется в свойстве или передаётся асинхронно. Его нужно отметить
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
UserDefaults — это хранилище для сохранения простых данных (строки, числа, массивы). Но если нужно сохранить сложные объекты, их сначала кодируют в `Data` (Codable), а затем сохраняют. Для примитивных типов (строки, числа, массивы, словари)
UserDefaults работает без кодирования:let defaults = UserDefaults.standard
// Сохранение
defaults.set("Иван", forKey: "username")
defaults.set(25, forKey: "age")
// Чтение
let name = defaults.string(forKey: "username") ?? "Нет имени"
let age = defaults.integer(forKey: "age")
print(name, age) // Иван 25
Если нужно сохранить свой объект, сначала его нужно закодировать в
Data.struct User: Codable {
let name: String
let age: Int
}Сохранение в
UserDefaultslet user = User(name: "Иван", age: 25)
let defaults = UserDefaults.standard
if let encoded = try? JSONEncoder().encode(user) {
defaults.set(encoded, forKey: "user")
}
Загрузка из
UserDefaultsif let savedData = defaults.data(forKey: "user"),
let savedUser = try? JSONDecoder().decode(User.self, from: savedData) {
print(savedUser.name, savedUser.age) // Иван 25
}
Чтобы удалить ключ:
defaults.removeObject(forKey: "user")
Можно сохранить массив объектов, просто закодировав его:
let users = [
User(name: "Иван", age: 25),
User(name: "Анна", age: 30)
]
if let encoded = try? JSONEncoder().encode(users) {
defaults.set(encoded, forKey: "users")
}
// Читаем массив обратно
if let savedData = defaults.data(forKey: "users"),
let savedUsers = try? JSONDecoder().decode([User].self, from: savedData) {
print(savedUsers) // [{name: Иван, age: 25}, {name: Анна, age: 30}]
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В чистых колбеках механизма EventEmitter нет, но его можно интегрировать, например, через библиотеку Node.js. EventEmitter используется для обработки событий, позволяя подписываться на них и обрабатывать асинхронный код. Колбеки могут быть связаны с событиями через вызов функций обратного вызова. Это полезно для построения асинхронной архитектуры.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊4
Garbage Collector (GC) и Automatic Reference Counting (ARC) – это два разных подхода к управлению памятью в программировании. Они решают одну задачу: автоматическое освобождение неиспользуемой памяти, но делают это по-разному.
Java, Kotlin, C#, Python, JavaScript
- GC периодически просматривает всю память приложения и ищет объекты, на которые больше нет ссылок.
- Когда такие объекты находятся, они удаляются, а память освобождается.
- Это автоматический процесс, который запускается по мере необходимости.
Где используется: Swift, Objective-C
- Каждый объект имеет счетчик ссылок (
reference count). - Когда переменная создает ссылку на объект, счетчик увеличивается.
- Когда переменная перестает ссылаться на объект, счетчик уменьшается.
- Когда счетчик достигает нуля, объект удаляется из памяти сразу же.
class Person {
var pet: Pet?
}
class Pet {
var owner: Person?
}
let person = Person()
let pet = Pet()
person.pet = pet
pet.owner = person // Теперь оба объекта держат друг друга, и ARC их не удалитРешение – использовать
weak: class Pet {
weak var owner: Person? // Теперь утечки памяти не будет
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Вероятно, имелось в виду "invalidatable optimization" — это оптимизация, при которой фреймворк (например, Auto Layout или layout system) может отменить кэш или результаты предыдущих вычислений, если данные изменились. Также это может быть связано с invalidateLayout() в UICollectionViewLayout.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔2💊2
Подписку на уведомления (
NotificationCenter), KVO или Combine в жизненном цикле UIViewController лучше размещать в методах, где гарантируется её актуальность и корректное удаление. Подписка на события обычно происходит в `viewDidLoad()`, так как этот метод вызывается один раз при создании контроллера.
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification),
name: .someNotification,
object: nil
)
}
@objc func handleNotification(_ notification: Notification) {
print("Получено уведомление")
}Если подписка должна работать только когда экран на экране, используем
viewWillAppear().var cancellable: AnyCancellable?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
cancellable = NotificationCenter.default.publisher(for: .someNotification)
.sink { _ in
print("Событие получено")
}
}
Когда контроллер скрывается, подписки можно удалить, чтобы избежать ненужных вызовов.
Удаление подписки на
NotificationCenter:override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: .someNotification, object: nil)
}Удаление
Combine подписки (cancellable)override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
cancellable?.cancel()
cancellable = nil
}Если подписка работает на протяжении всего жизненного цикла контроллера, её можно удалить в `deinit`.
deinit {
NotificationCenter.default.removeObserver(self)
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
По хорошей архитектуре — в Interactor (Clean Swift) или Model/Service Layer. Она не должна находиться во ViewController, чтобы сохранить читаемость и переиспользуемость.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊3🔥2👍1
Когда пользователь нажимает на иконку приложения на домашнем экране, iOS проходит несколько этапов перед тем, как приложение становится активным.
iOS-устройства управляются системой SpringBoard – это оболочка, отвечающая за домашний экран, иконки, фоновые процессы.
Когда пользователь тапает на иконку приложения, SpringBoard отправляет событие
UIApplicationLaunchOptionsKey в систему. Если приложение не запущено:
- iOS создаёт новый процесс и выделяет память.
- Загружаются зависимости (библиотеки, фреймворки).
- Создаётся объект
UIApplication. Здесь приложение инициализируется и загружается основной UI.
Метод в
AppDelegate: func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
print("Приложение запущено")
return true
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1🤔1
Реактивное программирование упрощает обработку асинхронных потоков данных, таких как события пользовательского интерфейса, API-запросы или изменения состояния. Основные проблемы, которые оно решает:
1. Управление сложными зависимостями между событиями.
2. Обработка потоков данных без блокировки.
3. Упрощение цепочек вызовов через декларативный стиль.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Если оставить контекст в замыкании, не принимая во внимание возможные проблемы, это может привести к нескольким серьезным проблемам, особенно в многопоточном и асинхронном программировании.
Одной из самых распространенных проблем является утечка памяти из-за циклов удержания (retain cycles). Это происходит, когда два или более объекта удерживают ссылки друг на друга, препятствуя освобождению памяти. В этом примере
closure захватывает self, что создает цикл удержания: MyClass держит сильную ссылку на closure, а closure держит сильную ссылку на self.class MyClass {
var value: Int = 0
var closure: (() -> Void)?
func setupClosure() {
closure = {
self.value += 1
}
}
}
let instance = MyClass()
instance.setupClosure()Когда замыкания захватывают изменяемый контекст, это может привести к условиям гонки и непредсказуемому поведению, особенно при работе в многопоточном окружении. Если метод
increment вызывается из разных потоков, это может привести к условиям гонки и некорректному изменению значения count.class Counter {
var count = 0
func increment() {
DispatchQueue.global().async {
self.count += 1
}
}
}
let counter = Counter()
counter.increment()Если замыкания захватывают тяжелые ресурсы (например, файлы, сети), это может привести к задержкам в их освобождении, что может негативно сказаться на производительности приложения. Если
FileHandler освобождается, но замыкание все еще захватывает file, это может привести к задержке в освобождении файлового дескриптора.class FileHandler {
var file: File?
func processFile() {
DispatchQueue.global().async {
self.file?.read()
}
}
}Когда используется слабая ссылка (
weak), замыкание может обнаружить, что захваченный объект освобожден, что приводит к тому, что слабая ссылка становится nil. Это требует дополнительных проверок и обработки.class MyClass {
var value: Int = 0
var closure: (() -> Void)?
func setupClosure() {
closure = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.value += 1
}
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM