Разделение кода на модули позволяет разработчикам легче понимать и навигировать по коду. Когда код структурирован и разбит на логически связанные части, его проще читать и поддерживать.
View: Отвечает за отображение данных и взаимодействие с пользователем.
Model: Содержит бизнес-логику и данные.
Controller/Presenter/Interactor: Управляет взаимодействием между View и Model.
Когда код разбит на отдельные модули, каждый модуль может быть использован повторно в разных частях приложения или даже в разных проектах. Это способствует созданию более модульных и переиспользуемых компонентов.
Разделение кода на модули упрощает написание и проведение тестов. Модульный код легче тестировать изолированно, что позволяет находить и исправлять ошибки быстрее и эффективнее.
Модульный код
class DataService {
func fetchData() -> [String] {
// Логика получения данных
}
}
class ViewModel {
let dataService: DataService
init(dataService: DataService) {
self.dataService = dataService
}
func getData() -> [String] {
return dataService.fetchData()
}
}Тест
func testFetchData() {
let dataService = MockDataService()
let viewModel = ViewModel(dataService: dataService)
let data = viewModel.getData()
XCTAssertEqual(data, ["MockData1", "MockData2"])
}Когда код структурирован и разбит на модули, разные члены команды могут работать над разными частями проекта одновременно, не мешая друг другу. Это способствует параллельной разработке и уменьшает конфликты слияния (merge conflicts).
Разделение кода на более мелкие, управляемые части помогает снизить общую сложность системы. Каждый модуль решает одну конкретную задачу, что делает систему более понятной и управляемой.
Разделение кода помогает следовать принципам SOLID:
S ingle Responsibility Principle (Принцип единственной ответственности): Каждый модуль отвечает за одну конкретную задачу.
O pen/Closed Principle (Принцип открытости/закрытости): Модули открыты для расширения, но закрыты для модификации.
L iskov Substitution Principle (Принцип подстановки Лисков): Модули можно заменить другими, не нарушая работу системы.
I nterface Segregation Principle (Принцип разделения интерфейсов): Избегайте создания "толстых" интерфейсов; предпочтительно иметь несколько специализированных интерфейсов.
D ependency Inversion Principle (Принцип инверсии зависимостей): Модули зависят от абстракций, а не от конкретных реализаций.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Реактивное программирование упрощает координацию асинхронных операций, избегая "callback hell".
До:
fetchData { data in
process(data) { processedData in
save(processedData) { success in
print("Data saved: \(success)")
}
}
}После:
fetchData()
.flatMap { process($0) }
.flatMap { save($0) }
.subscribe(onNext: { success in
print("Data saved: \(success)")
})
Обеспечивает простое и автоматическое обновление UI при изменении данных.
data.asObservable()
.bind(to: tableView.rx.items(cellIdentifier: "Cell")) { index, model, cell in
cell.textLabel?.text = model
}
.disposed(by: disposeBag)
Позволяет легко объединять данные из разных источников.
Observable.zip(loadUser(), loadPosts())
.subscribe(onNext: { user, posts in
display(posts)
})
Централизованная обработка ошибок в асинхронных цепочках.
fetchData()
.flatMap { process($0) }
.flatMap { save($0) }
.subscribe(onNext: { success in
print("Data saved: \(success)")
}, onError: { error in
print("Error: \(error)")
})
Автоматическая привязка данных к элементам UI.
usernameObservable
.bind(to: usernameLabel.rx.text)
.disposed(by: disposeBag)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Главный поток отвечает за обновление пользовательского интерфейса (UI) и обработку взаимодействия с пользователем. Если этот поток блокируется, приложение может стать неотзывчивым, что вызывает задержки в интерфейсе или даже его полную заморозку.
Если главный поток занят выполнением длительных операций, UI не будет обновляться, что приведет к замораживанию или задержке интерфейса.
Задержки и лаги при взаимодействии с приложением вызывают неудовлетворенность пользователей и могут привести к негативным отзывам.
Длительная блокировка главного потока может привести к тому, что система iOS решит завершить приложение, считая его неотзывчивым.
GCD предоставляет простой способ выполнения асинхронных задач в фоновом режиме.
DispatchQueue.global(qos: .background).async {
// Длительная задача
let result = performHeavyComputation()
DispatchQueue.main.async {
// Обновление UI с результатом
updateUI(with: result)
}
}OperationQueue предлагает более высокоуровневый интерфейс для управления асинхронными задачами с возможностью задания приоритетов и зависимостей.
let backgroundQueue = OperationQueue()
backgroundQueue.addOperation {
// Длительная задача
let result = performHeavyComputation()
OperationQueue.main.addOperation {
// Обновление UI с результатом
updateUI(with: result)
}
}
URLSession автоматически выполняет сетевые запросы в фоновом режиме.
let url = URL(string: "https://example.com/data")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
// Обработка данных
DispatchQueue.main.async {
// Обновление UI
updateUI(with: data)
}
}
task.resume()
Для выполнения запросов к базе данных можно использовать фоновые контексты (background contexts).
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.perform {
let fetchRequest: NSFetchRequest<Entity> = Entity.fetchRequest()
let results = try? backgroundContext.fetch(fetchRequest)
DispatchQueue.main.async {
// Обновление UI с результатами
updateUI(with: results)
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Может быть сложным для реализации из-за необходимости создания множества компонентов даже для простых задач.
Создание пользовательской ячейки
import UIKit
class CustomTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
func configure(with text: String) {
titleLabel.text = text
}
}
View
import UIKit
protocol TableViewProtocol: AnyObject {
func updateTableView()
}
class TableViewController: UIViewController, TableViewProtocol {
@IBOutlet weak var tableView: UITableView!
var presenter: TableViewPresenterProtocol?
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
presenter?.viewDidLoad()
}
func updateTableView() {
tableView.reloadData()
}
}
extension TableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return presenter?.numberOfRows() ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
if let text = presenter?.textForRow(at: indexPath) {
cell.configure(with: text)
}
return cell
}
}
Presenter
protocol TableViewPresenterProtocol: AnyObject {
func viewDidLoad()
func numberOfRows() -> Int
func textForRow(at indexPath: IndexPath) -> String
}
class TableViewPresenter: TableViewPresenterProtocol {
weak var view: TableViewProtocol?
var interactor: TableViewInteractorProtocol?
func viewDidLoad() {
interactor?.fetchData()
}
func numberOfRows() -> Int {
return interactor?.data.count ?? 0
}
func textForRow(at indexPath: IndexPath) -> String {
return interactor?.data[indexPath.row] ?? ""
}
}Interactor
protocol TableViewInteractorProtocol: AnyObject {
var data: [String] { get }
func fetchData()
}
class TableViewInteractor: TableViewInteractorProtocol {
var data: [String] = []
func fetchData() {
data = ["Item 1", "Item 2", "Item 3"]
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔3
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Да, в функциях часто используются свойства класса. Функции, методы и замыкания могут взаимодействовать со свойствами класса или структуры, чтобы получить доступ к данным или изменить их.
Могут использовать свойства класса для чтения или изменения данных. В этом примере метод
celebrateBirthday использует свойства name и age для обновления возраста и печати сообщения.class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func celebrateBirthday() {
age += 1
print("Happy Birthday, \(name)! You are now \(age) years old.")
}
}
let person = Person(name: "Alice", age: 30)
person.celebrateBirthday()
// Output: "Happy Birthday, Alice! You are now 31 years old."Используют свойства класса для установки начальных значений при создании экземпляра класса.
class Car {
var model: String
var year: Int
init(model: String, year: Int) {
self.model = model
self.year = year
}
func displayInfo() {
print("Car model: \(model), year: \(year)")
}
}
let car = Car(model: "Toyota", year: 2022)
car.displayInfo()
// Output: "Car model: Toyota, year: 2022"Могут захватывать и использовать свойства класса. Это часто используется в асинхронных операциях, таких как сетевые запросы.
class Downloader {
var url: String
init(url: String) {
self.url = url
}
func download(completion: @escaping () -> Void) {
print("Downloading from \(url)...")
DispatchQueue.global().async {
// Симуляция загрузки
sleep(2)
DispatchQueue.main.async {
completion()
}
}
}
}
let downloader = Downloader(url: "https://example.com/file")
downloader.download {
print("Download completed.")
}
// Output:
// "Downloading from https://example.com/file..."
// (2 секунды спустя)
// "Download completed."Используются для данных или функций, которые относятся ко всему классу, а не к конкретному экземпляру. В этом примере статическое свойство
count используется для отслеживания количества созданных экземпляров класса.class Counter {
static var count = 0
init() {
Counter.count += 1
}
static func displayCount() {
print("Number of instances: \(count)")
}
}
let counter1 = Counter()
let counter2 = Counter()
Counter.displayCount()
// Output: "Number of instances: 2"Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍1
Объект может быть освобожден из памяти, даже если на него существует сильная ссылка, при использовании ключевого слова
unowned в некоторых специфических обстоятельствах. Неустранимые ссылки (
unowned) не увеличивают счетчик ссылок на объект, к которому они относятся. В отличие от слабых ссылок (weak), unowned ссылки не обнуляются автоматически, когда объект освобождается. Это означает, что если вы попытаетесь обратиться к объекту через unowned ссылку после его освобождения, это приведет к аварийному завершению программы (runtime crash).Если бы вы попытались обратиться к owner после освобождения john, это привело бы к аварийному завершению программы, так как unowned ссылка не обнуляется и остается указателем на освобожденный объект.class Person {
var name: String
var creditCard: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class CreditCard {
var number: Int
unowned var owner: Person
init(number: Int, owner: Person) {
self.number = number
self.owner = owner
}
deinit {
print("CreditCard #\(number) is being deinitialized")
}
}
var john: Person? = Person(name: "John")
john!.creditCard = CreditCard(number: 1234, owner: john!)
john = nil
// Output:
// John is being deinitialized
// CreditCard #1234 is being deinitializedPerson с именем "John" и сильной ссылкой john.CreditCard, который имеет неустранимую ссылку owner на john.john освобождается (при установке john в nil), также освобождается и объект CreditCard.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Может вызывать проблемы, особенно в контексте замыканий, циклических ссылок и асинхронных операций.
Возникают, когда два объекта удерживают друг друга сильными ссылками. Решение: Использовать
weak или unowned ссылки. class Person {
var name: String
var apartment: Apartment?
init(name: String) {
self.name = name
}
}
class Apartment {
var unit: String
weak var tenant: Person? // Используем weak
init(unit: String) {
self.unit = unit
}
}Может привести к циклическим ссылкам и утечкам памяти. Решение: Использовать списки захвата (
capture lists) с weak или unowned. class MyViewController: UIViewController {
func loadData() {
someAsyncMethod { [weak self] in
guard let self = self else { return }
print(self.name)
}
}
}Могут завершиться после освобождения объекта, вызвавшего их. Решение: Использовать
weak или unowned ссылки внутри замыканий. class MyViewController: UIViewController {
func fetchData() {
DispatchQueue.global().async { [weak self] in
sleep(2)
DispatchQueue.main.async {
self?.updateUI()
}
}
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
Это ключевое слово, которое используется для создания неустранимых ссылок. Неустранимая ссылка (
unowned) не увеличивает счётчик ссылок на объект, к которому она относится, и, в отличие от слабых ссылок (weak), не обнуляется автоматически, когда объект освобождается из памяти. Использование unowned ссылок полезно в тех случаях, когда вы уверены, что объект, на который ссылается unowned ссылка, будет существовать на протяжении всей жизни ссылки.Unowned ссылки не удерживают объект в памяти, что предотвращает циклические ссылки.Если объект, на который ссылается
unowned ссылка, освобождается из памяти, попытка доступа к этой ссылке приведёт к ошибке времени выполнения (runtime crash).Unowned ссылки следует использовать только тогда, когда вы уверены, что объект будет существовать на протяжении всей жизни ссылки.class Person {
var name: String
var creditCard: CreditCard?
init(name: String) {
self.name = name
}
deinit {
print("\(name) is being deinitialized")
}
}
class CreditCard {
var number: Int
unowned var owner: Person
init(number: Int, owner: Person) {
self.number = number
self.owner = owner
}
deinit {
print("CreditCard #\(number) is being deinitialized")
}
}
var john: Person? = Person(name: "John")
john!.creditCard = CreditCard(number: 1234, owner: john!)
john = nil
// Output:
// John is being deinitialized
// CreditCard #1234 is being deinitializedPerson с именем "John".CreditCard, который имеет unowned ссылку owner на john.john освобождается (при присвоении nil), также освобождается и объект CreditCard. Случаи использования unownedВы уверены, что объект будет существовать на протяжении всей жизни ссылки.
unowned позволяет избежать использования опционалов (Optional), что упрощает код.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🔥1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3
Используется для отображения списков данных и оптимизирует производительность с помощью повторного использования ячеек.
tableView(_:numberOfRowsInSection:): Количество строк.tableView(_:cellForRowAt:): Конфигурация ячеек.tableView(_:didSelectRowAt:): Обработка выбора строки.tableView(_:heightForRowAt:): Настройка высоты строки.Ячейки (
UITableViewCell) отображают контент.Добавьте
UITableView в ViewController через Interface Builder или программно. Подключите UITableView как IBOutlet.Назначьте
ViewController источником данных и делегатом таблицы. Реализуйте методы протоколов. import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1000 // Количество строк
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Row \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Selected row: \(indexPath.row)")
}
}
Используйте
dequeueReusableCell(withIdentifier:for:) для повторного использования ячеек.Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
1. `loadView` создает представление, которое контроллер управляет.
2. `viewDidLoad` вызывается после загрузки представления контроллера в память.
3. `viewWillAppear` выполняется перед тем, как представление станет видимым.
4. `viewDidAppear` вызывается после того, как представление появилось на экране.
5. `viewWillDisappear` и `viewDidDisappear` вызываются перед и после того, как представление было удалено с экрана.
6. `deinit` вызывается перед освобождением экземпляра контроллера из памяти.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Модель представляет данные и бизнес-логику, представление отвечает за отображение данных и взаимодействие с пользователем, а ViewModel связывает модель и представление, управляя состоянием и логикой представления.
- Models
- User.swift
- Views
- UserView.swift
- ViewModels
- UserViewModel.swift
Модель представляет данные и бизнес-логику. Например, это может быть структура
User. struct User {
let name: String
let age: Int
}ViewModel связывает модель и представление, управляя состоянием и логикой представления. Он использует
@Published для обновления представления при изменении данных. import Combine
class UserViewModel: ObservableObject {
@Published var user: User?
func fetchUser() {
// Логика получения данных пользователя (например, из сети или базы данных)
self.user = User(name: "John Doe", age: 30)
}
}
Представление отвечает за отображение данных и взаимодействие с пользователем. В SwiftUI представление использует
@StateObject или @ObservedObject для наблюдения за изменениями данных в ViewModel. import SwiftUI
struct UserView: View {
@StateObject var viewModel = UserViewModel()
var body: some View {
VStack {
if let user = viewModel.user {
Text("Name: \(user.name)")
Text("Age: \(user.age)")
} else {
Text("Loading...")
}
}
.onAppear {
viewModel.fetchUser()
}
}
}
struct UserView_Previews: PreviewProvider {
static var previews: some View {
UserView()
}
}
User.swift
struct User {
let name: String
let age: Int
}UserViewModel.swift
import Combine
class UserViewModel: ObservableObject {
@Published var user: User?
func fetchUser() {
// Логика получения данных пользователя (например, из сети или базы данных)
self.user = User(name: "John Doe", age: 30)
}
}
UserView.swift
import SwiftUI
struct UserView: View {
@StateObject var viewModel = UserViewModel()
var body: some View {
VStack {
if let user = viewModel.user {
Text("Name: \(user.name)")
Text("Age: \(user.age)")
} else {
Text("Loading...")
}
}
.onAppear {
viewModel.fetchUser()
}
}
}
struct UserView_Previews: PreviewProvider {
static var previews: some View {
UserView()
}
}
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1