Blog

  • Swift, Concurrency Programming guide

    ✍️ Note

    All the codes and contents are sourced from Apple’s official documentation. This post is for personal notes where I summarize the original contents to grasp the key concepts

    Reference

    What is Concurrency Programming?

    Concurrency is the notion of multiple things happening at the same time. Although operating systems like OS X and iOS are capable of running multiple programs in parallel, most of those programs run in the background and perform tasks that require little continuous processor time. It is the current foreground application that both captures the user’s attention and keeps the computer busy. If an application has a lot of work to do but keeps only a fraction of the available cores occupied, those extra processing resources are wasted.

    Both OS X and iOS adopt a more asynchronous approach to the execution of concurrent tasks than is traditionally found in thread-based systems and applications. 

    https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1

    Terms

    • The term thread is used to refer to a separate path of execution for code. The underlying implementation for threads in OS X is based on the POSIX threads API.
    • The term process is used to refer to a running executable, which can encompass multiple threads.
    • The term task is used to refer to the abstract concept of work that needs to be performed.

    Concurrency and Application design

    Although threads have been around for many years and continue to have their uses, they do not solve the general problem of executing multiple tasks in a scalable way. With threads, the burden of creating a scalable solution rests squarely on the shoulders of you, the developer. You have to decide how many threads to create and adjust that number dynamically as system conditions change. Another problem is that your application assumes most of the costs associated with creating and maintaining any threads it uses.

    Instead of relying on threads, OS X and iOS take an asynchronous design approach to solving the concurrency problem. Asynchronous functions have been present in operating systems for many years and are often used to initiate tasks that might take a long time, such as reading data from the disk. When called, an asynchronous function does some work behind the scenes to start a task running but returns before that task might actually be complete. Typically, this work involves acquiring a background thread, starting the desired task on that thread, and then sending a notification to the caller (usually through a callback function) when the task is done. In the past, if an asynchronous function did not exist for what you want to do, you would have to write your own asynchronous function and create your own threads. But now, OS X and iOS provide technologies to allow you to perform any task asynchronously without having to manage the threads yourself.

    https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ConcurrencyandApplicationDesign/ConcurrencyandApplicationDesign.html#//apple_ref/doc/uid/TP40008091-CH100-SW1

    Grand Central Dispatch

    One of the technologies for starting tasks asynchronously is Grand Central Dispatch (GCD). This technology takes the thread management code you would normally write in your own applications and moves that code down to the system level. All you have to do is define the tasks you want to execute and add them to an appropriate dispatch queue. GCD takes care of creating the needed threads and of scheduling your tasks to run on those threads. Because the thread management is now part of the system, GCD provides a holistic approach to task management and execution, providing better efficiency than traditional threads.

    Apple

    screenshot 2024 03 12 at 10.37.39e280afpm

    OperationQueue

    Operation queues are Objective-C objects that act very much like dispatch queues. You define the tasks you want to execute and then add them to an operation queue, which handles the scheduling and execution of those tasks. Like GCD, operation queues handle all of the thread management for you, ensuring that tasks are executed as quickly and as efficiently as possible on the system.

    An operation queue is the Cocoa equivalent of a concurrent dispatch queue and is implemented by the NSOperationQueue class. Whereas dispatch queues always execute tasks in first-in, first-out order, operation queues take other factors into account when determining the execution order of tasks. Primary among these factors is whether a given task depends on the completion of other tasks. You configure dependencies when defining your tasks and can use them to create complex execution-order graphs for your tasks.

    The tasks you submit to an operation queue must be instances of the NSOperation class. An operation object is an Objective-C object that encapsulates the work you want to perform and any data needed to perform it. Because the NSOperation class is essentially an abstract base class, you typically define custom subclasses to perform your tasks. However, the Foundation framework does include some concrete subclasses that you can create and use as is to perform tasks.

    Operation objects generate key-value observing (KVO) notifications, which can be a useful way of monitoring the progress of your task. Although operation queues always execute operations concurrently, you can use dependencies to ensure they are executed serially when needed.

    For more information about how to use operation queues, and how to define custom operation objects, see Operation Queues.

    Apple

    DispatchQueue

    Dispatch queues are a C-based mechanism for executing custom tasks. A dispatch queue executes tasks either serially or concurrently but always in a first-in, first-out order. (In other words, a dispatch queue always dequeues and starts tasks in the same order in which they were added to the queue.) A serial dispatch queue runs only one task at a time, waiting until that task is complete before dequeuing and starting a new one. By contrast, a concurrent dispatch queue starts as many tasks as it can without waiting for already started tasks to finish.

    Dispatch queues have other benefits:

    • They provide a straightforward and simple programming interface.
    • They offer automatic and holistic thread pool management.
    • They provide the speed of tuned assembly.
    • They are much more memory efficient (because thread stacks do not linger in application memory).
    • They do not trap to the kernel under load.
    • The asynchronous dispatching of tasks to a dispatch queue cannot deadlock the queue.
    • They scale gracefully under contention.
    • Serial dispatch queues offer a more efficient alternative to locks and other synchronization primitives.

    The tasks you submit to a dispatch queue must be encapsulated inside either a function or a block object. Block objects are a C language feature introduced in OS X v10.6 and iOS 4.0 that are similar to function pointers conceptually, but have some additional benefits. Instead of defining blocks in their own lexical scope, you typically define blocks inside another function or method so that they can access other variables from that function or method. Blocks can also be moved out of their original scope and copied onto the heap, which is what happens when you submit them to a dispatch queue. All of these semantics make it possible to implement very dynamic tasks with relatively little code.

    Dispatch queues are part of the Grand Central Dispatch technology and are part of the C runtime. For more information about using dispatch queues in your applications, see Dispatch Queues. For more information about blocks and their benefits, see Blocks Programming Topics.

    Apple

    Dispatch Sources

    Dispatch sources are a C-based mechanism for processing specific types of system events asynchronously. A dispatch source encapsulates information about a particular type of system event and submits a specific block object or function to a dispatch queue whenever that event occurs. You can use dispatch sources to monitor the following types of system events:

    • Timers
    • Signal handlers
    • Descriptor-related events
    • Process-related events
    • Mach port events
    • Custom events that you trigger

    Dispatch sources are part of the Grand Central Dispatch technology. For information about using dispatch sources to receive events in your application, see Dispatch Sources.

    Apple

    Asynchronous Design Techniques

    Concurrency can improve the responsiveness of your code by ensuring that your main thread is free to respond to user events. It can even improve the efficiency of your code by leveraging more cores to do more work in the same amount of time. However, it also adds overhead and increases the overall complexity of your code, making it harder to write and debug your code.

    If you implemented your tasks using blocks, you can add your blocks to either a serial or concurrent dispatch queue. If a specific order is required, you would always add your blocks to a serial dispatch queue. If a specific order is not required, you can add the blocks to a concurrent dispatch queue or add them to several different dispatch queues, depending on your needs.

    If you implemented your tasks using operation objects, the choice of queue is often less interesting than the configuration of your objects. To perform operation objects serially, you must configure dependencies between the related objects. Dependencies prevent one operation from executing until the objects on which it depends have finished their work.

    Apple

    Tips for Improving Efficiency

    Consider computing values directly within your task if memory usage is a factor. 

    If your application is already memory bound, computing values directly now may be faster than loading cached values from main memory. Computing values directly uses the registers and caches of the given processor core, which are much faster than main memory. Of course, you should only do this if testing indicates this is a performance win.

    Identify serial tasks early and do what you can to make them more concurrent 

    If a task must be executed serially because it relies on some shared resource, consider changing your architecture to remove that shared resource. You might consider making copies of the resource for each client that needs one or eliminate the resource altogether.

    Avoid using locks

    The support provided by dispatch queues and operation queues makes locks unnecessary in most situations. Instead of using locks to protect some shared resource, designate a serial queue (or use operation object dependencies) to execute tasks in the correct order.

    Rely on the system frameworks whenever possible

    The best way to achieve concurrency is to take advantage of the built-in concurrency provided by the system frameworks. Many frameworks use threads and other technologies internally to implement concurrent behaviors. When defining your tasks, look to see if an existing framework defines a function or method that does exactly what you want and does so concurrently. Using that API may save you effort and is more likely to give you the maximum concurrency possible.

  • Swift, Rethrows

    Swift, Rethrows

    Some codes and contents are sourced from Apple’s official documentation. This post is for personal notes where I summarize the original contents to grasp the key concepts

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/#Rethrowing-Functions-and-Methods

    What is rethrows in Swift?

    A function or method can be declared with the rethrows keyword to indicate that it throws an error only if one of its function parameters throws an error. These functions and methods are known as rethrowing functions and rethrowing methods. Rethrowing functions and methods must have at least one throwing function parameter.

    Apple

    screenshot 2024 03 12 at 4.05.43e280afpm

    Let’s check Foundation’s map function. A transform is a rethrowing function parameter.

    It means transform function can throwing an error.

    A rethrowing function or method can contain a throw statement only inside a catch clause. This lets you call the throwing function inside a docatch statement and handle errors in the catch clause by throwing a different error. In addition, the catch clause must handle only errors thrown by one of the rethrowing function’s throwing parameters. For example, the following is invalid because the catch clause would handle the error thrown by alwaysThrows().

    A throwing method can’t override a rethrowing method, and a throwing method can’t satisfy a protocol requirement for a rethrowing method. That said, a rethrowing method can override a throwing method, and a rethrowing method can satisfy a protocol requirement for a throwing method.

    Apple

    screenshot 2024 03 12 at 4.24.16e280afpm

    alwaysThrows function is not a function parameter. In a someFunction, only callback function parameter can throwing a error.

    enum SomeError: Error {
        case error
    }
    
    enum AnotherError: Error {
        case error
    }
    
    func alwaysThrows() throws {
        throw SomeError.error
    }

    Example 1. This one it working fine.

    func someFunction(callback: () throws -> Void) rethrows {
        do {
            try callback()
        } catch {
            throw AnotherError.error
        }
    }

    Example 2. This one also working fine

    func someFunction(callback: () throws -> Void) rethrows {
        try callback()
    }

    Example 3. This one compile error (A function declared ‘rethrows’ may only throw if its parameter does)

    func someFunction(callback: () throws -> Void) rethrows {
        do {
            try alwaysThrows()
        } catch {
            throw AnotherError.error
        }
    }

    Because alwaysThrows function is not a part of someFunction’s parameter.

    Example 4. Use try in a closure

    screenshot 2024 03 12 at 4.32.39e280afpm

    When you use try in a closure, You should marked try someFunction

    try someFunction {
        let decoder = JSONDecoder()
        try decoder.decode(String.self, from: Data())
        print("someFunction")
    }

    Example 5. Call a closure without using try

    screenshot 2024 03 12 at 4.37.41e280afpm

    If you don’t use try in a closure, It’s okay not marking try at someFuntion.

    If I change rethrows to throws in the example above, what happens?

    screenshot 2024 03 12 at 4.41.09e280afpm

    There is a compile error. Always use try keyword when you call a someFunction.

    screenshot 2024 03 12 at 4.42.18e280afpm

    Let’s make a customMap using rethrows keyword.

    screenshot 2024 03 12 at 4.05.43e280afpm

    Built-In map function uses rethrows keyword.

    screenshot 2024 03 12 at 4.45.20e280afpm
    var input = [1, 2, 3, 4, 5]
    
    extension Array {
        func customMap<T>(_ transform: ((Element) throws -> T)) rethrows -> [T] {
            var result = [T]()
            for item in self {
                let transformedValue = try transform(item)
                result.append(transformedValue)
            }
            return result
        }
    }
    
    let output = input.customMap { item in
        "\(item)"
    }
    
    let output2 = input.map { item in
        "\(item)"
    }
    

    map itself uses try keyword. Because It allows users to use try in a closure. It we are not using try keyword in a closure we can simply use it without try keyword.

  • iOS, RunLoop

    iOS, RunLoop

    Some codes and contents are sourced from Apple’s official documentation. This post is for personal notes where I summarize the original contents to grasp the key concepts

    https://developer.apple.com/documentation/foundation/runloop

    screenshot 2024 03 11 at 11.17.02e280afpm
    screenshot 2024 04 09 at 10.14.29e280afam

    Your code provides the control statements used to implement the actual loop portion of the run loop—in other words, your code provides the while or for loop that drives the run loop. Within your loop, you use a run loop object to “run” the event-processing code that receives events and calls the installed handlers.

    A run loop receives events from two different types of sources. Input sources deliver asynchronous events, usually messages from another thread or from a different application. Timer sources deliver synchronous events, occurring at a scheduled time or repeating interval. Both types of source use an application-specific handler routine to process the event when it arrives.

    Figure 3-1 shows the conceptual structure of a run loop and a variety of sources. The input sources deliver asynchronous events to the corresponding handlers and cause the runUntilDate: method (called on the thread’s associated NSRunLoop object) to exit. Timer sources deliver events to their handler routines but do not cause the run loop to exit.

    In addition to handling sources of input, run loops also generate notifications about the run loop’s behavior. Registered run-loop observers can receive these notifications and use them to do additional processing on the thread. You use Core Foundation to install run-loop observers on your threads.

    The following sections provide more information about the components of a run loop and the modes in which they operate. They also describe the notifications that are generated at different times during the handling of events.

    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

    RunLoop.mode

    common

    When you add an object to a run loop using this mode, the runloop monitors the object when running in any of the common modes. For details about adding a runloop mode to the set of common modes,

    Apple

    default

    • for handling input sources

    perform a task

    RunLoop.current.perform {
        print("Hello RunLoop")
    }
    
    RunLoop.main.perform {
        print("Hellow?")
    }

    current

    • Returns the run loop for the current thread.

    main

    • Returns the run loop of the main thread.

    Add Timer to run a code at specific time

    class RunLoopTest {
        var timer: Timer!
        init() {
            let date = Date.now.addingTimeInterval(5)
            
            //After 5 secs from not, timer will fire an event, every 2 seconds It run a function
            timer = Timer(
                fireAt: date,
                interval: 2,
                target: self,
                selector: #selector(run),
                userInfo: ["time": date],
                repeats: true
            )
            RunLoop.main.add(timer, forMode: .common)
        }
        
        @objc func run() {
            print("Hellow? : \(timer.userInfo)")
        }
        
        func stop() {
            timer.invalidate()
        }
    }
    
    RunLoopTest()
    
    //Prints
    Hellow? : Optional({
        time = "2024-03-11 15:25:30 +0000";
    })
    Hellow? : Optional({
        time = "2024-03-11 15:25:30 +0000";
    })
    Hellow? : Optional({
        time = "2024-03-11 15:25:30 +0000";
    })
    
    

    Run a timer in a RunLoop while scrolling the table

    import UIKit
    
    class ViewController: UIViewController {
        var timer: Timer!
        var firedCount = 0
        
        lazy var tableView: UITableView = {
           let tableView = UITableView()
            tableView.dataSource = self
            tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
            return tableView
        }()
        
        lazy var items: [Int] = {
           var items = [Int]()
            for i in 0...1000 {
                items.append(i)
            }
            return items
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            timer = Timer(
                fireAt: Date.now.addingTimeInterval(5),
                interval: 0.2,
                target: self,
                selector: #selector(run),
                userInfo: nil,
                repeats: true
            )
            RunLoop.main.add(timer, forMode: .default)
            tableView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(tableView)
            NSLayoutConstraint.activate([
                tableView.topAnchor.constraint(equalTo: view.topAnchor),
                tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
        }
        
        @objc func run() {
            print("🔥 timer fired: \(firedCount)")
            firedCount += 1
        }
    }
    
    extension ViewController: UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            items.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = "\(items[indexPath.row])"
            return cell
        }
    }
    
    //Prints
    🔥 timer fired: 0
    
    🔥 timer fired: 1
    
    🔥 timer fired: 2
    
    🔥 timer fired: 3
    🔥 timer fired: 4
    🔥 timer fired: 5
    🔥 timer fired: 6
    

    Example 1. RunLoop Mode is defaults

    Above the code, timer starts after 5 secs. And I scrolling the table for 20 secs.

    screenshot 2024 03 12 at 12.20.49e280afpm

    Example 2. Changed RunLoop Mode to common

    screenshot 2024 03 12 at 12.22.39e280afpm

    Now I understand what RunLoop mode is…

    When you use default mode, timer might not fired if user touching the screens. defaults mode handles Input source.

  • Swift, Sorting and Binary Search Algorithm

    Swift, Sorting and Binary Search Algorithm

    Useful Foundation APIs

    swapAt

    • swap the value in an array by passing the index

    Bubble Sort

    func bubbleSort(input: inout [Int]) {
        for i in 0..<input.count {
            for j in 1..<input.count {
                if input[j] < input[j - 1] {
                    input.swapAt(j, j-1)
                }
            }
        }
    }
    
    var input = [4, 5, 2, 1, 6, 8, 9, 12, 13]
    bubbleSort(input: &input)
    screenshot 2024 03 10 at 9.39.01e280afpm
    screenshot 2024 03 11 at 9.41.53e280afpm

    Every steps compares 2 values and swapped it

    Red = Sorted Item

    Time complexity is O(n^2)

    Space complexity is O(1)

    Insertion Sort

    Apple Foundation uses TimSort (InsertionSort + MergeSort)

    screenshot 2024 05 15 at 9.22.59e280afpm
    screenshot 2024 05 15 at 9.24.43e280afpm

    Insertion Sort is another O(N^2) quadratic running time algorithm

    On large datasets it is very inefficient – but on arrays with 10-20 items it is quite good. (Apple uses TimSort, It items is less than 64 then use Insertion Sort. -> https://github.com/apple/swift/blob/387580c995fc9844d4f268723bd55e22440b1a3d/stdlib/public/core/Sort.swift#L462)

    A huge advantage is that it is easy to implement

    It is more efficient than other quadratic running time sorting procedures such as bubble sort or selection sort

    It is an adaptive algorithm – It speeds up when array is already substantially sorted

    It is stable so preserves the order of the items with equal keys

    Insertion sort is an in-place algorithm – does not need any additional memory

    It is an online algorithm – It can sort an array as it receives the items for example downloading data from web

    Hybrid algorithms uses insertion sort if the subarray is small enough: Insertion sort is faster for small subarrays than quicksort

    Variant of insertion sort is shell sort

    Sometimes selection sort is better: they are very similar algorithms

    Insertion sort requires more writes because the inner loop can require shifting large sections of the sorted portion of the array

    In general insertion sort will write to the array O(N^2) times while selection sort will write only O(N) times

    For this reason selection sort may be preferable in cases where writing to memory is significantly more expensive than reading (such as with flash memory)

    https://www.udemy.com/course/algorithms-and-data-structures-in-java-part-ii/learn/lecture/4901832#questions

    screenshot 2024 05 15 at 6.27.12e280afpm
    screenshot 2024 05 15 at 6.28.22e280afpm
    screenshot 2024 05 15 at 6.28.35e280afpm
    screenshot 2024 05 15 at 6.29.10e280afpm
    screenshot 2024 05 15 at 6.29.36e280afpm
    screenshot 2024 05 15 at 6.29.52e280afpm
    func insertSort(_ input: inout [Int]) {
        var i = 1
        for i in 1..<input.count {
            var j = i
            while j-1 >= 0, input[j] < input[j-1] {
                input.swapAt(j, j-1)
                j-=1
            }
        }
    }
    var input = [-9, 4, -9, 5, 8, 12]
    insertSort(&input)
    print(input)
    
    
    var input2 = [-9, 4, -9, 5, 8, 12, -249, 241, 4, 2, 12, 24140, 539, 3, 0, -2314]
    insertSort(&input2)
    print(input2)
    screenshot 2024 05 15 at 9.20.55e280afpm

    Merge Sort

    Divide and Conquer

    Break down into small subproblem, Use recursion.

    It needs two functions

    • split – divide into two array
    • merge – merging two array into the sorted array
    func mergeSort(input: inout [Int]) {
        //Base case
        if input.count == 1 {
            return
        }
        let midIndex = input.count / 2
        var left = Array(repeating: 0, count: midIndex)
        var right = Array(repeating: 0, count: input.count - midIndex)
        
        //Split into 2 sub array (create sub problems)
        split(input: &input, left: &left, right: &right)
        
        //Recursive - Divide and Conquer
        mergeSort(input: &left)
        mergeSort(input: &right)
        
        //Merge all together
        merge(input: &input, left: &left, right: &right)
    }
    func split(input: inout [Int], left: inout [Int], right: inout [Int]) {
        let leftCount = left.count
        for (index, item) in input.enumerated() {
            if index < leftCount {
                left[index] = item
            }
            else {
                right[index - leftCount] = item
            }
        }
    }
    func merge(input: inout [Int], left: inout [Int], right: inout [Int]) {
        var mergeIndex = 0
        var leftIndex = 0
        var rightIndex = 0
        
        while (leftIndex < left.count && rightIndex < right.count) {
            if left[leftIndex] < right[rightIndex] {
                input[mergeIndex] = left[leftIndex]
                leftIndex += 1
            }
            else if rightIndex < right.count {
                input[mergeIndex] = right[rightIndex]
                rightIndex += 1
            }
            mergeIndex += 1
        }
        if leftIndex < left.count {
            while mergeIndex < input.count {
                input[mergeIndex] = left[leftIndex]
                mergeIndex += 1
                leftIndex += 1
            }
        }
        if rightIndex < right.count {
            while mergeIndex < input.count {
                input[mergeIndex] = right[rightIndex]
                mergeIndex += 1
                rightIndex += 1
            }
        }
    }
    screenshot 2024 03 10 at 10.43.40e280afpm

    Split Function

    Calculate midIndex

    Split them into two subarray

    screenshot 2024 03 10 at 11.23.11e280afpm
    screenshot 2024 03 11 at 10.38.33e280afpm
    screenshot 2024 03 11 at 10.39.25e280afpm

    Time complexity: O(nlogn)

    Space complexity: O(n)

    • Because It needs a copy values when merging two array into the one sorted array

    Quick Sort

    func partition(_ input: inout [Int], low: Int, high: Int) -> Int {
        let pivot = input[low]
        var l = low
        var h = high
        while l < h {
            while (input[l] <= pivot && l < h) {
                l += 1
            }
            while (input[h] > pivot) {
                h -= 1
            }
            if l < h {
                input.swapAt(l, h)
            }
        }
        //Put pivot to the right position
        input.swapAt(low, h)
        return h
    }
    
    func quickSort(_ input: inout [Int], low: Int, high: Int) {
        //base
        if low >= high {
            return
        }
        //pivot
        let pivot = partition(&input, low: low, high: high)
        
        //left
        quickSort(&input, low: low, high: pivot - 1)
    
        //right
        quickSort(&input, low: pivot + 1, high: high)
    }
    
    var input = [2, 3, 1, 4, 9]
    quickSort(&input, low: 0, high: input.count - 1)
    print(input)
    screenshot 2024 03 15 at 9.22.17e280afpm
    screenshot 2024 03 15 at 9.44.45e280afpm

    Quick Sort is also Divide and Conquer.

    Time Complexity

    • Average Time Complexity is O(nlogn)

    Space Complexity

    • O(logn) -> Extra space for the call stack in the recursive call
    • O(n) -> worst case

    Binary Search

    Search a sorted list!

    //Binary Search
    func findIndex(array: [Int], value: Int) -> Int {
        var min = 0
        var max = array.count - 1
        
        while min <= max {
            let mid = min + (max - min) / 2
            
            if value == array[mid] {
                return mid
            }
            //search right side
            if value > array[mid] {
                min = mid - 1
            }
            //search left side
            else {
                max = mid + 1
            }
        }
        return -1
    }
    
    let sortedArray = Array(0...100)
    findIndex(array: sortedArray, value: 49)
    findIndex(array: sortedArray, value: 78)
    findIndex(array: sortedArray, value: 22)
    findIndex(array: sortedArray, value: 89)
    screenshot 2024 03 15 at 11.20.54e280afpm
  • Swift, DispatchQueue

    Swift, DispatchQueue

    ✍️ Note

    All the codes and contents are sourced from Apple’s official documentation. This post is for personal notes where I summarize the original contents to grasp the key concepts

    Apple documents

    DispatchQueue

    QoS (Quality of Service)

    • UserInteractive
      • Animations, event handling, or updates to your app’s user interface.
      • User-interactive tasks have the highest priority on the system. Use this class for tasks or queues that interact with the user or actively update your app’s user interface. For example, use this class for animations or for tracking events interactively.
    • UserInitiated
      • Prevent the user from actively using your app
      • User-initiated tasks are second only to user-interactive tasks in their priority on the system. Assign this class to tasks that provide immediate results for something the user is doing, or that would prevent the user from using your app. For example, you might use this quality-of-service class to load the content of an email that you want to display to the user.
    • Default
      • Default tasks have a lower priority than user-initiated and user-interactive tasks, but a higher priority than utility and background tasks. Assign this class to tasks or queues that your app initiates or uses to perform active work on the user’s behalf.
    • Utility
      • Utility tasks have a lower priority than default, user-initiated, and user-interactive tasks, but a higher priority than background tasks. Assign this quality-of-service class to tasks that do not prevent the user from continuing to use your app. For example, you might assign this class to long-running tasks whose progress the user does not follow actively.
    • Background
      • Background tasks have the lowest priority of all tasks. Assign this class to tasks or dispatch queues that you use to perform work while your app is running in the background.

    let concurrentQueue = DispatchQueue(label: "concurrent", qos: .userInitiated, attributes: 
    .concurrent)
    let serialQueue = DispatchQueue(label: "serial", qos: . userInitiated)

    Example 1. Perform async tasks on serialQueue

    for i in 0...3 {
      serialQueue.async {
        print("serial task(\(i)) start")
        sleep(1)
        print("serial task(\(i)) end")
      }
    }
    
    //prints
    serial task(0) start
    serial task(0) end
    
    serial task(1) start
    serial task(1) end
    
    serial task(2) start
    serial task(2) end
    
    serial task(3) start
    serial task(3) end

    It’s make sense because serialQueue can run a one task at a time.

    Example 2. Perform sync tasks on serialQueue

    for i in 0...3 {
      serialQueue.sync {
        print("serial task(\(i)) start")
        sleep(1)
        print("serial task(\(i)) end")
      }
    }
    
    //prints
    serial task(0) start
    serial task(0) end
    
    serial task(1) start
    serial task(1) end
    
    serial task(2) start
    serial task(2) end
    
    serial task(3) start
    serial task(3) end

    Results are the same as example 1

    Submits a work item for execution on the current queue and returns after that block finishes executing.

    https://developer.apple.com/documentation/dispatch/dispatchqueue/2016083-sync

    Example 3. Perform a sync task on the concurrentQueue

    for i in 0...3 {
      concurrentQueue.sync {
        print("concurrent task(\(i)) start")
        sleep(1)
        print("concurrent task(\(i)) end")
      }
    }
    
    //Prints
    concurrent task(0) start
    concurrent task(0) end
    
    concurrent task(1) start
    concurrent task(1) end
    
    concurrent task(2) start
    concurrent task(2) end
    
    concurrent task(3) start
    concurrent task(3) end

    Example 4. Perform a async task on the concurrentQueue

    for i in 0...3 {
      concurrentQueue.async {
        print("concurrent task(\(i)) start")
        sleep(1)
        print("concurrent task(\(i)) end")
      }
    }
    
    //Prints
    concurrent task(0) start
    concurrent task(3) start
    concurrent task(1) start
    concurrent task(2) start
    
    concurrent task(0) end
    concurrent task(1) end
    concurrent task(3) end
    concurrent task(2) end

    Schedules a work item for immediate execution, and returns immediately.

    https://developer.apple.com/documentation/dispatch/dispatchqueue/2016103-async

    It immediate execution and returns immediately.

    Example 5. Perform async task on the concurrentQueue and sync task on the serialQueue

    for i in 0...3 {
      concurrentQueue.async {
        print("concurrent task(\(i)) start")
        sleep(1)
        print("concurrent task(\(i)) end")
      }
                
      serialQueue.sync {
        print("serial task(\(i)) start")
        sleep(1)
        print("serial task(\(i)) end")
      }
    }
    
    //Prints
    concurrent task(0) start
    serial task(0) start
    serial task(0) end
    serial task(1) start
    concurrent task(1) start
    concurrent task(0) end
    concurrent task(1) end
    serial task(1) end
    serial task(2) start
    concurrent task(2) start
    serial task(2) end
    serial task(3) start
    concurrent task(3) start
    concurrent task(2) end
    serial task(3) end
    concurrent task(3) end

    Example 6. Run async tasks on the concurrentQueue and serialQueue

    for i in 0...3 {
       concurrentQueue.async {
         print("concurrent task(\(i)) start")
         sleep(1)
         print("concurrent task(\(i)) end")
       }
    
       serialQueue.async {
         print("serial task(\(i)) start")
         sleep(1)
         print("serial task(\(i)) end")
       }
    }
    
    //Prints
    concurrent task(0) start
    concurrent task(2) start
    concurrent task(3) start
    serial task(0) start
    concurrent task(1) start
    concurrent task(3) end
    concurrent task(0) end
    concurrent task(2) end
    serial task(0) end
    concurrent task(1) end
    serial task(1) start
    serial task(1) end
    serial task(2) start
    serial task(2) end
    serial task(3) start
    serial task(3) end

    As you can see a SerialQueue run a task and returned when task has finished either perform it on sync or async.

    What about dispatchQueue.main.async?

    for i in 0...3 {
      DispatchQueue.main.async {
        print("main task(\(i)) start")
        sleep(1)
        print("main task(\(i)) end")
      }
    }
    
    //Prints
    main task(0) start
    main task(0) end
    
    main task(1) start
    main task(1) end
    
    main task(2) start
    main task(2) end
    
    main task(3) start
    main task(3) end

    The dispatch queue associated with the main thread of the current process.

    The system automatically creates the main queue and associates it with your application’s main thread. Your app uses one (and only one) of the following three approaches to execute blocks submitted to the main queue:

    As with the global concurrent queues, calls to suspend()resume()dispatch_set_context(_:_:), and the like have no effect when used on the queue in this property.

    https://developer.apple.com/documentation/dispatch/dispatchqueue/1781006-main

    As the results, It is not returns immediately like a concurrent queue. Because It’s a serial queue. You can check it on Xcode.

    What about dispatchQueue.global().async?

    for i in 0...3 {
      DispatchQueue.global().async {
        print("global task(\(i)) start")
        sleep(1)
        print("global task(\(i)) end")
      }
    }
    
    //Prints
    global task(3) start
    global task(2) start
    global task(0) start
    global task(1) start
    
    global task(1) end
    global task(0) end
    global task(3) end
    global task(2) end

    This method returns a queue suitable for executing tasks with the specified quality-of-service level. Calls to the suspend()resume(), and dispatch_set_context(_:_:) functions have no effect on the returned queues.

    Tasks submitted to the returned queue are scheduled concurrently with respect to one another

    https://developer.apple.com/documentation/dispatch/dispatchqueue/2300077-global

    🤯 When function is returned?

    It’s a good example for understanding sync vs async.

    Before look at the example, let’s remind

    Dispatch queues are FIFO queues to which your application can submit tasks in the form of block objects. Dispatch queues execute tasks either serially or concurrently. Work submitted to dispatch queues executes on a pool of threads managed by the system. Except for the dispatch queue representing your app’s main thread, the system makes no guarantees about which thread it uses to execute a task.

    You schedule work items synchronously or asynchronously. When you schedule a work item synchronously, your code waits until that item finishes execution. When you schedule a work item asynchronously, your code continues executing while the work item runs elsewhere.

    https://developer.apple.com/documentation/dispatch/dispatchqueue

    ConcurrentQueue with sync

    runWorkItem function is returned when after finishing the sync task.

    ConcurrentQueue with async

    runWorkItem function returned and then async task starts

    SerialQueue with async

    DispatchQueue.main.async

    Custom Serial DispatchQueue

    SerialQueue with sync but what if a submitted task is delayed?

    Keep in mind

    Work submitted to dispatch queues executes on a pool of threads managed by the system. Except for the dispatch queue representing your app’s main thread, the system makes no guarantees about which thread it uses to execute a task.

    https://developer.apple.com/documentation/dispatch/dispatchqueue

    sync -> block will run and returned when task finished

    async -> block will immediately returned when task started

    syncasyncwhen function returned?
    SerialQeueuewait until task finishedwait until task finishedeither sync or async it returned when after task finished
    ConcurrentQueuewait until task finishedimmediately return when task startedsync: it returned after task finished
    async: it returned before task finished
  • Swift, ARC Automatic Reference Counting

    Swift, ARC Automatic Reference Counting

    ✍️ Note

    All the codes and contents are sourced from Apple’s official documentation. This post is for personal notes where I summarize the original contents to grasp the key concepts

    Apple document

    class Person {
        let name: String
        init(name: String) {
            self.name = name
            print("\(name) is being initialized")
        }
        deinit {
            print("\(name) is being deinitialized")
        }
    }

    Check Strong Reference Count

    Example 1

    var person = Person(name: "Shawn") //1
    CFGetRetainCount(person) //2

    Example 2

    CFGetRetainCount(Person(name: "Shawn") //1

    Example 3

    var person = Person(name: "Shawn") //2
    var person2 = person //3
    var person3 = person //4
    
    CFGetRetainCount(person) //4

    As you can see when you initiate an object, the retain count is 1. And when you assign it, retain count will be counting up.

    Strong Reference Cycle between classes

    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { print("\(name) is being deinitialized") }
    }
    
    
    class Apartment {
        let unit: String
        init(unit: String) { self.unit = unit }
        var tenant: Person?
        deinit { print("Apartment \(unit) is being deinitialized") }
    }

    Example 1

    var person = Person(name: "Shawn")
    var apt = Apartment(unit: "3")
    
    CFGetRetainCount(person) //2
    CFGetRetainCount(apt) //2

    Example 2 (🔥 Strong Reference Cycle)

    var person = Person(name: "Shawn")
    var apt = Apartment(unit: "3")
    
    person.apartment = apt
    apt.tenant = person
    
    CFGetRetainCount(person) //3 (apt.tenant -> count up)
    CFGetRetainCount(apt) //3 (person.apartment -> count up)
    

    ✅ Solution -> weak / unowned

    What is weak reference and when to use it?

    Use a weak reference when the other instance has a shorter lifetime — that is, when the other instance can be deallocated first.

    Apple – https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated. And, because weak references need to allow their value to be changed to nil at runtime, they’re always declared as variables, rather than constants, of an optional type.

    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { print("\(name) is being deinitialized") }
    }
    
    
    class Apartment {
        let unit: String
        init(unit: String) { self.unit = unit }
        weak var tenant: Person?
        deinit { print("Apartment \(unit) is being deinitialized") }
    }

    What is unowned reference and when to use it?

    In contrast, use an unowned reference when the other instance has the same lifetime or a longer lifetime. Unlike a weak reference, an unowned reference is expected to always have a value. As a result, marking a value as unowned doesn’t make it optional, and ARC never sets an unowned reference’s value to nil.

    Important

    Use an unowned reference only when you are sure that the reference always refers to an instance that hasn’t been deallocated.

    If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.

    Apple – https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    class Customer {
        let name: String
        var card: CreditCard?
        init(name: String) {
            self.name = name
        }
        deinit { print("\(name) is being deinitialized") }
    }
    
    
    class CreditCard {
        let number: UInt64
        unowned let customer: Customer
        init(number: UInt64, customer: Customer) {
            self.number = number
            self.customer = customer
        }
        deinit { print("Card #\(number) is being deinitialized") }
    }

    Example 1

    var customer: Customer! = Customer(name: "Shawn")
    customer.card = CreditCard(number: 7788_9999_1223_1225, customer: customer)
    
    CFGetRetainCount(customer) //2 (because CreditCard's customer is defined as unowned let)
    
    customer = nil
    
    //Shawn is being deinitialized
    //Card #7788999912231225 is being deinitialized 

    unowned(unsafe)

    Swift also provides unsafe unowned references for cases where you need to disable runtime safety checks — for example, for performance reasons. As with all unsafe operations, you take on the responsibility for checking that code for safety. You indicate an unsafe unowned reference by writing unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    unownedself

    When you access the unowned(unsafe), It try to access the memory location where the instance used to be

    unowned

    when use unowned, error type is different.

    Unowned Optional References

    You can mark an optional reference to a class as unowned. In terms of the ARC ownership model, an unowned optional reference and a weak reference can both be used in the same contexts. The difference is that when you use an unowned optional reference, you’re responsible for making sure it always refers to a valid object or is set to nil.

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    class Department {
        var name: String
        var courses: [Course]
        init(name: String) {
            self.name = name
            self.courses = []
        }
    }
    
    
    class Course {
        var name: String
        unowned var department: Department
        unowned var nextCourse: Course?
        init(name: String, in department: Department) {
            self.name = name
            self.department = department
            self.nextCourse = nil
        }
    }

    Example

    var department: Department! = Department(name: "Horticulture")
    
    let intro = Course(name: "Survey of Plants", in: department)
    var intermediate: Course! = Course(name: "Growing Common Herbs", in: department)
    var advanced: Course = Course(name: "Caring for Tropical Plants", in: department)
    
    intro.nextCourse = intermediate
    intermediate.nextCourse = advanced
    department.courses = [intro, intermediate, advanced]
    
    //Deinit department and intermediate
    department = nil
    intermediate = nil
    
    //Try to access nextCourse
    intro.nextCourse //SIGABRT Error

    Unlike weak, when you try to access the deallocated object, It causes a crash issue. Even you set it as unowned optionals.

    Unowned References and Implicitly Unwrapped Optional Properties

    class Country {
        let name: String
        var capitalCity: City! //Unwrapped Optional Properties
        init(name: String, capitalName: String) {
            self.name = name
            //At this point, Country already initiated. So can pass self to City initializer
            self.capitalCity = City(name: capitalName, country: self)
        }
    }
    
    
    class City {
        let name: String
        unowned let country: Country //Unowned References
        init(name: String, country: Country) {
            self.name = name
            self.country = country
        }
    }

    var capitalCity: City!

    • It’s initial value is nil

    Above the example is Two-Phased initialization

    🤔 Strong Reference Cycles for Closures

    It’s such an important topic.

    A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance. This capture might occur because the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.

    This strong reference cycle occurs because closures, like classes, are reference types. When you assign a closure to a property, you are assigning a reference to that closure. In essence, it’s the same problem as above — two strong references are keeping each other alive. However, rather than two class instances, this time it’s a class instance and a closure that are keeping each other alive.

    Swift provides an elegant solution to this problem, known as a closure capture list

    https://docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting

    class HTMLElement {
        let name: String
        let text: String?
    
        lazy var asHTML: () -> String = {
            if let text = self.text {
                return "<\(self.name)>\(text)</\(self.name)>"
            } else {
                return "<\(self.name) />"
            }
        }
    
        init(name: String, text: String? = nil) {
            self.name = name
            self.text = text
        }
    
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
    print(paragraph!.asHTML())
    
    //Deallocate HTMLElement
    paragraph = nil // Can't deallocate it because the reference cycle between property (asHTML) and closure's capture list
    screenshot 2024 03 10 at 12.02.07e280afam

    Even though the closure refers to self multiple times (self.text and self.name), it only captures one strong reference to the HTMLElement instance.

    Apple

    Resolving Strong Reference Cycles for Closures

    Use weak or unowned at capture lists

    Defining a capture list

    Each item in a capture list is a pairing of the weak or unowned keyword with a reference to a class instance (such as self) or a variable initialized with some value (such as delegate = self.delegate). These pairings are written within a pair of square braces, separated by commas.

    Place the capture list before a closure’s parameter list and return type if they’re provided:

    Apple

    lazy var someClosure = {
            [unowned self, weak delegate = self.delegate]
            (index: Int, stringToProcess: String) -> String in
        // closure body goes here
    }
    
    
    lazy var someClosure2 = {
            [unowned self, weak delegate = self.delegate] in
        // closure body goes here
    }

    🔥 Think it again, weak vs unowned in Closures

    Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.

    Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.

    Apple

    class HTMLElement {
        let name: String
        let text: String?
        //Use unowned self to break reference cycle
        lazy var asHTML: () -> String = {
                [unowned self] in
            if let text = self.text {
                return "<\(self.name)>\(text)</\(self.name)>"
            } else {
                return "<\(self.name) />"
            }
        }
    
        init(name: String, text: String? = nil) {
            self.name = name
            self.text = text
        }
    
        deinit {
            print("\(name) is being deinitialized")
        }
    }
    screenshot 2024 03 10 at 12.21.50e280afam

    Capture value from nested Closure

    class Test {}
    DispatchQueue.global().async {
      let test = Test()
       print("closure A: Start retain count is: \(CFGetRetainCount(test))")
       DispatchQueue.global().async {
         print("nested closure B retainCount: \(CFGetRetainCount(test))")
       }
       print("closure A: End")
    }
    
    //Prints
    closure A: Start retain count is: 2
    closure A: End
    nested closure B retainCount: 3

    As you can see nested closure also counting up ARC.

    But… what about this example?

    screenshot 2024 03 14 at 11.00.59e280afpm

    test object was already deallocated. And when you trying to access test, It should not be nil because It’s unowned.

    Conclusion

    Understanding How ARC works is very important to avoid memory leaks and crash issues. It’s also good to know what happens when objects are deallocated and How ARC handles those objects.

  • Leetcode official iOS app – How can I change Chinese language to English

    Leetcode official iOS app – How can I change Chinese language to English

    Download Leetcode official app

    Did you know leetcode has an official iOS app? You can download it.

    But there is one problem. They are not fully support English.

    Good news is we can change Leetcode problem descriptions to English. It’s enough to solve problem!

    Step 1. Change Settings

    img 0453 1

    The seconds option meaning Question Language. Let’s change it to English.

    Check your setting (blue circle)

    Step 2. Go to the second tab (Question bank)

    img 0454 1

    You can see some Chinese characters are changed to English (Blue circle)

    img 0455 1

    As you can see all the questions are English. Let’s enjoy problem solving!

  • 20년 넘은 스케이트보드 수리하기

    20년 넘은 스케이트보드 수리하기

    스케이트보드 부품은 크게 데크, 트럭, 휠, 베어링이다.

    20년 넘은 소중한 보드 수리과정을 공개한다

    트럭 수리하기

    트럭 상태가 정말 최악이었다. 볼트는 다 상했고 실린더(쿠션)도 작살난 상태였다.

    녹 제거도 필수.. WD40을 휴지에 적셔서 딱아냈다. (WD40은 절대로 베어링에는 뿌리지 말자, 난 트럭의 녹 제거에만 사용했다)

    image
    img 7435

    보드샵에가서 인디팬던트 실린더랑 교체해야 될 부품을 사왔다. (내 트럭은 아주 오래된 스탠다드 제품의 트럭이지만 이격없이 잘 맞았다)

    img 7818
    img 7821

    작살난 검은색 부품도 새로 교체했다.

    img 8024

    문제가 생겼다. 휠을 연결하는 부분이 마모가 되서 너트가 돌아가지 않았다. 이런 경우 위와 같이 생긴 툴을 이용해서 마모가 된 부분에 너트가 잘 조여지도록 갈아야 한다. 툴을 구매할 때 반드시 가운데 모양이 저렇게 생긴 제품을 사는 것을 추천한다.

    img 8022
    img 8027

    오케이! 이제 너트가 잘 조여졌다. 휠 고정 완료!

    img 8029

    실린더(노란색) 교체했고 킹핀을 고정시키는 너트도 새걸로 교체했다.

    img 8028

    베어링 교체하기

    의외로 제일 간단하다. 트럭을 이용해서 기존에 있는 베어링을 쉽게 제거할 수 있다.

    img 7710

    뭔가 설명하기 좀 어려운데 일단 휠을 고정하고 있는 너트를 트럭에서 제거한다. 그리고 휠을 다시 살짝 트럭의 볼트 조이는 곳에 걸친다음에 꺽어서 베어링을 제거하면 된다.

    img 7825

    가성비 최고인 Spitfire 베어링으로 교체 완료

    데크 수리하기

    시행착오를 많이 겪었다. 일단 상태에 따라서 판단하고 제품을 구매하면 된다.

    먼저 그립 테이프를 벗기자.

    img 7623

    헤어드라이기로 열을 가하면서 잘 떼어내자. 진득진듯한 것이 만약 데크에 남는다면 일이 엄청 복잡해진다. 뗄때 잘 떼는게 정말 중요하다.

    자 다음으로..

    손상된 데크 수리하기

    1. 우드 글루

    데크의 나무 겹겹이 벌어진 곳이 있다

    데크의 일 부분이 깨졌고 깨진 조각을 갖고 있다

    위의 경우에 해당하면 우드 글루가 필요하다. 그리고 클램프로 고정시켜서 단단하게 부착 시켜야 된다.

    img 7956

    내가 실수한 부분. 사실 나의 데크는 우드 글루를 저렇게 많이 바를 필요가 없었다. 중간 중간 벌어진 곳이나 틈새 정도에만 발라도 충분했다.

    2. 우드 필러

    img 7955

    데크의 상태를 보면 깨지고 파인 부분들이 많이 보인다. 이런 경우 우드 필러를 이용해서 메꿀수가 있다.

    img 7984

    우드필러로 떡칠을 했다. 저 상태로 2-3일 냅두자. 완전히 굳을 때까지..

    img 8041

    80이나 120이라고 적힌 사포(샌드 페이퍼)로 잘 갈아주자. 맨들 맨들 해질때까지 고르게 문지르면 위의 사진 처럼 된다.

    img 8039

    오케이! 이제 그립 테이프를 붙일 준비가 되었다.

    img 8042 1

    이 단계가 제일 쉽다. 끝에서 부터 천천히 눌러가면서 붙이자.

    img 8044

    다 부착했으면 툴이나 드라이버를 이용해서 저렇게 보드 외곽을 문질러주면 잘라내야 될 선이 선명하게 표시된다.

    img 8047

    표시된 선을 따라 칼로 잘라내면 된다.

  • Do you require a server for your iOS app? CloudKit might be the perfect fit for you!

    Do you require a server for your iOS app? CloudKit might be the perfect fit for you!

    Choosing a server can be challenging, especially if you’re not a backend engineer but still need one for your iOS app. You may find yourself in this situation.

    So, Why did I consider CloudKit?

    Well, first off, it’s maintained by Apple and has been around since its introduction at the 2014 WWDC.

    Apple actively uses it for various apps like Photos and Notes, which gives me confidence in its longevity and reliability. Plus, I expect it to receive regular updates from Apple.

    My experience

    Parse.com

    I used Parse (Acquired by Facebook) and contributed to the Parse iOS SDK. I liked it, but Facebook announced its discontinuation.

    Consequently, I had to migrate to alternatives like back4app or Sashido.io, which serve as alternatives to parse.com. While they were good, I encountered issues with the lack of active updates to the iOS SDK. This led me to contribute to its development, but I felt it was a waste of time. I wanted to focus on my app rather than on open-source projects.

    Vapor

    I like Vapor because I can create a server using Swift, which is a significant benefit as an iOS Engineer. Additionally, there are numerous helpful resources available, such as books, YouTube videos, and Udemy courses.

    But why did I consider CloudKit?

    Unlike CloudKit, I would need to implement server logic from scratch, including authentication, deployment, database setup, migration, and data sharing between users.

    My approach

    screenshot 2024 02 12 at 4.14.49e280afpm

    Context

    • I’ve already implemented a server using Vapor to handle authentication. While working on sharing data between users, I researched an easy way to implement the invite/accept feature and found that CloudKit fully supports it.

    The server I implemented using Vapor is deployed on AWS, using ECS with a Load Balancer. Its primary role is handling SignIn/SignUp and supporting user-related features such as changing passwords and usernames. All user information is saved in PostgreSQL.

    For data synchronization across user devices, I utilize NSPersistentCloudKitContainer, which syncs data between CoreData and CloudKit seamlessly. This integration is very convenient and eliminates the need for manual synchronization logic.

    CloudKit also supports invite and accept functionality for sharing data between users, eliminating the need to implement server-side logic for this feature.

    Be aware of CloudKit topics

    Now, returning to Vapor,

    I can access and edit CloudKit’s data using CloudKit Web Service. NSPersistentCloudKitContainer use a special zone called com.apple.coredata.cloudkit.zone. If you’re interested how to access it using CloudKit Web Service, you can check out Reading CloudKit Records for Core Data.

    Public Data

    As for public data storage, I haven’t decided yet where to store it. I’ll update you once I make a decision.

    How about CloudKit costs?

    Currently, the pricing seems like a black box. I can’t find pricing information on the Apple Developer website. That’s why I can’t decide where I should store public data.

    If you want to know AWS costs, see my previous post.

    I captured pricing information by accessing the archived website.

    screenshot 2024 02 12 at 2.15.25e280afpm
    screenshot 2024 02 12 at 2.15.42e280afpm
    screenshot 2024 02 12 at 2.14.45e280afpm

    Overage Fees

    Asset Storage $0.03/GB
    Database Storage $3.00/GB
    Data Transfer $0.10/GB
    Requests per sec $100 per 10 requests
  • 리얼포스 키보드 키캡 분리 및 청소

    리얼포스 키보드 키캡 분리 및 청소

    오랜만에 키보드를 청소했다

    키캡 분리기로 모든 키를 제거하고 키보드 상판을 칫솔로 청소해줬다

    img 7870
    img 7871

    얼핏 보면 깨끗해보이지만 상판에 전에 없었던 약간의 녹을 발견했다.

    검색해보니 리얼포스의 고질적인 문제였다고 한다. 음 키보드에 뭘 흘린적이 한번도 없는데도 녹이 생긴걸 보니 습한 지역에서는 발생할 수도 있겠구나 싶다.

    img 7872 1

    녹이 발생한 부분 😢

    그래도 사용하는데는 전혀 지장없으니 괜찮다. 최대한 오래 사용할 수 있으면 좋겠다.

    참고로 녹이 생겼다고해서 절대로 wd40을 사용하면 안된다. 혹시나 녹을 제거하겠다고 키보드에 뿌리거나 사용라면 키보드 버려야 된다

    암튼 이상으로 간단한 키보드 청소 후기를 마친다