Swift - notes
Autoclosure
Lazy evaluation of the function's arguments. Instead of eager calculation of values, the clousure is passed, and executed only when needed.
func test(_ closure: @autoclosure() -> Bool) {
<#Code#>
}
test(8==9)
Destructuring Assignment
let (name, surname) = ("Artur", "Gurgul")
Checking if value is in range
let i = 101
if case 100...101 = i {
<#Code#>
}
if (100...101).contains(i) {
<#Code#>
}
Accessing tuple values
("value1", "value2").0
Pattern matching
enum Example {
case first(String)
case secund(String)
}
let example: Example = .first("test")
switch example {
case .first(let value), .secund(let value):
print(value)
}
if case let .first(value) = example {
print(value)
}
print odd numbers
for i in 1...100 where i%2 != 0 {
}
Blurable view in UIKit
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)
}
}
extension BackgroundView: Blurable {}
Difference between @ObservedObject
, @State
, and @EnvironmentObject
- Use
@State
for simple properties that belong to a single view. They should usually be markedprivate
.- 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.” :
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
View modifier
The notification we'll send when a shake gesture happens.
extension UIDevice {
static let deviceDidShakeNotification = Notification
.Name(rawValue: "deviceDidShakeNotification")
}
Override the default behavior of shake gestures to send our notification instead.
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.
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.
extension View {
func onShake(perform action: @escaping () -> Void) -> some View {
self.modifier(DeviceShakeViewModifier(action: action))
}
}
An example view that responds to being shaken
struct ContentView: View {
var body: some View {
Text("Shake me!")
.onShake {
print("Device shaken!")
}
}
}
Swizzling
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
class Car {
static var start: Int {
return 100
}
class var stop: Int {
return 0
}
}
class Student: Person {
// Not allowed
// override static var start: Int {
// return 150
// }
override class var stop: Int {
return 5
}
}
Always Publisher example
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)
}
}
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
}
}
}
extension Always.Subscription: Cancellable {
func cancel() {
subscriber = nil
}
}
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
let publisher = PassthroughSubject<Int, Never>()
publisher.send(1)
let buffered = publisher.buffer(size: 4, prefetch: .keepFull, whenFull: .dropOldest)
Combine publishers
let publisher1 = [1,2].publisher
let publisher2 = [3,4].publisher
Emits first and then second
let combinedPublisher = Publishers
.Concatenate(prefix: publisher1.eraseToAnyPublisher(),
suffix: publisher2.eraseToAnyPublisher())
Combine without ordering
let combinedPublisher = Publishers.Merge(publisher1.eraseToAnyPublisher(),
publisher2.eraseToAnyPublisher())
Other operators worth to look at
Zip
- pair the emitted object, like a zip in the jacketCombineLatest
- Whenpublisher1
andpublisher2
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
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
let timer = Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
var counter = 0
subscriber = timer
.map { _ in counter += 1 }
.sink { _ in
if counter >= 5 {
timer.upstream.connect().cancel()
}
}
It will work similar to this
subscriber = timer.prefix(5)
Assign
class Dog {
var name: String = ""
}
let dog = Dog()
let publisher = Just("Snow")
publisher.assign(to:/.name, on: dog)
class MyModel: ObservableObject {
@Published var lastUpdated: Date = Date()
init() {
Timer
.publish(every: 1.0, on: .main, in: .common)
.autoconnect()
.assign(to: &$lastUpdated)
}
}
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:
@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
class SomeClass: NSObject {
@objc dynamic var name = "Name"
}
object.value(forKey: "name") as String
object.setValue("New name", forKey: "name")
Create map
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
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
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
func add(a: Int, b: Int, c: Int) -> Int {
a + b + c
}
let adding = curry(add)
let adding5 = uncurry(adding(5))
or
let adding5 = currying(5, add)
then
print(adding5(6, 7))
Check availability
https://www.avanderlee.com/swift/available-deprecated-renamed/
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")
}
guard #available(iOS 15, *) else {
print("Returning if iOS 14 or lower")
return
}
print("This code only runs on iOS 15 and up")
@available(iOS 14, *)
final class NewAppIntroduction {
// ..
}
@available(iOS, deprecated: 12, obsoleted: 13, message: "We no longer show an app introduction on iOS 14 and up")
@available(*, unavailable, renamed: "launchOnboarding")
Mapping
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
public typealias Point<T: Numeric> = (x: T, y: T)
public typealias MyResult<T> = Result<T, Error>
async/await