Skip to content

Instantly share code, notes, and snippets.

@avii-7
Last active September 4, 2024 07:59
Show Gist options
  • Save avii-7/61b26a8e339aa646d25162d66730ec3b to your computer and use it in GitHub Desktop.
Save avii-7/61b26a8e339aa646d25162d66730ec3b to your computer and use it in GitHub Desktop.
Swift Learnings

Swift — “copy-on-write”

"copy on write" is an optimization technique that helps in managing memory more efficiently.

It is used primarly with value types such as Array, Dictionary and String to defer the actual copying of data until it is modified. This helps in minimizing unnecessary data copying and thus improve performance.

How "copy on write" works ?

When you assign or pass a value type to another variable or function. Swift doesn't immediately creates the copy of data. Instead it shares the same underlying storage until the copy is modified.

Note: When you have an array of custom structs, the array itself will benefit from COW.

Why Extension cannot contains stored properties ?

This design decision is based on few key reasons.

1. Memory Layout and Instance size:

The memory layout of a type is determined at complie time based on its properties.

Allowing stored properties in extension would complicate this process, as compiler would need to accounts additional properties that might be added in extension and recompile the type.

If extension could add stored properties, the compiler needs to dynamically adjust the memory layout of a type, which could lead to performance overhead.

2. Initilization Complexity

Initialization logic becomes more complex if stored properties are allowed in extensions. The complier needs to ensure that all the stored properties are properly initialized even those are added in extensions.

3. Definition

Extensions are intended to extend existing types with new functionality rather than altering the fundamental structure.


Observation:

  1. Frameworks provided by Apple like Foundation, UIKit, CoreGraphics etc is precomplied. They are dynamically linked at runtime rather than compiled being compiled with our source code.
  2. Memory layout of a type, including its size (calculated from properties) is determined at compile time.
    This means the exact memory structure is known and fixed when code is compiled.
    For the layout and size the struct and classes includes all the stored properites while classes also include additional meta data for object management. Enums layout depends upon cases and associated values.

Reference:

  1. ChatGPT
  2. Reddit

git command to remove existing file from remote repository

  git rm -r --cached .
  git add .
  git commit -m "fixed untracked files"

High Order Functions

High order functions are functions that can take other functions as a parameters or return functions as their results.

Commenly used high order functions:

  1. Map: The map function transforms each element of an array using a given function and return a new array containing the transformed elements.
let numbers = [1, 2, 3, 4, 5]
let squaredNumbers = numbers.map { $0 * $0 }
print(squaredNumbers) // Output: [1, 4, 9, 16, 25]
  1. Filter: The filter function creates a new array with elements that satisfy a given condition.
let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // Output: [2, 4, 6]
  1. Reduce: The reduce function combines all the elements of the array into single value, by applying combining closure.
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // Output: 15
  1. Compact Map: This Compact Map function transforms element of an array using a given closure and remove any resulting nil values.

High order functions can be chained together for even more powerful transformations.

Optionals in Swift

An enum type that represents either a wrapped value or absence of value.

It is a genric enum with two cases:

enum Optional<Wrapped>: ExpressibleByNilLiteral {
    case none
    case some(Wrapped)

    public init(_ some: Wrapped) {
        self = .some(some)
    }

    // from ExpressibleByNilLiteral
    public init(nilLiteral: ()) {
        self = .none
    }
}

ExpressibleByNilLiteral is a protocol, that helps the type to be initialized using nil value.

However, generally instead of using the .none case you would use nil to indicate the absence of a value.

Properties, methods, and subscripts can return an optional and unwrapping of an optional an be done in many ways like:

1. Force unwrap:

  • Force unwrapping either return the value if it exists or trigger a runtime error when value is nil.

  • If you are sure value won't be nil in your case, you can use force unwrap to avoid checking for nil's.

Example:

  1. URL(string:) returns Optional
  2. Int(string:) return Optional

2. If let and guard let

Both allow us to unwrap a value safely. In both cases statements will only be execute when optional contains a value instead of nil.

But guard let comes with slight difference. It helps you focus on happy path. guard let requires to exit the current scope optional contains nil value. Exit can be done using return, throw, break or continue keywords.

3. Nil Coelscing operator

It also helps in unwrapping a value and provide a default value when optional contains nil value.

4. Implicitly Unwrapped Optionals

It is not an unwrapping mechanism, but a way to prevent optional checking by alterting type.

It is written as:

let val: String!

May also contains value or be nil. But they don't need to be checked before they are used. If you try to use a value that contains nil your code will crash.

The usual reason is that there are some things we all know will start life as being nil, but will be non-nil by the time we need them and won’t be nil again.

Code to print CoreData database address

let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        debugPrint(path[0])

Output:

file:///Users/avii/Library/Developer/CoreSimulator/Devices/EDAF4AEF-50DE-4603-83F9-CA9BF1B5ECF8/data/Containers/Data/Application/8AD4AD1B-A429-4A9E-BED7-7F6EA2B77FA1/Documents/

Print array address

print(array.withUnsafeBufferPointer { String(reflecting: $0.baseAddress) })

Print Value type address (dout)

withUnsafePointer(to: &valueType) { print($0) }

Print Reference type address

func printReferenceTypeAddress(reference: AnyObject) {
    print(Unmanaged.passUnretained(reference).toOpaque())
}

rethrows keyword in Swift

Rethrows in swift allow forwarding a thrown error by a given function parameter. It is used a lot in methods like map, filter, reduce and foreach and helps the compiler to determine whether or not try prefix is needed.

How to use the rethrows keyword

The rethrow keyword used in functions that do not throw errors themselves but instead forward errors from their function parameters.

It also allows the compiler to ask for the try keyword only if given callback actually is throwing errors.

Take the following example of a rethrowing method taking a throwing callback:

extension Array {
    
    func customMap<T>(_ closure: (Element) throws -> T) rethrows -> [T] {
        var result: [T] = []
        
        for element in self {
            result.append(try closure(element))
        }
        
        return result
    }
}

If the callback we pass in doesn’t throw an error, we can call the method as follows:

let arr = [1, 2, 3]
let squaredArray = arr.customMap { $0 * $0 }

However, as soon as our callback is throwing error, the compiler require us use try for our rethrowing method.

Simulator

  1. Switch between light mode and dark mode: Shift ⇧ + Cmd ⌘ + A

Mac

  1. Spotlight: Cmd ⌘ + SpaceBar
  2. Switch windows of same app (i) Forward: Cmd ⌘ + ` (backtick) key. (ii) Backward: Cmd ⌘ + Shift ⇧ + ` (backtick) key.
  3. Emoji Popup: Ctrl + alt + space.
  4. Show hidden files: CMD + SHIFT + .

XCode

  1. Search File in Project: Cmd ⌘ + Shift ⇧ + O
  2. Search text in Project: Cmd ⌘ + Shift ⇧ + F
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment