Сцены и фазы переходов (Scene phases and transitions) — важные аспекты управления состоянием приложения в SwiftUI, особенно когда речь идет о многозадачности и жизненном цикле приложения. SwiftUI предоставляет механизмы для обработки этих состояний и переходов через специальные property wrappers и API.
Приложение активно и отображает контент на экране.
Приложение находится на переднем плане, но не взаимодействует с пользователем (например, при входящем вызове).
Приложение находится в фоне и не отображается на экране.
Пример использования @Environment(\.scenePhase)
import SwiftUI
struct ContentView: View {
@Environment(\.scenePhase) var scenePhase
var body: some View {
Text("Привет, мир!")
.onChange(of: scenePhase) { newPhase in
switch newPhase {
case .active:
print("Сцена активна")
case .inactive:
print("Сцена неактивна")
case .background:
print("Сцена в фоне")
@unknown default:
print("Неизвестное состояние сцены")
}
}
}
}
Переходы между фазами сцены происходят автоматически, когда пользователь взаимодействует с приложением или системой. Однако важно правильно обрабатывать эти переходы, чтобы обеспечить корректную работу приложения, например, сохранять данные при переходе в фоновый режим.
Для приложений, использующих
UIKit и SwiftUI вместе, обработка фаз сцены может выполняться в классе SceneDelegate. import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
}
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Сцена стала активной
print("Сцена активна")
}
func sceneWillResignActive(_ scene: UIScene) {
// Сцена станет неактивной
print("Сцена неактивна")
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Сцена перешла в фоновый режим
print("Сцена в фоне")
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Сцена перейдет в передний план
print("Сцена на переднем плане")
}
}
Позволяет сохранять важные данные, когда приложение переходит в фоновый режим, и восстанавливать их при возвращении.
Позволяет освобождать ресурсы, когда приложение не активно, и выделять их, когда оно становится активным.
Обеспечивает плавный и предсказуемый опыт для пользователя при смене состояний приложения.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
2. Direct Dispatch: прямой вызов метода без участия vtable.
3. Protocol Witness Table (PWT): используется для вызовов методов, реализованных через протоколы.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В SwiftUI существует несколько модификаторов, которые позволяют изменять и обрабатывать события жизненного цикла вью. Эти модификаторы помогают реагировать на различные состояния представления и управлять его поведением.
Выполняет действие, когда представление появляется на экране.
Выполняет действие, когда представление исчезает с экрана.
Выполняет асинхронное действие при появлении представления на экране.
Реагирует на изменения определенного состояния или значения.
Реагирует на обновления из
Publisher.onAppear выполняет действие, когда представление появляется на экране. struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.onAppear {
print("Представление появилось на экране")
}
}
}onDisappear выполняет действие, когда представление исчезает с экрана. struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.onDisappear {
print("Представление исчезло с экрана")
}
}
}task выполняет асинхронное действие при появлении представления на экране. Это полезно для загрузки данных или выполнения других асинхронных задач. struct ContentView: View {
var body: some View {
Text("Привет, мир!")
.task {
await loadData()
}
}
func loadData() async {
// Асинхронная загрузка данных
print("Данные загружаются")
}
}onChange реагирует на изменения определенного состояния или значения. struct ContentView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("Счётчик: \(counter)")
Button("Увеличить счётчик") {
counter += 1
}
}
.onChange(of: counter) { newValue in
print("Счётчик изменился на \(newValue)")
}
}
}onReceive реагирует на обновления из Publisher. Это полезно для обработки данных из Combine. import Combine
struct ContentView: View {
@State private var data: String = "Загрузка..."
let publisher = PassthroughSubject<String, Never>()
var body: some View {
Text(data)
.onReceive(publisher) { value in
data = value
}
}
}
Легко управлять состоянием представлений в различные моменты их жизненного цикла.
Выполнение асинхронных задач, таких как загрузка данных, при появлении представлений на экране.
Реакция на изменения состояний и значений в представлении.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Синхронные и асинхронные соединения имеют свои уникальные преимущества и недостатки. Понимание этих различий важно для правильного выбора подхода в зависимости от контекста и требований приложения.
Плюсы
Синхронные операции проще понимать и реализовывать, так как они выполняются последовательно.
Отладка синхронного кода проще, так как последовательность выполнения предсказуема.
Синхронный код выполняется по порядку, что делает логику более прозрачной.
Минусы
Синхронные операции могут блокировать основной поток выполнения, что приводит к замедлению работы интерфейса или приложения в целом.
Приложения с синхронными операциями могут становиться менее отзывчивыми, так как долгие операции могут замораживать пользовательский интерфейс.
Синхронный подход может ограничивать возможности масштабирования, так как каждая операция ожидает завершения предыдущей.
Плюсы
Асинхронные операции позволяют выполнять другие задачи, не дожидаясь завершения долгих операций, что увеличивает общую производительность приложения.
Асинхронный подход позволяет поддерживать отзывчивость пользовательского интерфейса, так как долгие операции выполняются в фоновом режиме.
Асинхронный код лучше масштабируется, так как позволяет обрабатывать множество запросов одновременно.
Минусы
Асинхронные операции требуют более сложной реализации и понимания концепций, таких как колбэки, промисы или async/await.
Отладка асинхронного кода может быть сложнее из-за непредсказуемой последовательности выполнения.
Асинхронные операции могут приводить к неопределенным состояниям, если не обрабатывать их правильно, что может привести к трудноуловимым ошибкам.
Синхронный пример
func fetchDataSynchronously() {
let url = URL(string: "https://api.example.com/data")!
let data = try? Data(contentsOf: url)
if let data = data {
print("Данные загружены: \(data)")
} else {
print("Ошибка загрузки данных")
}
}Асинхронный пример
func fetchDataAsynchronously() {
let url = URL(string: "https://api.example.com/data")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
print("Данные загружены: \(data)")
} else if let error = error {
print("Ошибка загрузки данных: \(error)")
}
}.resume()
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это модификатор, который предотвращает изменения:
- Класс: нельзя наследовать.
- Метод: нельзя переопределять.
- Переменная: значение нельзя изменить после инициализации.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Не поддерживает множественное наследование для классов, что является общим выбором в современных объектно-ориентированных языках программирования из-за сложностей и проблем, которые множественное наследование может создать (например, проблема алмаза смерти). Однако предлагаются альтернативные механизмы, позволяющие имитировать некоторые аспекты множественного наследования.
Похожи на интерфейсы в других языках. Они позволяют классам, структурам и перечислениям принимать "контракты", которые определяют методы и свойства, которые должны быть реализованы. Протоколы могут наследоваться и расширяться, предоставляя гибкий способ добавления функциональности к типам.
protocol Movable {
func move()
}
protocol Drawable {
func draw()
}
class Character: Movable, Drawable {
func move() {
print("Персонаж двигается")
}
func draw() {
print("Персонаж рисуется")
}
}
Позволяют добавлять новую функциональность к существующим типам, включая те, которые определены в стандартной библиотеке или внешних библиотеках. Хотя расширения не могут добавлять хранимые свойства, они могут добавлять новые вычисляемые свойства, методы и протоколы.
extension Character: Drawable {
func draw() {
print("Персонаж рисуется через расширение")
}
}Это альтернативный подход к наследованию, при котором один тип включает другой как часть своей реализации, вместо того чтобы наследовать от него. Это предпочтительный метод для достижения повторного использования кода, позволяющий избежать ограничений и сложностей наследования.
class Engine {
func start() {
print("Двигатель запущен")
}
}
class Car {
let engine = Engine()
func startEngine() {
engine.start()
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
- Layer: низкоуровневый компонент (из Core Animation), который отвечает за рендеринг содержимого и может использоваться для сложных анимаций или оптимизаций.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
Используется в замыканиях (closures) для управления тем, как замыкание захватывает переменные и константы из окружающего контекста. Могут захватывать и хранить ссылки на любые константы и переменные из контекста, в котором они определены. Это удобно, но может привести к сильным ссылочным циклам и утечкам памяти, если замыкание захватывает
self или другие экземпляры класса.Предоставляет способ определить правила захвата переменных в замыкании. Она задаётся в начале замыкания и позволяет избежать нежелательных сильных ссылок, особенно при работе с
self в методах класса, что очень важно для предотвращения утечек памяти в приложениях.Синтаксис Capture List
{ [capture rule] (parameters) -> return type in
// Код замыкания
}Пример с простым замыканием
var a = 0
var b = 0
let closure = { [a] in
print(a, b)
}
a = 10
b = 10
closure() // Выведет "0 10"
Использование Capture List для предотвращения сильных ссылочных циклов
class MyClass {
var property = "Property"
func doSomething() {
let closure = { [weak self] in
print(self?.property ?? "нет self")
}
closure()
}
deinit {
print("MyClass экземпляр был деинициализирован")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Термин "диспетчеризация" часто используется для описания механизма, посредством которого вызывается метод или функция в ответ на вызов. В контексте ООП, особенно в языках с поддержкой полиморфизма, диспетчеризация может быть статической (ранней) или динамической (поздней).
Использует компиляцию времени компиляции для определения того, какой метод будет вызван. Это означает, что компилятор определяет адрес вызываемого метода на этапе компиляции, и этот адрес не изменяется во время выполнения программы.
В языках, таких как C и Swift (при использовании
final классов или структур), методы, известные во время компиляции, могут быть вызваны без дополнительной нагрузки, связанной с поиском в таблице виртуальных функций.Используется в языках программирования, поддерживающих полиморфизм и наследование, и означает, что метод, который будет вызван, определяется во время выполнения. Это позволяет объектам различных классов отвечать на одни и те же сообщения (вызовы методов), каждый по-своему.
Таблица виртуальных методов (VMT): В объектно-ориентированных языках, таких как C++ или Java, каждый класс с виртуальными методами имеет таблицу виртуальных методов. Эта таблица содержит адреса всех виртуальных методов, которые могут быть вызваны для объекта данного класса. Поиск по таблице: Во время выполнения, когда вызывается метод, производится поиск соответствующего метода в таблице VMT, и используется адрес, найденный в таблице, что вносит задержку по сравнению со статической диспетчеризацией.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
- Слабые ссылки (weak): не увеличивают счётчик ссылок, используются для предотвращения циклических зависимостей.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Это ключевой механизм ООП, позволяющий классам наследовать свойства, методы и другие характеристики от других классов. Это позволяет создавать новые классы на основе существующих, расширяя их функциональность или изменяя её.
Базовый класс определяет общие свойства и методы, которые могут быть унаследованы подклассами.
Подкласс наследует (или "расширяет") базовый класс. Он может переопределять унаследованные методы и свойства, добавлять новые методы и свойства, а также добавлять инициализаторы или изменять существующие.
Подклассы могут переопределять методы, свойства и индексаторы базового класса для изменения или расширения их поведения.
Можно предотвратить переопределение методов, свойств или индексаторов с помощью ключевого слова
final. Если метод, свойство или индексатор объявлен как final, то он не может быть переопределён в подклассе.class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// Этот метод будет переопределен в подклассах, если необходимо
}
}
class Bicycle: Vehicle {
var hasBasket = false
}
class Car: Vehicle {
var gear = 1
final func drive() {
print("Car is moving")
}
override func makeNoise() {
print("Vroom!")
}
}Подклассы могут вызывать методы своего суперкласса с помощью ключевого слова
super. Это позволяет подклассам расширять, а не заменять поведение суперкласса.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Обычно имеют в виду различия между использованием стека вызовов (память стека) и кучи (heap) в контексте управления памятью в программировании. Эти два типа памяти имеют разные способы выделения и освобождения памяти, каждый со своими преимуществами и недостатками.
Память в стеке выделяется и освобождается по очень простому и быстрому принципу: LIFO (Last In, First Out). Это означает, что для выделения памяти достаточно переместить указатель стека вверх (при добавлении данных) или вниз (при удалении данных). Этот процесс почти мгновенен и не требует дополнительных вычислений.
В куче память выделяется динамически, что требует управления доступными блоками памяти. Аллокатор памяти должен найти достаточно большой свободный блок, что может занять значительное время, особенно если память фрагментирована. Также освобождение памяти в куче требует более сложной обработки, включая возможную дефрагментацию.
Доступ к данным в стеке очень быстрый, потому что данные всегда добавляются и удаляются с "вершины" стека, где находится указатель стека. Это делает доступ к текущим локальным переменным очень быстрым и предсказуемым. Доступ к данным в куче может быть менее эффективным, поскольку данные могут быть разбросаны по разным частям памяти. Кроме того, дополнительное время требуется для поиска и управления блоками памяти.
Его память автоматически очищается при выходе из области видимости, что упрощает управление памятью и снижает риск утечек памяти. Память, выделенная в куче, остаётся занятой до тех пор, пока явно не будет освобождена. Это увеличивает риск утечек памяти, если разработчик забудет освободить память.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
- Аллокацию памяти.
- Освобождение через сборщик мусора (в Java, Swift) или вручную (в C++).
- Компактирование для предотвращения фрагментации.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2🤯2👍1
Это абстрактная структура данных, работающая по принципу LIFO (Last In, First Out), что означает "последний пришёл — первый вышел". Это значит, что последний добавленный элемент будет первым при извлечении из стека. Под капотом реализации стека могут быть разные, и они зависят от конкретного языка программирования и задач, которые необходимо решить.
Один из самых распространённых способов реализации стека — это использование массива. В такой реализации элементы стека хранятся в массиве, и индекс последнего элемента (вершина стека) отслеживается отдельной переменной.
struct Stack<Element> {
private var storage: [Element] = []
mutating func push(_ element: Element) {
storage.append(element)
}
mutating func pop() -> Element? {
return storage.popLast()
}
func peek() -> Element? {
return storage.last
}
var isEmpty: Bool {
return storage.isEmpty
}
}Стек можно реализовать с использованием связных списков, где каждый элемент списка содержит данные и ссылку на следующий элемент в стеке. Вершина стека в такой реализации — это начало связного списка.
class Node<Element> {
var value: Element
var next: Node?
init(value: Element) {
self.value = value
}
}
struct Stack<Element> {
private var head: Node<Element>?
mutating func push(_ element: Element) {
let node = Node(value: element)
node.next = head
head = node
}
mutating func pop() -> Element? {
let node = head
head = head?.next
return node?.value
}
func peek() -> Element? {
return head?.value
}
var isEmpty: Bool {
return head == nil
}
}Это системный стек, который используется во время выполнения программы для хранения информации о вызовах функций/методов. Он хранит адреса возврата, параметры функций, локальные переменные и другие данные, необходимые для управления вызовами функций и их возврата.
Обратную польскую нотацию для вычисления арифметических выражений. Управление вызовами функций в программном стеке. Поддержка операций undo в приложениях.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
В iOS-приложениях можно создавать анимации несколькими способами.
Самый простой способ анимации представлений (views) в iOS - это использование метода
UIView.animate. Вот пример кода, который изменяет положение и прозрачность представления за 1 секунду:UIView.animate(withDuration: 1.0) {
myView.frame.origin.y += 100
myView.alpha = 0.5
}Для более сложных анимаций можно использовать Core Animation, например,
CABasicAnimation. Вот пример анимации изменения позиции слоя (layer):let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = NSValue(cgPoint: CGPoint(x: 50, y: 50))
animation.toValue = NSValue(cgPoint: CGPoint(x: 150, y: 150))
animation.duration = 1.0
myView.layer.add(animation, forKey: "positionAnimation")
Этот класс предоставляет более детальный контроль над анимациями. Его можно использовать для создания и управления анимациями в реальном времени:
let animator = UIViewPropertyAnimator(duration: 1.0, curve: .easeInOut) {
myView.frame.origin.y += 100
myView.alpha = 0.5
}
animator.startAnimation()Для создания реалистичных анимаций с пружинным эффектом можно использовать метод
UIView.animate с параметрами пружинного демпфирования:UIView.animate(withDuration: 1.0,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.5,
options: [],
animations: {
myView.frame.origin.y += 100
myView.alpha = 0.5
}, completion: nil)
Для анимации переходов между экранами используется
UIViewControllerAnimatedTransitioning. Это требует реализации методов протокола UIViewControllerAnimatedTransitioning:class CustomTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else { return }
transitionContext.containerView.addSubview(toView)
toView.alpha = 0.0
UIView.animate(withDuration: 0.5, animations: {
toView.alpha = 1.0
}, completion: { finished in
transitionContext.completeTransition(finished)
})
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM