This commit is contained in:
Artur Gurgul 2025-08-03 16:27:25 +02:00
commit acc61e858b
18 changed files with 5068 additions and 0 deletions

View file

@ -0,0 +1,658 @@
---
layout: default
title: iOS Architecture Patterns
categories: swift
---
### Remove story board dependency
1. Remove `Main.storyboard` file
1. Remove storyboard reference from `Info.plist` → In Scene Configuration find `Storyboard Name` and delete it
1. Go to build settings and remove `UIKit MainStoryboard File Base Name` field
1. Create a window in Scene Delegate
```swift
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: scene)
window.rootViewController = ViewController()
window.makeKeyAndVisible()
self.window = window
}
```
## Dependency injection
<!-- https://www.swiftbysundell.com/tips/testing-code-that-uses-static-apis/ -->
[Explanation of the code under this link](https://www.avanderlee.com/swift/dependency-injection/)
```swift
public protocol InjectionKey {
associatedtype Value
static var currentValue: Self.Value { get set }
}
```
```swift
struct InjectedValues {
private static var current = InjectedValues()
static subscript<K>(key: K.Type) -> K.Value where K : InjectionKey {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedValues, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
```
```swift
@propertyWrapper
struct Injected<T> {
private let keyPath: WritableKeyPath<InjectedValues, T>
var wrappedValue: T {
get { InjectedValues[keyPath] }
set { InjectedValues[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedValues, T>) {
self.keyPath = keyPath
}
}
```
#### Define dependency
```swift
private struct UsersRepositoryKey: InjectionKey {
static var currentValue: AnyUsersRepository = UsersRepository()
}
extension InjectedValues {
var usersRepository: AnyUsersRepository {
get { Self[UsersRepositoryKey.self] }
set { Self[UsersRepositoryKey.self] = newValue }
}
}
protocol AnyUsersRepository {
func getUsers(_ result: @escaping (Result<[User], Error>)->Void)
}
class UsersRepository: AnyUsersRepository {
func getUsers(_ result: @escaping (Result<[User], Error>)->Void) {
<#Implementation#>
}
}
```
```swift
@Injected(\.usersRepository) var usersRepository: AnyUsersRepository
```
```swift
InjectedValues[\.usersRepository] = MockedUsersRepository()
```
## Model-View-Controller
#### Clasic version
- `View` and `Model` are linked together, so reusability is reduced.
- Note: Views in iOS apps are quite often reusable.
<p>
{% svg ../svgs/classic-mvc.svg class="center-image" %}
</p>
#### Apple version
<p>
{% svg ../svgs/apple-mvc.svg class="center-image" %}
</p>
**Model** responsibilities:
- Business logic
- Accessing and manipulating data
- Persistence
- Communication/Networking
- Parsing
- Extensions and helper classes
- Communication with models
Note: The `Model` must not communicate directly with the `View`. The `Controller` is the link between those
**View** responsibilities:
- Animations, drawings (`UIView`, `CoreAnimation`, `CoreGraphics`)
- Show data that controller sends
- Might receive user input
**Controller** responsibilities:
- Exchange data between `View` and `Model`
- Receive user actions and interruptions or signals from the outside the app
- Handles the view life cycle
#### Advantages
- Simple and usually less code
- Fast development for simple apps
#### Disadvantages
- Controllers coupled views
- Massive `ViewController`s
#### Communication between components
- [Delegation](https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html) pattern
- [Target-Action](https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html) pattern
- [Observer](https://developer.apple.com/documentation/foundation/nsnotificationcenter) pattern with `NSNotificationCenter`
- [Observer](https://developer.apple.com/documentation/swift/using-key-value-observing-in-swift) pattern with `KVO`
## Model-View-Presenter
In this design pattern View is implemented with classes `UIView` and `UIViewController`. The `UIViewController` has less responsibilities which are limited to:
- Routing/Coordination
- Navigation
- Passing informations via a delegation pattern
#### View
```swift
class ExampleController: UIViewController {
private let exampleView = ExampleView()
override func loadView() {
super.loadView()
setup()
}
private func setup() {
let presenter = ExamplePresenter(exampleView)
exampleView.presenter = presenter
exampleView.setupView()
self.view = exampleView
}
}
```
#### Presenter
```swift
protocol ExampleViewDelegate {
func updateView()
}
class ExamplePresenter {
private weak var exampleView: ExampleViewDelegate?
init(_ exampleView: ExampleViewDelegate) {
self.exampleView = exampleView
}
}
```
#### Advantages
- Easier to test business logic
- Better separation of responsibilities
#### Disadvantages
- Usually not a better choice for smaller projects
- Presenters might become massive
- Controllers still handle navigation. Possible solutions &rarr; extend the pattern with Router or Coordinator.
#### Common layers
- Data access layer: `CRUD` operations facilitated with `CoreData` `Realm` etc.
- Services: Classes that interacts with database entities, like retrieve data, transform them into objects.
- Extensions/utils
## Model-View-ViewModel
`ViewModel` has no references to the view.
<p>
{% svg ../svgs/mvvm.svg class="center-image" %}
</p>
Binding is done using: `Combine Framework`, `RxSwift`, `Bond` or `KVO` or using delegation pattern
* **`Model`** does same things as in `MVP` and `MVC`.
* **`View`** also is similar, but binds with `ViewModel`
* **`ViewModel`** keeps updated state of the view, and process data for i
#### Advantages
- Better reparation of responsibilities
- Better testability, without needing to take into account the views
#### Disadvantages
- Might be slower and introduce dependency on external libraries
- Harder to learn and can become complex
#### **Extension with Coordinator MVVM-C**
Role of `Coordinator` is to manage navigation flow.
```swift
protocol Coordinator {
var navigationController: UINavigationController { get set }
func start()
}
```
and an example implementation
```swift
class ExampleCoordinator: Coordinator {
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = UINavigationController
}
func start() {
let viewModel = ExampleViewModel(someService: SomeService(),
coordinator: self)
navigationController.pushViewController(ExampleController(viewModel),
animated: true)
}
func showList(_ list: ExampleListModel) {
let listCoordinator = ListCoordinator(navigationController: navigationController
list: list)
listCoordinator.start()
}
}
```
In the book I am reading the author created an `ExampleCoordinatorProtocol` with a `func showList(_ list: ExampleListModel)` where the `ExampleCoordinator` implemented it. I think it does not make any sense, however if we might want to inject the coordinator then we might want to relay on an abstraction.
```swift
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: scene)
let navigationController = UINavigationController()
exampleCoordinator = ExampleCoordinator(navigationController: navigationController)
exampleCoordinator?.start()
window.rootViewController = navigationController
window.makeKeyAndVisible()
self.window = window
}
```
## VIPER
<p>
{% svg ../svgs/viper-ownership.svg class="center-image" %}
</p>
#### View
It includes `UIViewController`
- Made only to preserve elements like Buttons Labels
- It sends informations to presenters, and receive messages what to show and knows how
#### Interactor
- Receives informations form databases, servers etc.
- The book says that the Interactor receive actions from presenter, and returns the result via Delegation Pattern.
- The interactor never sends entities to the Presenter
#### Presenter
- Is in the centre and serves as a link
- Process events from the view and requests data from the Interctor. It receives that as primitives, never Entities.
- it handles navigation to the other screens using the Router
#### Entity
- Simple models usually data structures
- They can only be used by the Interactor
#### Router
- Creates screens
- Handles navigation, but itself does not know where to go to.
- _The book says it is the owner of the `UINavigationController` and UIViewController, but it is contrary to other parts of the book, so I do not know_
- Similar to `Coordinator` form MVVM-C
<!--
https://github.com/ochococo/Design-Patterns-In-Swift
https://nalexn.github.io/clean-architecture-swiftui/
https://medium.com/@vladislavshkodich/architectures-comparing-for-swiftui-6351f1fb3605
-->
**`Entity.swift`**
```swift
struct User: Codable {
let name: String
}
```
**`Interactor.swift`**
```swift
enum FetchError: Error {
case failed
}
protocol AnyInteractor {
var presenter: AnyPresenter? { get set }
func getUsers()
}
class UserInteractor: AnyInteractor {
@Injected(\.usersRepository) var usersRepository: AnyUsersRepository
var presenter: AnyPresenter?
func getUsers() {
usersRepository.getUsers { [weak self] in self?.presenter?.interactorDidFetchUsers(with: $0) }
}
}
```
**`Presenter.swift`**
```swift
protocol AnyPresenter {
var router: AnyRouter? { get set }
var interactor: AnyInteractor? { get set }
var view: AnyView? { get set }
func interactorDidFetchUsers(with result: Result<[User], Error>)
}
class UserPresenter: AnyPresenter {
func interactorDidFetchUsers(with result: Result<[User], Error>) {
switch result {
case let .success(users):
view?.update(with: users)
case let .failure(error):
view?.update(with: error.localizedDescription)
}
}
var router: AnyRouter?
var interactor: AnyInteractor? {
didSet {
interactor?.getUsers()
}
}
var view: AnyView?
}
```
**`Router.swift`**
```swift
typealias EntryPoint = AnyView & UIViewController
protocol AnyRouter {
var entry: EntryPoint? { get }
static func start() -> AnyRouter
}
class UserRouter: AnyRouter {
var entry: EntryPoint?
static func start() -> AnyRouter {
let router = UserRouter()
var view: AnyView = UserViewController()
var presenter: AnyPresenter = UserPresenter()
var interactor: AnyInteractor = UserInteractor()
view.presenter = presenter
interactor.presenter = presenter
presenter.router = router
presenter.view = view
presenter.interactor = interactor
router.entry = view as? EntryPoint
return router
}
}
//There are a few retain cycles with view, presenter, router and interactor. One option you can do is to make those protocols conforms to AnyObject, and mark these references as "weak":
//1. router's ref to presenter
//2. router's ref to view
//3. presenter's ref to view
//4. interactor's ref to presenter
```
**`View.swift`**
```swift
protocol AnyView {
var presenter: AnyPresenter? { get set }
func update(with users: [User])
func update(with error: String)
}
class UserViewController: UIViewController, AnyView {
var presenter: AnyPresenter?
private let tableView: UITableView = {
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.isHidden = true
return tableView
}()
var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
}
func update(with users: [User]) {
DispatchQueue.main.async {
self.users = users
self.tableView.reloadData()
self.tableView.isHidden = false
}
}
func update(with error: String) {
}
}
extension UserViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = users[indexPath.row].name
return cell
}
}
```
<!--
RIBs
https://github.com/uber/RIBs
https://medium.com/swlh/ios-architecture-exploring-ribs-3db765284fd8
https://github.com/uber/RIBs/wiki
-->
<!--
redux
https://medium.com/mackmobile/getting-started-with-redux-in-swift-54e00f323e2b
-->
<!--
# Factory
```swift
protocol ImageReader {
func getDecodeImage() -> DecodedImage
}
class DecodedImage {
private var image: String
init(image: String) {
self.image = image
}
var description: String {
"\(image): is decoded"
}
}
class GifReader: ImageReader {
private var decodedImage: DecodedImage
init(image: String) {
self.decodedImage = DecodedImage(image: image)
}
func getDecodeImage() -> DecodedImage {
decodedImage
}
}
class JpegReader: ImageReader {
private var decodedImage: DecodedImage
init(image: String) {
decodedImage = DecodedImage(image: image)
}
func getDecodeImage() -> DecodedImage {
decodedImage
}
}
func runFactoryExample() {
let reader: ImageReader
let format = "gif"
let image = "example image"
switch format {
case "gif":
reader = GifReader(image: image)
default:
reader = JpegReader(image: image)
}
let decodedImage = reader.getDecodeImage()
print(decodedImage.description)
}
```
```swift
protocol Observer<ValueType> {
associatedtype ValueType
func update(value: ValueType)
}
struct Subject<T> {
private var observers: [(T) -> Void] = []
mutating func attach<O: Observer>(observer: O) where O.ValueType == T {
observers.append { observer.update(value: $0) }
}
func notyfi(value: T) {
for observer in observers {
observer(value)
}
}
}
class ConcreteObserver: Observer {
func update(value: String) {
print("received: \(value)")
}
}
func runObserverExample() {
var subject = Subject<String>()
let observer1 = ConcreteObserver()
subject.attach(observer: observer1)
let observer2 = ConcreteObserver()
subject.attach(observer: observer2)
subject.notyfi(value: "some string")
}
// Version with more modern syntax
/*
protocol Observer<ValueType> {
associatedtype ValueType
func update(value: ValueType)
}
struct Subject<T> {
private var observers = Array<any Observer<T>>()
mutating func attach(observer: any Observer<T>) {
observers.append(observer)
}
func notify(value: T) {
for observer in observers {
observer.update(value: value)
}
}
}
*/
```
-->

30
ios/modularization.md Normal file
View file

@ -0,0 +1,30 @@
---
layout: default
title: Modularization of an iOS app
categories: swift
---
### Creating shared product
1. add submodule to your product `git submodule add git@github.com:artur-gurgul-pro/sharepack.git Sharepack`
1. Extract shared code to multi-module package. See `Sharepack`
2. Add local package
TBD
Initialisation of a library
```bash
mkdir MyDeps && cd MyDeps
swift package init --type library
```
```bash
swift package generate-xcodeproj
```
```bash
xcodebuild -resolvePackageDependencies
```

571
ios/notes.md Normal file
View file

@ -0,0 +1,571 @@
---
layout: default
title: Swift - notes
categories: swift
---
#### Autoclosure
Lazy evaluation of the function's arguments. Instead of eager calculation of values, the clousure is passed, and executed only when needed.
```swift
func test(_ closure: @autoclosure() -> Bool) {
<#Code#>
}
test(8==9)
```
#### Destructuring Assignment
```swift
let (name, surname) = ("Artur", "Gurgul")
```
#### Checking if value is in range
```swift
let i = 101
if case 100...101 = i {
<#Code#>
}
if (100...101).contains(i) {
<#Code#>
}
```
#### Accessing tuple values
```swift
("value1", "value2").0
```
#### Pattern matching
```swift
enum Example {
case first(String)
case secund(String)
}
let example: Example = .first("test")
```
```swift
switch example {
case .first(let value), .secund(let value):
print(value)
}
if case let .first(value) = example {
print(value)
}
```
_print odd numbers_
```swift
for i in 1...100 where i%2 != 0 {
}
```
#### Blurable view in UIKit
```swift
extension Blurable where Self: UIView {
    func addBlur(_ alpha: CGFloat = 0.5) {
        let effect = UIBlurEffect(style: .prominent)
        let effectView = UIVisualEffectView(effect: effect)
        effectView.frame = self.bounds
        effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        effectView.alpha = alpha
        self.addSubview(effectView)
    }
}
```
```swift
extension BackgroundView: Blurable {}
```
## Difference between `@ObservedObject`, `@State`, and `@EnvironmentObject`
[https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject](https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject)
>- Use `@State` for simple properties that belong to a single view. They should usually be marked `private`.
>- Use `@ObservedObject` for complex properties that might belong to several views. Most times youre using a reference type you should be using `@ObservedObject` for it.
>- Use `@StateObject` once for each observable object you use, in whichever part of your code is responsible for creating it.
>- Use `@EnvironmentObject` for properties that were created elsewhere in the app, such as shared data.
### Cold vs hot observables
From: Anton Moiseev's Book [“Angular Development with Typescript, Second Edition.”](https://www.manning.com/books/angular-development-with-typescript-second-edition) :
> **Hot and cold observables**
>
> There are **two** types of **observables**: hot and cold. The main difference is that a **cold observable** **creates** a **data producer** for **each subscriber**, whereas a **hot observable creates** a **data producer first**, and **each subscriber** gets the **data** from **one producer**, **starting** from **the moment of** **subscription**.
>
> Lets compare watching a **movie** on **Netflix** to going into a **movie theater**. Think of yourself as an **observer**. Anyone who decides to watch Mission: Impossible on Netflix will get the entire movie, regardless of when they hit the play button. Netflix creates a new **producer** to stream a movie just for you. This is a **cold observable**.
>
> If you go to a movie theater and the showtime is 4 p.m., the producer is created at 4 p.m., and the streaming begins. If some people (**subscribers**) are late to the show, they miss the beginning of the movie and can only watch it starting from the moment of arrival. This is a **hot observable**.
>
> A **cold observable** starts producing data when some code invokes a **subscribe()** function on it. For example, your app may declare an observable providing a URL on the server to get certain products. The request will be made only when you subscribe to it. If another script makes the same request to the server, itll get the same set of data.
>
> A **hot observable** produces data even if no subscribers are interested in the data. For example, an accelerometer in your smartphone produces data about the position of your device, even if no app subscribes to this data. A server can produce the latest stock prices even if no user is interested in this stock.
# Interesting snippets
<!-- https://swiftbysundell.com/articles/swiftui-views-versus-modifiers/ -->
# View modifier
The notification we'll send when a shake gesture happens.
```swift
extension UIDevice {
static let deviceDidShakeNotification = Notification
.Name(rawValue: "deviceDidShakeNotification")
}
```
Override the default behavior of shake gestures to send our notification instead.
```swift
extension UIWindow {
open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
NotificationCenter
.default
.post(name: UIDevice.deviceDidShakeNotification, object: nil)
}
}
}
```
A view modifier that detects shaking and calls a function of our choosing.
```swift
struct DeviceShakeViewModifier: ViewModifier {
let action: () -> Void
func body(content: Content) -> some View {
content
.onAppear()
.onReceive(NotificationCenter
.default
.publisher(for: UIDevice.deviceDidShakeNotification)) { _ in
action()
}
}
}
```
A View extension to make the modifier easier to use.
```swift
extension View {
func onShake(perform action: @escaping () -> Void) -> some View {
self.modifier(DeviceShakeViewModifier(action: action))
}
}
```
An example view that responds to being shaken
```swift
struct ContentView: View {
var body: some View {
Text("Shake me!")
.onShake {
print("Device shaken!")
}
}
}
```
# Swizzling
```swift
extension UIViewController {
@objc dynamic func newViewDidAppear(animated: Bool) {
viewDidAppear(animated)
print("View appeared")
}
}
private let swizzling: Void = {
let originalMethod = class_getInstanceMethod(UIViewController.self,
#selector(UIViewController.viewDidLoad))
let swizzledMethod = class_getInstanceMethod(UIViewController.self,
#selector(UIViewController.newViewDidAppear))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
```
# class vs static values
```swift
class Car {
static var start: Int {
return 100
}
class var stop: Int {
return 0
}
}
```
```swift
class Student: Person {
// Not allowed
// override static var start: Int {
// return 150
// }
override class var stop: Int {
return 5
}
}
```
# Always Publisher example
```swift
public struct Always<Output>: Publisher {
public typealias Failure = Never
public let output: Output
public init(_ output: Output) {
self.output = output
}
public func receive<S: Subscriber>(subscriber: S)
where S.Input == Output, S.Failure == Failure {
let subscription = Subscription(output: output,
subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
```
```swift
private extension Always {
final class Subscription<S: Subscriber>
where S.Input == Output, S.Failure == Failure {
private let output: Output
private var subscriber: S?
init(output: Output, subscriber: S) {
self.output = output
self.subscriber = subscriber
}
}
}
```
```swift
extension Always.Subscription: Cancellable {
func cancel() {
subscriber = nil
}
}
```
```swift
extension Always.Subscription: Subscription {
func request(_ demand: Subscribers.Demand) {
var demand = demand
while let subscriber = subscriber, demand > 0 {
demand -= 1
demand += subscriber.receive(output)
}
}
}
```
# Buffered publisher
```swift
let publisher = PassthroughSubject<Int, Never>()
publisher.send(1)
let buffered = publisher.buffer(size: 4, prefetch: .keepFull, whenFull: .dropOldest)
```
# Combine publishers
```swift
let publisher1 = [1,2].publisher
let publisher2 = [3,4].publisher
```
Emits first and then second
```swift
let combinedPublisher = Publishers
.Concatenate(prefix: publisher1.eraseToAnyPublisher(),
suffix: publisher2.eraseToAnyPublisher())
```
Combine without ordering
```swift
let combinedPublisher = Publishers.Merge(publisher1.eraseToAnyPublisher(),
publisher2.eraseToAnyPublisher())
```
**_Other operators worth to look at_**
- `Zip` - pair the emitted object, like a zip in the jacket
- [`CombineLatest`](https://developer.apple.com/documentation/combine/publisher/combinelatest%28_:%29) - When `publisher1` and `publisher2` emitted some event then the latest values from each are taken and reemitted. From now on each change from either publisher is passed down.
#### Example of zip
```swift
let numbers = [1, 2, 3, 4].publisher
let twos = sequence(first: 2,
next: {_ in 2}).publisher
numbers
.zip(twos)
.map { pow(Decimal($0), $1) }
.sink(receiveValue: { p in
print(p)
}).store(in: &cancellables)
```
#### Cancelling a publisher
```swift
let timer = Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
```
```swift
var counter = 0
subscriber = timer
.map { _ in counter += 1 }
.sink { _ in
if counter >= 5 {
timer.upstream.connect().cancel()
}
}
```
It will work similar to this
```swift
subscriber = timer.prefix(5)
```
#### Assign
```swift
class Dog {
var name: String = ""
}
let dog = Dog()
let publisher = Just("Snow")
publisher.assign(to:/.name, on: dog)
```
```swift
class MyModel: ObservableObject {
@Published var lastUpdated: Date = Date()
init() {
Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.assign(to: &$lastUpdated)
}
}
```
```swift
class MyModel: ObservableObject {
@Published var id: Int = 0
}
let model = MyModel()
Just(100).assign(to: &model.$id)
```
Here's an example of using the `@dynamicMemberLookup` attribute in Swift:
```swift
@dynamicMemberLookup
struct DynamicStruct {
subscript(dynamicMember member: String) -> String {
return "You accessed dynamic member '\(member)'"
}
}
let dynamicStruct = DynamicStruct()
let result = dynamicStruct.someDynamicMember
print(result) // Output: "You accessed dynamic member 'someDynamicMember'"
```
Key Value Coding
```swift
class SomeClass: NSObject {
@objc dynamic var name = "Name"
}
```
```swift
object.value(forKey: "name") as String
```
```swift
object.setValue("New name", forKey: "name")
```
### Create map
```Swift
extension Sequence {
func dictionay<T>(keyPath: KeyPath<Element, T>) -> [T: Element] {
var dictionary = [T: Element]()
for elemement in self {
let key = elemement[keyPath: keyPath]
dictionary[key] = elemement
}
return dictionary
}
}
```
### Compact
```swift
extension Array {
public func compact<T>() -> [T] where Element == Optional<T> {
compactMap { $0 }
}
}
```
### Swift - currying
[https://thoughtbot.com/blog/introduction-to-function-currying-in-swift](https://thoughtbot.com/blog/introduction-to-function-currying-in-swift)
```swift
func curry<A, B, C, D>(_ f: @escaping (A, B, C) -> D) -> (A) -> (B) -> (C) -> D {
{ a in { b in { c in f(a, b, c) } } }
}
func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
{ a in { b in f(a, b) } }
}
func uncurry<A, B, C>(_ f: @escaping (A) -> (B) -> C) -> (A, B) -> C {
{ f($0)($1) }
}
func uncurry<A, B, C, D>(_ f: @escaping (A) -> (B) -> (C) -> D) -> (A, B, C) -> D {
{ f($0)($1)($2) }
}
func currying<A, B, C>(_ a: A, _ f: @escaping (A, B) -> C) -> (B) -> C {
{ (curry(f))(a)($0) }
}
func currying<A, B, C, D>(_ a: A, _ f: @escaping (A, B, C) -> D) -> (B, C) -> D {
{ (curry(f))(a)($0)($1) }
}
```
#### Example of usage
```swift
func add(a: Int, b: Int, c: Int) -> Int {
a + b + c
}
```
```swift
let adding = curry(add)
let adding5 = uncurry(adding(5))
```
or
```swift
let adding5 = currying(5, add)
```
then
```swift
print(adding5(6, 7))
```
## Check availability
[https://www.avanderlee.com/swift/available-deprecated-renamed/](https://www.avanderlee.com/swift/available-deprecated-renamed/)
```swift
if #available(iOS 15, *) {
print("This code only runs on iOS 15 and up")
} else {
print("This code only runs on iOS 14 and lower")
}
```
```swift
guard #available(iOS 15, *) else {
print("Returning if iOS 14 or lower")
return
}
print("This code only runs on iOS 15 and up")
```
```swift
@available(iOS 14, *)
final class NewAppIntroduction {
// ..
}
```
```swift
@available(iOS, deprecated: 12, obsoleted: 13, message: "We no longer show an app introduction on iOS 14 and up")
@available(*, unavailable, renamed: "launchOnboarding")
```
Mapping
```swift
func maping<T>(keyPath: KeyPath<Element, T>) -> [T: Element] {
var dictionary = [T: Element]()
for elemement in self {
let key = elemement[keyPath: keyPath]
dictionary[key] = elemement
}
return dictionary
}
```
### Type aliases
```swift
public typealias Point<T: Numeric> = (x: T, y: T)
public typealias MyResult<T> = Result<T, Error>
```
async/await

68
ios/recipies.md Normal file
View file

@ -0,0 +1,68 @@
---
layout: default
title: Recipies
categories: swift
---
```swift
extension UIFont {
var bold: UIFont {
guard let newDescriptor = fontDescriptor.withSymbolicTraits(.traitBold) else {
return self
}
return UIFont(descriptor: newDescriptor, size: pointSize)
}
}
```
```swift
public struct FontStyle: ViewModifier {
let isBold: Bool
private let font: Font
public init(isBold: Bool = false) {
self.isBold = isBold
self.font = self.font.bold
}
public func body(content: Self.Content) -> some View {
content.font(font)
}
}
extension View {
public func fontStyle(isBold: Bool = false) -> some View {
modifier(FontStyle(isBold: isBold))
}
}
```
## Testing
```swift
class ExampleXCTestCase: XCTestCase {
let app = XCUIApplication()
override func tearDown() {
app.terminate()
}
override func tearDownWithError() throws {
app.terminate()
}
}
```
In tests (it has to be called before `app.launch()`)
```swift
launchEnvironment["key"] = "value"
```
in the app:
```swift
ProcessInfo.processInfo.environment["key"]
```

418
ios/threading.md Normal file
View file

@ -0,0 +1,418 @@
---
layout: default
title: Threading
categories: swift
---
Chunk of jobs that can be performed on the separate thread are arranged into:
- `Closures` or `Functions`/`Methods` and send to `DispatchQueue`
- Classes and send as objects to `OperationQueue`. The queue can perform `Closure` as well
#### Kinds of jobs
* Computation intensive jobs: When the thread uses entire processing capability of `CPU`. The reasonable maximum number of the threads is the number of CPU cores.
* I/O intensive jobs: In that case we can trigger more threads than we have CPU cores. An optimal of threads is `Threads` = `Cores` / (1-`Blocking Factor`).
# [GCD](https://github.com/apple/swift-corelibs-libdispatch) (Grand Central Dispatch)
Creating queue. Note without parameter `attributes` we will get serial queue
```swift
let queue = DispatchQueue(label: "important.job",
qos: .default,
attributes: .concurrent)
```
#### Examples of sending jobs to queues:
Send to the queue and proceed with the execution in the current context
```swift
queue.async {
<#Code of the job#>
}
```
Same as above, but on main thread where UI operations should be done
```swift
DispatchQueue.main.async {
<#Code of the job#>
}
```
Perform on the global queue
```swift
DispatchQueue.global(qos: .background).async {
<#Code of the job#>
}
```
Send block that will be executed when all jobs sent at this point are completed. Stop current thread will the queue is done with the sent block.
```swift
queue.sync(flags: [.barrier]) {
<#Code of the job#>
}
```
Perform job in the time interval
```swift
queue.schedule(after: .init(.now()), interval: .seconds(3)) {
<#Code of the job#>
}
```
Getting the label of the queue
```swift
extension DispatchQueue {
static var label: String {
return String(cString: __dispatch_queue_get_label(nil),
encoding: .utf8) ?? ""
}
}
```
### `DispatchGroup`
```swift
extension Sequence {
public func threadedMap<T>(_ mapper: @escaping (Element) -> T) -> [T] {
var output = [T]()
let group = DispatchGroup()
let queue = DispatchQueue(label: "queue-for--map-result", qos: .background)
for obj in self {
queue.async(group:group) {
output.append(mapper(obj))
}
}
group.wait()
return output
}
}
```
### Mutex
Performing max 3 tasks at the time
```swift
let semaphore = DispatchSemaphore(value: 3)
for _ in 0..<15 {
queue.async(qos: .background) {
semaphore.wait()
<#Code of the job#>
semaphore.signal()
}
}
```
### Locking and unlocking the thread
**NSLock**
Example of usage. The concurrent queue becomes sort of serial. One job at the time, but execution in random order.
```swift
let lock = NSLock()
for _ in 1...6 {
queue.async {
lock.lock()
<#Code of the job#>
lock.unlock()
}
}
```
**Mutex**
Same thing using `pthread` API
```swift
var mutex = pthread_mutex_t()
pthread_mutex_init(&mutex, nil)
for i in 1...6 {
queue.async {
pthread_mutex_lock(&mutex)
<#Code of the job#>
pthread_mutex_unlock(&mutex)
}
}
```
When you are done with the lock, you need to release it
```swift
pthread_mutex_destroy(&mutex)
```
# `NSOperationQueue`
```swift
let queue = OperationQueue()
queue.name = "Queue Name"
queue.maxConcurrentOperationCount = 1
```
```swift
class MyVeryImportantOperation: Operation {
override func main() {
if isCancelled { return }
// Some chunk of time consuming task
if isCancelled { return }
// Some another chunk of time consuming task
// and so on...
}
}
```
Sending jobs to the queue
```swift
let myOperation = MyVeryExpensiveOperation()
queue.addOperation(myOperation)
```
```swift
queue.addOperation {
<#Code of the job#>
}
```
Notes `NSOperationQueue` fit better when requires canceling, suspending a block and/or dependency management
# NSThread
```swift
let thread = Thread {
<#code#>
}  
```
```swift
let thread = Thread(target: self,
selector: #selector(jobMethod:),
object: argumentObject)
```
```swift
thread.start()
```
Async method
```swift
func doStuff() async {
<#code#>
}
```
# Span a thread in `NSObject` context
```swift
perform(#selector(job), on: .main, with: nil, waitUntilDone: false)
```
# async/await
Available from `Swift 5.5`. More info at [docs.swift.org](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/)
#### Await the task group
```swift
let movies = await withTaskGroup(of: Movie.self) { group in
var movies = [Movie]()
movies.reserveCapacity(2)
for no in 1...2 {
group.addTask {
return await apiClient.download(movie: no)
}
}
for await movie in group {
movies.append(movie)
}
return movies
}
```
**Order of execution**
```swift
print("Before task group")
await withTaskGroup(of: Void.self) { group in
for item in list {
group.addTask {
await doSomething()
print("Task completed")
}
}
print("For loop completed")
}
print("After task group")
```
```plain
 1 => print("Before task group")
 2 => print("For loop completed")
 3 => print("Task completed")
 4 => print("Task completed")
 5 => print("Task completed")
 6 => print("After task group")
```
#### `Actor`s
- Do not support inheritance
```swift
actor TestActor {
var property = 1
func update(no: Int) {
sleep(UInt32.random(in: 1...3))
property = no
}
}
```
```swift
let actor = TestActor()
await actor.update(no: 5)
```
#### Call synchronously and parallel
```swift
let firstMovie = await apiClient.download(movie: 1)
let secondMovie = await apiClient.download(movie: 2)
let movies = [firstMovie, secondMovie]
```
```swift
async let firstMovie = apiClient.download(movie: 1)
async let secondMovie = apiClient.download(movie: 2)
let photos = await [firstMovie, secondMovie]
```
#### Call the main thread
```swift
let t = Task { @MainActor in
print ("update UI")
return 5
}
await print(t.value)
```
#### Call `async` function from a regular method
```swift
func job(no: Int) async -> Int {
sleep(UInt32.random(in: 1...3))
return no
}
```
```swift
let result = Task {
await job(no: 5)
}
Task {
await print(result.value)
}
```
# pthread
This is an object that will be sent to the thread
```swift
class ThreadParameter {
let paramater = 1
}
```
This is a function that will be spanned on the background thread
```swift
func threadedFunction(pointer: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? {
var threadParameter = pointer.load(as: ThreadParameter.self)
<#Code of the job#>
return nil
}
```
Creating a `pthread`
```swift
var myThread: pthread_t? = nil
let threadParameter = ThreadParameter()
var pThreadParameter = UnsafeMutablePointer<ThreadParameter>.allocate(capacity:1)
pThreadParameter.pointee = threadParameter
let result = pthread_create(&myThread, nil, threadedFunction, pThreadParameter)
```
waiting for finishing the thread
```swift
if result == 0, let myThread {
pthread_join(myThread,nil)
}
```
# Span threads using `Combine` framework
**`subscribe(on:)`**
Quotes from: [https://trycombine.com/posts/subscribe-on-receive-on/](https://trycombine.com/posts/subscribe-on-receive-on/)
> `subscribe(on:)` sets the scheduler on which youd like the current subscription to be “managed” on. This operator sets the scheduler to use for creating the subscription, cancelling, and requesting input.
>
> ... `subscribe(on:)` sets the scheduler to subscribe the upstream on.
>
> A side effect of subscribing on the given scheduler is that `subscribe(on:)` also changes the scheduler for its downstream ....
Subscription is done once and calling second time the `subscribe(on: )` have no effect.
`subscribe(on: DispatchQueue.global(qos: .background))` queues on this call &rarr; `func request(_ demand: Subscribers.Demand)`
**`receive(on:)`**
> The correct operator to use to change the scheduler where downstream output is delivered is `receive(on:)`.
>
> On other words `receive(on:)` sets the scheduler where the downstream receives output on.
>
> You can use `receive(on:)` similarly to `subscribe(on:)`. You can use multiple `receive(on:)` operators and that will always change the downstream scheduler
# Usage of `RunLoop`
<!-- Diffrence between queue and runloop https://www.avanderlee.com/combine/runloop-main-vs-dispatchqueue-main/ -->
Doncumentation on [RunLoop](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html)
> The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is non. - Apple
# Measure a performance time
```swift
let startTime = CFAbsoluteTimeGetCurrent()
<#Code of the job#>
let endTime = CFAbsoluteTimeGetCurrent()
print("Duration:\(endTime - startTime) seconds")
```
#### Stop thread for some time
```swift
sleep(2)
```
```swift
try await Task.sleep(until: .now + .seconds(2), clock: .continuous)
```