Мьютекс (от англ. "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
CocoaPods — это менеджер зависимостей для проектов iOS/macOS. Он позволяет подключать сторонние библиотеки в проект, управлять их версиями, устанавливать и обновлять автоматически.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Apple предпочитает value types (структуры
struct) по умолчанию в Swift по нескольким причинамstruct копируется при передаче, а не передается по ссылке, как class. Это снижает вероятность гонки данных (data race), когда один поток изменяет объект, а другой читает его одновременно.
В многопоточной среде это делает код более безопасным.
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 1, y: 2)
var p2 = p1 // p2 - это копия, изменения в p2 не затронут p1
p2.x = 10
print(p1.x) // 1
print(p2.x) // 10struct хранятся в стеке, а не в куче, что делает их создание и удаление быстрее. Куча (
heap) требует управления памятью (ARC – Automatic Reference Counting), а struct — нет. class MyClass { var value = 0 } // В куче (heap), управляется ARC
struct MyStruct { var value = 0 } // В стеке (stack), копируется при передачеstruct ведут себя как примитивные типы (Int, Double), что делает код предсказуемым. Их изменение происходит локально, без неожиданных эффектов в других частях программы.
class Car {
var speed: Int
init(speed: Int) { self.speed = speed }
}
var car1 = Car(speed: 60)
var car2 = car1 // car2 - это ссылка на тот же объект
car2.speed = 100 // Изменение затрагивает car1!
print(car1.speed) // 100 (хотя мы меняли car2!)Swift изначально построен на
struct: Int, Double, Bool, Array, Dictionary, String — это структуры. Это делает язык более безопасным и производительным.
var arr1 = [1, 2, 3]
var arr2 = arr1 // Копия массива, а не ссылка!
arr2.append(4)
print(arr1) // [1, 2, 3] (не изменился!)
print(arr2) // [1, 2, 3, 4] (новый массив)
Хотя
struct — предпочтительный выбор, class нужен, когда: Нужна ссылочная семантика (например, объект должен изменяться в разных местах кода).
Есть сложные иерархии наследования.
Требуется работа с Objective-C (
NSObject).Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Свойство clipsToBounds управляет тем, отображается ли содержимое (включая дочерние вью или слой) за пределами границ UIView. Если оно установлено в true, то всё, что выходит за пределы рамок вью, будет обрезано. Это особенно важно при работе с масками, эффектами, границами и кастомной графикой.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
В зависимости от контекста, под "объектами, участвующими в зависимостях" можно понимать различные концепции. В программировании под зависимостями чаще всего подразумеваются связи между объектами или модулями, где один объект зависит от другого для выполнения своих функций.
Когда один объект (например,
ViewController) зависит от другого (NetworkManager), первый становится клиентом, а второй – зависимостью. class NetworkManager {
func fetchData() {
print("Данные загружены")
}
}
class ViewController {
let networkManager: NetworkManager
init(networkManager: NetworkManager) {
self.networkManager = networkManager
}
func loadData() {
networkManager.fetchData()
}
}Жесткие зависимости можно ослабить, используя протоколы.
protocol NetworkService {
func fetchData()
}
class NetworkManager: NetworkService {
func fetchData() {
print("Данные загружены")
}
}
class ViewController {
let networkService: NetworkService
init(networkService: NetworkService) {
self.networkService = networkService
}
func loadData() {
networkService.fetchData()
}
}В MVVM зависимость между
ViewController и ViewModel. В VIPER модули зависят друг от друга, но слабо связаны через протоколы.
В DI (Dependency Injection) зависимости передаются снаружи, что повышает тестируемость и гибкость.
Чтобы управлять внешними зависимостями (библиотеками), используются
Swift Package Manager (SPM)
CocoaPods
Carthage
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Имплементация протокола — это реализация всех обязательных требований (свойств и методов), определённых в протоколе. В Swift это делается с помощью ключевого слова protocol, и затем класс, структура или enum, подписываясь на него, реализуют нужный функционал.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это сообщения, которые отправляются приложением пользователю даже тогда, когда само приложение не активно. Это эффективный способ удержания пользователей и предоставления им важной информации.
Когда приложение устанавливается и запускается на устройстве, оно регистрируется для получения 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
👍1
Это обёртка для значения, которая добавляет дополнительные функции или настройки.
1. Пример:
2. Обеспечивает удобство и декларативный подход к программированию.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2
В iOS жизненный цикл
UIViewController определяет последовательность вызовов методов, происходящих во время создания, отображения, скрытия и уничтожения контроллера. На этом этапе создаётся экземпляр
UIViewController, но его view ещё не загружено. - Можно переопределить
init() или init(coder:), если контроллер создаётся из Storyboard. - Можно передавать данные через инициализатор.
class MyViewController: UIViewController {
var titleText: String
init(titleText: String) {
self.titleText = titleText
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}Когда контроллеру нужно отобразить свой
view, вызывается loadView() (если представление создаётся программно) и viewDidLoad() (если загружается из Storyboard или XIB). loadView() – создаёт view программно (обычно не переопределяется). viewDidLoad() – вызывается один раз после загрузки view. override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
print("viewDidLoad - View загружено в память")
}Когда
UIViewController добавляется в иерархию UIWindow и становится видимым, вызываются следующие методы: viewWillAppear(_:) – вызывается перед появлением на экране. viewDidAppear(_:) – вызывается после появления на экране. viewWillDisappear(_:) – вызывается перед скрытием. viewDidDisappear(_:) – вызывается после скрытия. override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear - View скоро появится на экране")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear - View уже на экране")
}Когда
UIViewController больше не нужен, вызывается deinit(), а его view может быть выгружено с вызовом viewDidUnload() (но сейчас это редко используется). deinit {
print("deinit - Контроллер удалён из памяти")
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔3👍2
+ Упрощает адаптацию интерфейса под разные экраны.
+ Легче поддерживать и изменять.
- Медленнее, чем прямое управление через фреймы.
Frame:
+ Более производительный, так как не требует расчётов.
- Требует учёта размеров экрана вручную, что делает код менее гибким.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Если вы хотите задать отступы (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
🤔1
1. Создать кастомную кнопку и переопределить point(inside:with:)
2. Либо обернуть кнопку в UIView с большим frame, но маленькой кнопкой внутри.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2
В Swift ссылки (references) на объекты могут быть сильными (strong) и слабыми (weak). Они отличаются способом управления памятью и временем жизни объектов, на которые ссылаются.
Сильная ссылка (strong reference) удерживает объект в памяти. Пока существует хотя бы одна сильная ссылка на объект, он не будет удалён из памяти.
Сильные ссылки используются по умолчанию и обеспечивают, что объект остаётся доступным до тех пор, пока он необходим.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var person1: Person? = Person(name: "Alice")
var person2: Person? = person1 // person2 имеет сильную ссылку на тот же объект, что и person1
person1 = nil // Объект все еще удерживается в памяти благодаря person2
print(person2?.name) // Output: Alice
person2 = nil // Теперь объект будет удалён из памяти, так как нет сильных ссылокСлабая ссылка (weak reference) не удерживает объект в памяти. Если объект больше не имеет сильных ссылок, он будет удалён из памяти, даже если на него существуют слабые ссылки.
Слабые ссылки используются для предотвращения циклических ссылок, которые могут привести к утечкам памяти.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
class Apartment {
var tenant: Person?
init(tenant: Person?) {
self.tenant = tenant
}
}
var alice: Person? = Person(name: "Alice")
var apartment: Apartment? = Apartment(tenant: alice)
// Создание слабой ссылки для предотвращения циклической зависимости
class Tenant {
var name: String
weak var apartment: Apartment? // Слабая ссылка
init(name: String) {
self.name = name
}
}
let tenant = Tenant(name: "Bob")
apartment?.tenant = alice
alice = nil // Объект Person будет удалён из памяти, так как больше нет сильных ссылок
print(apartment?.tenant?.name) // Output: nilСильные ссылки: Удерживают объект в памяти. Объект будет освобождён только тогда, когда все сильные ссылки на него будут удалены.
Слабые ссылки: Не удерживают объект в памяти. Объект будет освобождён, когда не останется сильных ссылок.
Сильные ссылки: Используются по умолчанию для обеспечения сохранности объектов в памяти.
Слабые ссылки: Используются для предотвращения циклических ссылок и утечек памяти, особенно в структурах данных с взаимосвязанными объектами.
Сильные ссылки: Не обнуляются автоматически при удалении объекта из памяти.
Слабые ссылки: Автоматически обнуляются, когда объект, на который они ссылаются, удаляется из памяти.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Если передать nil в target у UI-элемента, система будет искать обработчик в responder chain. Если его не найдёт — действие будет проигнорировано, но ошибки не произойдёт.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
Паттерны проектирования – это готовые решения частых задач, улучшающие структуру кода, его поддержку и масштабируемость.
Помогают упростить создание объектов и сделать его гибким.
Определяет интерфейс для создания объекта, но поручает подклассам выбрать его тип.
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
- Не создаёт сильной ссылки, не удерживает объект в памяти.
- Избегает retain cycle, как и weak.
- В отличие от weak, не является опциональным, но при обращении к уничтоженному объекту приводит к крашу — требует уверенности, что объект жив.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Внедрение зависимостей (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
🔥2👍1
fallthrough используется в switch, чтобы намеренно перейти к следующему case, даже если тот не подходит по условию.
По умолчанию в Swift после выполнения одного case switch завершается, и fallthrough — это явное указание продолжить дальше.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
В iOS для выполнения фоновых задач существуют несколько ключевых механизмов:
DispatchQueue.global(qos: .background).async {
// Фоновая задача
}let queue = OperationQueue()
queue.addOperation {
// Фоновая операция
}
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// Фоновое обновление данных
completionHandler(.newData)
} import BackgroundTasks
func scheduleBackgroundTask() {
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
try? BGTaskScheduler.shared.submit(request)
}
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let session = URLSession(configuration: configuration)
let url = URL(string: "https://example.com/largefile")!
let task = session.downloadTask(with: url)
task.resume()
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Опциональные функции (
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1