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
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 sourcesdeliver 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.
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,
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)
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)
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)
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
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.
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 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
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 queuesexecutes 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.
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 queuesexecutes 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.
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
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.
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.
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.
When you access the unowned(unsafe), It try to access the memory location where the instance used to be
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.
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
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
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 nilwhen 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")
}
}
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?
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.
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.
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
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.
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.
싱가포르로 이주하기 전에 내가 개인적으로 궁금했던 정보이다. 혹여나 싱가포르 이직을 고려하고 있는 외벌이 분들에게 도움 되길 바란다. 자..싱가포르에서 한달 생활하는 데 고정으로 나가는 비용이 얼마정도인지 정리해봤다.
(도움이 되었다면 좋아요 혹은 팔로우가 지속적으로 블로그를 운영하는 데 큰 힘이 됩니다)
월세
일단 현재 외곽지역에 살고있다. 일단 재택근무가 1주일에 3일정도 가능한 회사라서 외곽도 괜찮다. 준공년도 2019년으로 비교적 신축 콘도이고 세대수도 한 1400세대 정도인데 2 베드룸 (678 sqft) 기준으로 3200 SGD이다. 콘도 관리비는 집주인이 부담하기 때문에 신경쓰지 않아도 된다.
인터넷
ViewQwest 업체를 사용하고 있다. 라우터 없는 요금제로 1G에 월 39.72 SGD
빨간 박스에 본인 연봉을 입력하자. 그러면 하단 NET TAX PAYABLE 에 본인이 내야 될 세금이 나온다. 참고로 싱가포르는 세금을 일시불로 낼 수도 있고 12개월 무이자로 분납할 수도 있다.
수도, 가스, 전기요금
이 부분이 정말 집집마다 천차만별이다. 참고로 내가 사는 지역은 생각보다 시원하다. 고층에 살고 있는데 창문 열어놓고 선풍기만 틀고 산다. 에어컨을 하루에 1시간도 안틀고 살고 있고 그래도 덥지 않다. 다만 샤워는 꽤 자주 하는 편이고 하루에 한 3번 정도? 가스는 요리할 때나 온수틀 때 주로 사용하고 집에서 요리는 그래도 자주 하는 편이다.
You must be logged in to post a comment.