init
This commit is contained in:
commit
acc61e858b
18 changed files with 5068 additions and 0 deletions
658
ios/ios-architecture-patterns.md
Normal file
658
ios/ios-architecture-patterns.md
Normal 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 → 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
30
ios/modularization.md
Normal 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
571
ios/notes.md
Normal 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 you’re 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**.
|
||||
>
|
||||
> Let’s 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, it’ll 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
68
ios/recipies.md
Normal 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
418
ios/threading.md
Normal 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 you’d 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 → `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)
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue