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🤔1
Подписку на уведомления (
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
HTTP — это запрос-ответ, однонаправленный протокол. WebSocket — двунаправленный, позволяет клиенту и серверу обмениваться данными в реальном времени без повторных соединений. HTTP хорош для REST API, WebSocket — для чатов, игр, стриминга.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Мьютекс (от англ. "mutex" - mutual exclusion, взаимное исключение) — это механизм синхронизации, используемый в многопоточном программировании для предотвращения одновременного доступа нескольких потоков к общим ресурсам, таким как переменные, структуры данных или файлы. Он помогает избежать состояния гонки (race condition), когда результат выполнения программы зависит от неопределённого порядка доступа потоков к ресурсу.
Мьютекс обеспечивает доступ к общему ресурсу только одному потоку в каждый момент времени. Когда один поток захватывает мьютекс, другие потоки должны ждать, пока мьютекс не будет освобождён.
Поток захватывает мьютекс перед доступом к общему ресурсу и освобождает его после завершения работы с этим ресурсом. Если мьютекс уже захвачен другим потоком, текущий поток будет блокирован до тех пор, пока мьютекс не будет освобождён.
import Foundation
class SafeCounter {
private var value = 0
private let lock = NSLock()
func increment() {
lock.lock() // Захват мьютекса
value += 1
lock.unlock() // Освобождение мьютекса
}
func getValue() -> Int {
lock.lock() // Захват мьютекса
let currentValue = value
lock.unlock() // Освобождение мьютекса
return currentValue
}
}
let counter = SafeCounter()
DispatchQueue.global().async {
for _ in 0..<1000 {
counter.increment()
}
}
DispatchQueue.global().async {
for _ in 0..<1000 {
counter.increment()
}
}
// Подождём немного, чтобы дать потокам закончить работу
Thread.sleep(forTimeInterval: 1)
print("Final counter value: \(counter.getValue())")
Мьютексы защищают общие ресурсы от одновременного доступа, предотвращая повреждение данных.
Код становится более предсказуемым и стабильным, так как исключаются состояния гонки.
Если мьютексы захватываются в неправильном порядке, это может привести к ситуации, когда два или более потока блокируют друг друга, ожидая освобождения мьютексов.
Чрезмерное использование мьютексов может привести к снижению производительности из-за увеличения времени ожидания потоков.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Паттерны проектирования – это готовые решения частых задач, улучшающие структуру кода, его поддержку и масштабируемость.
Помогают упростить создание объектов и сделать его гибким.
Определяет интерфейс для создания объекта, но поручает подклассам выбрать его тип.
protocol Button {
func press()
}
class iOSButton: Button {
func press() { print("iOS button pressed") }
}
class AndroidButton: Button {
func press() { print("Android button pressed") }
}
class ButtonFactory {
static func createButton(for os: String) -> Button {
return os == "iOS" ? iOSButton() : AndroidButton()
}
}
let button = ButtonFactory.createButton(for: "iOS")
button.press() // "iOS button pressed"Гарантирует, что у класса будет только один экземпляр.
class Database {
static let shared = Database() // Единственный экземпляр
private init() { }
func query() { print("Запрос в базу данных") }
}
Database.shared.query()Позволяет пошагово создавать сложные объекты.
class Burger {
var cheese = false
var bacon = false
}
class BurgerBuilder {
private var burger = Burger()
func addCheese() -> Self {
burger.cheese = true
return self
}
func addBacon() -> Self {
burger.bacon = true
return self
}
func build() -> Burger {
return burger
}
}
let myBurger = BurgerBuilder().addCheese().addBacon().build()
print(myBurger.cheese) // true
print(myBurger.bacon) // trueОпределяют удобные способы связи между объектами.
Позволяет совместить несовместимые интерфейсы.
protocol EuropeanSocket {
func provide220V()
}
class EuropeanPlug: EuropeanSocket {
func provide220V() { print("220V подано") }
}
class USPlug {
func provide110V() { print("110V подано") }
}
// Адаптер для американской вилки
class USAdapter: EuropeanSocket {
private let usPlug: USPlug
init(usPlug: USPlug) { self.usPlug = usPlug }
func provide220V() {
usPlug.provide110V()
print("Адаптация до 220V")
}
}
let adapter = USAdapter(usPlug: USPlug())
adapter.provide220V()
// "110V подано"
// "Адаптация до 220V"Динамически добавляет объекту новое поведение.
protocol Coffee {
func cost() -> Int
}
class SimpleCoffee: Coffee {
func cost() -> Int { return 100 }
}
// Декоратор "Молоко"
class MilkDecorator: Coffee {
private let coffee: Coffee
init(_ coffee: Coffee) { self.coffee = coffee }
func cost() -> Int {
return coffee.cost() + 30
}
}
let coffee = MilkDecorator(SimpleCoffee())
print(coffee.cost()) // 130Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
- удобное описание моделей;
- автоматическую синхронизацию данных;
- механизмы сохранения в SQLite (или других форматах);
- поддержку undo, lazy loading, валидации и интеграции с UI.
Core Data особенно полезна в приложениях с богатой моделью данных, связями и сложной логикой.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Внедрение зависимостей (Dependency Injection, DI) в iOS-приложениях позволяет сделать код более модульным, тестируемым и поддерживаемым. Рассмотрим основные способы внедрения зависимостей в Swift.
Это самый распространенный и рекомендуемый способ. Зависимости передаются в объект через его инициализатор.
protocol NetworkServiceProtocol {
func fetchData()
}
class NetworkService: NetworkServiceProtocol {
func fetchData() {
print("Данные загружены")
}
}
// Класс, которому нужна зависимость
class ViewModel {
private let networkService: NetworkServiceProtocol
init(networkService: NetworkServiceProtocol) {
self.networkService = networkService
}
func loadData() {
networkService.fetchData()
}
}
// Использование
let networkService = NetworkService()
let viewModel = ViewModel(networkService: networkService)
viewModel.loadData()Зависимость передается через свойство класса.
class ViewModel {
var networkService: NetworkServiceProtocol?
func loadData() {
networkService?.fetchData()
}
}
// Использование
let viewModel = ViewModel()
viewModel.networkService = NetworkService()
viewModel.loadData()Зависимость передается непосредственно в метод, который её использует.
class ViewModel {
func loadData(with networkService: NetworkServiceProtocol) {
networkService.fetchData()
}
}
// Использование
let viewModel = ViewModel()
let networkService = NetworkService()
viewModel.loadData(with: networkService)Класс сам запрашивает зависимость у глобального локатора.
class ServiceLocator {
static let shared = ServiceLocator()
private var services: [String: Any] = [:]
func register<T>(_ service: T) {
let key = String(describing: T.self)
services[key] = service
}
func resolve<T>() -> T? {
let key = String(describing: T.self)
return services[key] as? T
}
}
// Регистрация зависимостей
let locator = ServiceLocator.shared
locator.register(NetworkService() as NetworkServiceProtocol)
// Использование
class ViewModel {
func loadData() {
let networkService: NetworkServiceProtocol? = ServiceLocator.shared.resolve()
networkService?.fetchData()
}
}Специальные библиотеки помогают управлять зависимостями.
import Swinject
let container = Container()
container.register(NetworkServiceProtocol.self) { _ in NetworkService() }
class ViewModel {
private let networkService: NetworkServiceProtocol
init(networkService: NetworkServiceProtocol) {
self.networkService = networkService
}
func loadData() {
networkService.fetchData()
}
}
// Разрешение зависимости через контейнер
let networkService = container.resolve(NetworkServiceProtocol.self)!
let viewModel = ViewModel(networkService: networkService)
viewModel.loadData()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Во время анимации значение frame напрямую не обновляется, но можно получить его через presentation layer: view.layer.presentation()?.frame. Это позволяет узнать промежуточное положение вью на текущем кадре анимации.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
RunLoop — это фундаментальный механизм в iOS и macOS, который управляет циклом обработки событий в приложении. Он отслеживает и обрабатывает входящие события, такие как нажатия клавиш, касания экрана, таймеры и сетевые запросы, и поддерживает приложение в активном состоянии, пока оно не завершится.RunLoop постоянно выполняет цикл, ожидая входящие события и обрабатывая их по мере поступления. Этот цикл состоит из нескольких этапов: ожидание события, обработка события и повтор цикла.RunLoop может работать в разных режимах, которые определяют, какие источники событий будут отслеживаться и обрабатываться. Основные режимы включают default и tracking (для событий отслеживания, таких как прокрутка). В каждой итерации RunLoop обрабатывает события только для текущего режима.RunLoop.current.run(mode: .default, before: Date.distantFuture)
RunLoop может отслеживать различные источники событий, такие как таймеры (Timer), порты (Port), ввод пользователей (такие как касания экрана и клики мыши), а также пользовательские источники (Input Source).RunLoop может управлять таймерами, которые выполняют задачи через определенные интервалы времени. let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
print("Timer fired!")
}
RunLoop.current.add(timer, forMode: .default)RunLoop используется для обработки событий в основном потоке (main thread) приложения. Это особенно важно для поддержания отзывчивости пользовательского интерфейса, поскольку все взаимодействия с UI происходят в основном потоке.import Foundation
class Example {
var timer: Timer?
func startRunLoop() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
RunLoop.current.run()
}
@objc func timerFired() {
print("Timer fired!")
}
}
let example = Example()
example.startRunLoop()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ссылочные типы хранят ссылку на область памяти, где находятся данные. При передаче копируется ссылка, а не сами данные. Примеры: классы.
Типы значения копируют свои данные при передаче. Каждая переменная имеет свою собственную копию. Примеры: структуры, перечисления.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Если вы хотите задать отступы (margins, padding) для
UIView в жизненном цикле UIViewController, то важно выбрать правильный момент, когда размеры view уже определены. class MyViewController: UIViewController {
let myView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
myView.backgroundColor = .red
view.addSubview(myView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Установка отступов (margins)
myView.frame = view.bounds.insetBy(dx: 20, dy: 50)
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM