묻고 답해요
160만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
18강 NSCache 예시 질문
안녕하세요!18강 전체적으로 cache 에 값을 세팅하기 전후로 lock 이나 semaphore 등을 이용해서 GCD 에서 thread safe 를 구현하는 방법에 대한 예시를 들어준것 확인했습니다이때 removeAll() 의 앞뒤로는 따로 처리가 되어있지않아서, 정말 필요가 없어서 작성되어있지 않은건지, 아니면 단순 예시라서 여기까지는 적용하지 않은건지 궁금합니다func clearAll() { cache.removeAll() } 두번째로, 이건 주제와 좀 동떨어진 질문이긴한데요..! (혹시 강의 성격과 너무 벗어났다 싶으면 무시해주세요!)예시를 NSCache 로 들어주셨는데, NSCache 문서에 보면 캐시를 직접 잠그지 않고도 다양한 스레드에서 캐시에 항목을 추가, 제거 및 쿼리할 수 있다고 나와있는걸 확인했습니다.You can add, remove, and query items in the cache from different threads without having to lock the cache yourself즉, 문서를 통해 자체적으로 thread safe 하게 뭔가를 하고 있구나..를 유추할 수 있기 때문에 (내부적으로는 NSLock 을 사용하고 있긴하지만요) 이럴때는 저희쪽에서 @unchecked Sendable 만 사용해도 무방할까요?하지만 이런 문서 등을 확인하지 않는 이상 정확히 NSCache 가 thread safe 하게 뭔가를 처리한다는걸 확인할 수 없기 때문에, 예시처럼 @uncheked Sendable 명시와 동시에 자체적으로 semaphore 나 lock 을 걸어줘야하는건지..? 앨런님은 어떻게 생각하시는지 궁금합니다!감사합니다~
-
미해결앨런 Swift Concurrency for Swift 6 (Part-2)
Task 클로저 내 `non-Sendable` 값 타입 접근 시, 캡처 리스트가 정의된 Task 순서에 따른 컴파일러 에러 차이
12강 16분 즘에, Task 의 클로저에 value type 의 프로퍼티를 캡처 리스트로 명시하면 아래와 같은 코드에서는 에러가 나지 않는다고 되어있습니다.struct ValueCounter { var value = 0 mutating func increment() -> Int { value = value + 1 return value } } func test() { var valueCounter = ValueCounter() Task { print(valueCounter.increment()) print(valueCounter.value) } Task { [valueCounter] in var newValueCounter = valueCounter print(newValueCounter.increment()) print(newValueCounter.value) } }하지만 제가 Xcode 26.0.1 에서 확인했을때는 해당 코드의 첫번째 Task 에서 다음과 같은 컴파일 에러가 발생했습니다Sending value of non-Sendable type '() async -> ()' risks causing data races이에 추가로 이것 저것 확인해보다가, 아래와 같이 캡처 리스트를 사용하는 Task 를 먼저 작성하면, 에러가 발생하지 않는것을 확인했습니다.// 캡처 리스트 사용하는 Task 순서 변경하니 정상 func test2() { var valueCounter = ValueCounter() Task { [valueCounter] in var newValueCounter = valueCounter print(newValueCounter.increment()) print(newValueCounter.value) } Task { print(valueCounter.increment()) print(valueCounter.value) } }이와 같은 현상을 어떻게 설명할 수 있을지 궁금합니다.첫번째 예시의 두번째 Task 에서는 [valueCounter] in 으로 현재 값을 캡처하려고해도, 이미 첫번째로 정의된 Task 에서 valueCounter.increment() 를 호출하면서 다른 스레드 (편의상) 에서 값을 변경하고 있기 때문에, 동일 시점에 딱 한개의 쓰레드에서의 접근이 깨져서 이런 에러가 발생하는 걸까요? (그렇다기엔 에러 위치는 첫번째 Task 정의에서 떠서... 아닌가 싶기도하고요..)두번째 예시의 첫번째 Task 에서는 캡처 리스트로 값을 캡처해서 valueCounter.increment() 를 호출하고, 두번째 Task 는 valueCounter.increment() 를 하려고해도 이 시점에서 valueCounter 를 참조하고 있는건 이곳 뿐이기 때문에 (첫번째 Task 에서는 캡처해서 사용), 동일 시점에 딱 한개의 쓰레드에서의 접근이 보장되어서 에러가 발생하지 않는걸까요? 결과를 기준으로 나름대로 고민을 해봤는데, 어쨌든 다 추측이라서.. 혹시 이와 같은 현상을 어떻게 이해하면 될지 궁금합니다감사합니다.
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
Task 의 default 우선순위 문의 (utility vs medium)
안녕하세요 3강 19:34 쯤에 Task 의 default 우선순위를 utility 로 말씀주셨는데, 제가 확인해봤을때는 medium 인 듯해서.. medium 이 맞을까요?Task.detached { print(Task.currentPriority) } // TaskPriority.mediumTaskPriority 의 구현부를 볼때도 default 가 medium 으로 되어있는것 같구요! @available(*, deprecated, renamed: "medium") public static let `default`: TaskPriority별로 중요한건 아니구, 나중에 한번 더 언급해주실거라고 하셔서 1부를 끝까지 들어봤는데, 별도로 말씀주시는 내용이 없는 듯 하여 가볍게 질문드립니다~감사합니다
-
미해결[Lv.3] 실전 네트워크 통신 - SwiftUI Combine, Async/Await
@Published의 용도
안녕하세요이번 실전 네트워크 통신 강의를 들으며 ViewModel을 만들 때 1번 방법이 아닌 2번 방법으로 만들 수 있다는 사실을 알게되었습니다. 앞쪽 강의에서 기존 1번 방식에서는 변수 생성 시 @Published 를 붙여야했지만, 2번 방식처럼 @Observable을 사용하면 @Published를 붙이지 않아도 된다고 하셔서 결국 위의 두 코드는 같은 기능을 한다고 이해를 했습니다. 그런데 이번 강의에서 아래와 같이 @Published를 사용하여서, 이 부분이 이해가 잘 가지 않습니다 혼자 고민해본 결과로는, - count 변수: 단순히 값을 읽고 쓰는 용도- textFieldID: 퍼블리셔(특정 시간/조건에 따라 값을 방출하는..?)로 사용 위의 용도 차이이다. 그렇다면 기존 방식에서 @Published를 모두 붙여야했던 이유는 무엇인지..? 라는 질문이 또 생기는 것 같습니다. 답변 주시면 감사하겠습니다!!
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
18강 자식 작업의 메타데이터 상속 관련 강의 자료 문의
안녕하세요~18강 8:35 쯤 강의자료에 자식 작업은 부모작업의우선순위 실행중인액터로컬변수를 상속한다고 되어있는데 이때 실행중인 액터에 취소선이 그어져있는 이유가 있을까요?혹시 의미가 있는건지...궁금합니다감사합니다
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
withCheckedContinuation 에서 resume 호출의 안정성 보장 질문
안녕하세요~ 강의 잘 보고 있습니다. withCheckedContinuation 에서 resume 호출에 대해 질문이 있습니다.11강의 12:56 쯤에서 'withCheckedContinuation 에서는 resume 을 두번 호출해도, runtime 에서 알아서 두번째 resume 되는걸 체크해서 크래시가 발생하지 않게 해준다고 공식문서에서 나와있다. 즉, 안전하다' 라고 설명을 해주시는데요 (12강에서도 Checked 는 안전하다고 나와있구요),제가 확인을 해보았을때는 withCheckedContinuation 에서도 resume 을 두번 호출하면 런타임 에러가 발생하는 것을 확인했습니다.다만 CheckedContinuation과 UnsafeContinuation 의 케이스별 에러 로그 / 콘솔 로그가 조금 달랐습니다.애플 문서에서는 checked continuation 만이 오용(misuse)에 대한 detect 및 diagnose 를 제공한다고 되어있는데요, 이를 통해 제가 유추할 수 있는건 withCheckedContinuation 은 런타임에 "안전" 하다기 보다는, resume 을 잘못 사용했을때 좀 더 자세한 에러 로그(여러번 호출시) 및 콘솔 로그(미호출시)를 통해 개발자가 resume 을 한번만 올바르게 호출할 수 있도록 도와주는 역할을 한다는 것이었습니다.혹시 withCheckContinuation 에 대해 강의해서 설명해주신 "안전" 의 의미와, "두번째 resume 되는걸 체크해서 크래시가 발생하지 않게 해준다" 의 문서 레퍼런스를 알 수 있을까요?참고로 체크한 코드는 다음과 같습니다func fetchGreeting(completion: @escaping (Result<String, MyError>) -> Void) { completion(.success("hello")) } enum MyError: Error { case bye } func asyncFetchGreeting() async throws -> String { let greeting = try await withCheckedThrowingContinuation { continuation in fetchGreeting { greeting in continuation.resume(with: greeting) // 📍 resume 호출부 } } return greeting } Task { do { let greeting = try await asyncFetchGreeting() } catch { } }감사합니다.
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
참고 코드 자료 7-StructuredConcurrency(102, 103줄) 오타?
자주하는 질문 모음링크: https://pointed-earwig-996.notion.site/Swift-Concurrency-for-Swift-6-Part-1-22eecb0b83154ac28c7c66446f6e54e1?pvs=4 자주하는 질문을 먼저 확인 부탁드리며, 질문은 최대한 구체적으로하셔야 빠르게 답변드릴 수 있습니다. [질문 예시](1) 몇강, 몇초의 내용이 잘 이해가 안갑니다. (제가 적어놓은 강의 번호 "10강, 7분 강의 내용 중에... " )(2) 강의자료 몇페이지의 내용이 잘 이해가 안갑니다.(3) 정확하게 어떤 포인트에 대한 내용이 이해가 안갑니다. 다시 설명해주실 수 있나요? 동시성 강의 코드파일 7번(7-StructuredConcurrency(102, 103줄) ) 내용인데요전체코드func fetchAsyncLetTwoImages() async throws -> (UIImage, UIImage) { let start = Date() /// 구조적 동시성 작업의 생성 (하위 작업의 생성) async let image1 = try await fetchImage(num: 1) async let image2 = try await fetchImage(num: 2) let images = try await (image1, image2) print(Date().timeIntervalSince(start).formatted(.number.precision(.fractionLength(1)))) return images } 이렇게 되어 있는데 async let image1 위치에서 fetchImage(num:1)의 값을 기다리는 형태입니다. 아마도 이렇게 바뀌는게 맞지 않나 싶습니다. /// 구조적 동시성 작업의 생성 (하위 작업의 생성) async let image1 = fetchImage(num: 1) async let image2 = fetchImage(num: 2) 그런데 위의 코드는 순서대로 결과값을 받는 경우이고 아래 코드는 동시에 돌려서 빨리 나오는 결과값이 먼저 반영되나 싶었는데 그것도 애매하네요.A코드 /// 구조적 동시성 작업의 생성 (하위 작업의 생성) async let image1 = try await fetchImage(num: 1) async let image2 = try await fetchImage(num: 2)B코드 /// 구조적 동시성 작업의 생성 (하위 작업의 생성) async let image1 = fetchImage(num: 1) async let image2 = fetchImage(num: 2)실제로 두 코드를 돌려보니 A코드는 4.8 ~ 3.2초 걸렸는데 대략 평균적으로 3.6초 걸리고, B코드는 4.2 ~ 2.5초 걸려서 대략 평균적으로 3.3초 정도 걸리더라구요.image1과 image2는 try await Task.sleep(for: .seconds(2))가 걸려 있어 각각 2초씩이어서 동시에 돌아가서 결과를 받으면 2초 조금 넘을 것 같고 순서대로 받으면 4초 조금 넘을 것입니다. 그런데 두 코드 모두 4초가 안걸리는 경우가 많았다는 점에서 동시성이 적용되는 것 같습니다. 그런데 어떤 속도의 차이가 있을까 했는데 별 차이가 없는 느낌이기도 합니다.Claude에게 물어보니 A코드가 동시성의 효과를 보지 못한다고 하지만 속도 면에서 B코드가 특별히 빠르다고 단정하기도 애매해서요. 오타라고 생각되지만 A코드는 어떤 의미가 있을까 궁금합니다.
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
3강 스레드 제어권 관리 질문드립니다
안녕하세요!3강 6분 30초 부터 “func2가 스레드 제어권을 운영체제에 양보했다가 func2의 실행이 끝나면 재개되고 함수가 리턴” 이라는 내용이 나오는데요.관련하여 아래 세 가지 문의 드립니다.재개 <- 운영체제에서 func2로 스레드 제어권이 돌아온다는 건가요? 어떤 의미로 쓰인 말인지 궁금합니다.func2가 리턴 될 때 스레드 제어권이 func2에 있었다면(1의 상황), 리턴 시점에 제어권도 func1로 돌아가는 게 맞나요? 결과적으로 func2가 리턴 될 때 스레드 제어권이 func2에서 func1로 양도되는 것은 GDC / Swift Concurrency 동일한건가요? 답변 주시면 감사하겠습니다. (_ _)
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
10번 강의 관련하여 질문드립니다.
안녕하세요. 10강 내용 중 질문이 있습니다.6분 20초 정도 시간대인데요.위 코드의 106 line에서의 쓰레드가 2번 쓰레드일 경우, doSomething() 메서드 자체가 2번 쓰레드에서 실행된다고 말씀하셨는데요. doSomething() 메서드가 2번쓰레드에서 실행될지, 다른 쓰레드에서 실행될지는 알 수 없는 것 아닌가요? 즉, async 메서드가 실행되는 쓰레드와 await 호출 직전 시점의 쓰레드가 반드시 같지는 않을 수도 있는 것으로 알고있어서 질문드립니다. 감사합니다.
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
ImageProject 관련 문의
안녕하세요,ImageProject 관련하여 설명해주신 내용 중 궁금한 점이 있어 문의드립니다. 1.await imageDownloader.cache.keys.contains(url)와let keys = await imageDownloader.cache.keysif keys.contains(url)중에후자를 사용하는 것이 액터 재진입에 있어 더욱 안정적인 코드라고 말씀하셨는데요액터 재진입 관점에서는 전자의 코드가 actor isolated 된 상태에서 contain 여부까지 확인이 되고후자의 코드는 key값을 가져오고 contain 여부를 판단하는 시점에 actor에 다른 태스크가 진입하여 값이 변경될 가능성이 있기 때문에전자가 더 안정적인 코드가 아닌가요?어떤 이유 때문에 후자의 코드를 사용해야하는지 다시 설명해주시면 감사하겠습니다. 2. privatelet storage = DiskStorage()의 코드는Global actor 'ImageDatabase'-isolated default value in a actor-isolated context오류를 발생시켜 storage를 추후에 초기화 하도록 수정해주셨는데요privatelazyvar storage = DiskStorage()으로 하면 storage가 호출되는 시점에 초기화 되니 그 때에는 이미 actor의 격리영역이 정해진 상태라 이상이 없을 것이라고 생각했습니다만같은 오류가 발생하더라구요.@ImageDatabase 로 격리되어 있기 때문에 비동기적으로 초기화되어야 하지만 그렇지 못하기 때문에 오류가 발생하는 것인지 궁금합니다. 감사합니다.
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
비동기 반복문은 하나의 thread에서만 동작하게 되나요??
안녕하세요.강의를 통해 많은 도움을 받고 있습니다.!다름이 아니라, 17강 - 11분20초 부분에서 "작업의 결과를 모을 때는 하나의 thread에서만 동작하게 됩니다. 예를 들자면 2번 thread 하나에서만 비동기 반복문이 동작하게 되는거에요" 라고 설명을 해 주셨는데제가 이해한 바로는 'Swift Concurrency는 thread관점에서 벗어나서, Task라는 작업의 단위를 기준으로 비동기 관리를 한다' 라고 이해하고 있습니다.때문에, "비동기 반복문에서도 await을 통해 비동기 결과를 받고 있는데, 이 때 특정 thread에 고정된다는 것이 보장 될 수 있는건가?" 하는 궁금증이 생겨서 질문드립니다!만약 하나의 thread에 고정되어 있다면, group을 통해서 결과가 넘어오게 될 때, 자식 Task중 과도하게 오래걸리는 작업이 있다고 가정하면 비동기 반복문이 실행되는 특정 thread가 계속 blocking되는건가? 하는 의문이 들어서요,,,!
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
10강 내용 문의드립니다.
안녕하세요. 10강 내용 중 8분 40초 즈음에Task 가 여러 개일 경우 '동일 시점에 여러개의 쓰레드에서 접근/사용 가능성이 있기 때문에' Race Condition이 발생할 수 있다고 설명해주셨는데요. 교재 10p에서 'Task는 현재 실행중인 컨텍스트의 메타데이터를 그대로 상속해서 사용'한다고 설명하고 있고, 메타데이터에는 '실행 액터'도 포함되어 있어서(현재 시점에서는 액터를 쓰레드와 비슷한 의미로 이해하고 있습니다.) , Task 클로저 외부와 내부의 액터(~= 쓰레드?)는 같을 것 같은데요. 이로 미루어 봤을 때, Task 1과 Task 2의 클로저가 실행되는 쓰레드도 같아야 할 것 같다는 생각이 들더라구요.(swift 6모드에선 비동기 컨텍스트에서 Thread.current에 접근할 수 없어 swift 5 모드로 실행했습니다.)간단히 테스트 해봤을 때 위처럼 두개의 Task의 클로저가 실행되는 쓰레드가 메인 쓰레드로 같은데요. 이는 test() 메서드가 MainActor에서 실행되기에(@MainActor) 이와 같은 쓰레드에서 실행되는 것으로 이해했습니다.제가 잘못 이해하고 있는 지점이 있을 것 같은데요. 뒷 내용을 아직 듣지 못했지만 참지 못하고 질문드려봅니다! ㅎㅎ 답변해주시면 감사하겠습니다!
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
강의를 들으면서 생긴 질문이 있습니다.
안녕하세요, 강의를 들으면서 생긴 질문 세가지가 있습니다. 답변해주시면 너무 감사드리겠습니다. Swift Concurrency에서 NSLock을 사용해도 되나요? 이전 GCD환경에서 데이터 동기화를 위해 사용하던 NSLock을 Swift Concurrency환경에서 사용해도 같은 효과를 볼수있을지 궁금합니다. Actor에 접근할때는 await으로 Task를 따서 비동기적으로 접근하는데, 기존에 비슷한 참조타입인 class에 접근하는것보다 속도가 느려지는 이슈는 없을까요? cpu에서 context switch를 하면, 저장하고 로딩하는 동기화하는 작업때문에 컨텍스트 스위칭이 비효율적이다 라고 설명해주셨는데, 그럼 swift concurrency에서는 task가 다른 cpu(다른 쓰레드) 에서 재개되는것도 동기화작업이 필요할텐데 이 작업은 비싼(?) 비효율적인 작업이 아닌가요? 질문이 조금 많은데.. 액터까지 강의 들으면서 궁금했던 점이라 부탁드립니다.
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
협력적 쓰레드 풀에서 쓰레드 운영방식 질문 드립니다.
3강 협력적 쓰레드 풀에서 쓰레드 운영방식에 대한 질문 드립니다.보통 컴퓨터 살때 8코어, 10코어..이런식으로 얘기 하잖아요..CPU당 하나의 쓰레드를 만들어서 운영한다면 Swift Concurrency에서는 8개, 10개 정도의 쓰레드만 가지고 동작하는건가요?
-
미해결떠먹는 자바스크립트 비동기
교안 어디있나요?
교안 어디서 다운받나요?
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
25.05.13일 업데이트 내용 문의
안녕하세요. 25.05.13일 업데이트 표시됐는데 어떤 부분인지 알 수 있을까요?
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
5강 weak self 처리 관련
안녕하세요! Swift Concurrency 강의 너무 잘 듣고 있습니다. 5강 weak self 처리 관련하여 질문이 있는데요. Task 클로저의 경우 작업이 끝나는 즉시 내부 클로저가 소멸하기 때문에 클로저 내부에서 캡처된 참조도 해조되어 강한 순환 참조가 발생하지 않는다고 나와있는데, 이는 DispatchQueue에 전달하는 클로저에서도 마찬가지 아닌가요? DispatchQueue에 전달된 클로저도 작업이 완료되면 메모리에서 해제되는 것으로 알고 있어서, 약간의 혼란이 생겼습니다. DispatchQueue.global().async { self.processData() // self를 강하게 캡처 } // 클로저가 실행되는 동안 self가 메모리에 유지되며, 작업 완료 후 클로저가 해제되면 self도 참조카운트가 감소되는 것으로 알고 있습니다.그렇다면 Task와 DispatchQueue의 차이점은 'self를 명시적으로 쓸 필요 없다/써야 한다' 뿐이지, 순환 참조 관련된 처리는 다르지 않지 않을까 하는 고민인데요. 뭔가 제가 잘못 이해한 부분이 있을 것 같습니다. 혹시 이 부분 설명 해주실 수 있으실까요? 더불어서 Task를 쓰면 순환 참조가 생길 가능성 자체도 없는 것인지도 궁금합니다. 감사합니다 😊
-
미해결떠먹는 자바스크립트 비동기
교안 어디서 찾아야하나요?
동기와 비동기 개념동기와 비동기 개념이전다음봤어요커리큘럼질문&답변노트채팅스크립트 질문&답변제목
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-1)
Task 클로저에서 weak self 미사용에 대해 질문드립니다!
양질의 강의 만들어주셔서 업무에 많이 도움이 될 것 같아 먼저 감사의 말씀 드립니다:)'Task(작업)과 self 사용 관련 및 캡처리스트의 weak self 사용 (5강)'에서 질문 드립니다. Task 클로저 내에서 weak self를 생략하더라도 메모리 누수 관점에서 문제가 없는 것은 이해가 되었습니다!다만, GCD를 Task로 전환하는 관점에서 생각해봤는데요 'self가 해제된 시점'에서 'weak self 사용'한 'GCD 클로저'에서는 내부 동작이 실행되지 않을 것 같은데요'self가 해제된 시점'에서 'weak self 미사용'한 'Task 클로저'에서는 내부 동작의 실행이 보장될 것 같은데 제가 이해한게 맞을지 궁금합니다!길지 않은 비동기 작업이더라도 미세하게 동작 차이가 발생할 수 있는 부분이 아닐까 싶어 질문 드렸습니다!
-
해결됨앨런 Swift Concurrency for Swift 6 (Part-2)
안녕하세요. 액터 홉핑 관련 질문드립니다.
📌자주하는 질문 모음링크: https://pointed-earwig-996.notion.site/Swift-Concurrency-for-Swift-6-Part-2-1acbbab5ec9280a0944edcab00e027dd?pvs=4 자주하는 질문을 먼저 확인 부탁드리며, 질문은 최대한 구체적으로하셔야 빠르게 답변드릴 수 있습니다. [질문 예시](1) 몇강, 몇초의 내용이 잘 이해가 안갑니다. (제가 적어놓은 강의 번호 "10강, 7분 강의 내용 중에... " )(2) 강의자료 몇 페이지의 내용이 잘 이해가 안갑니다.(3) 정확하게 어떤 포인트에 대한 내용이 이해가 안갑니다. 다시 설명해주실 수 있나요 안녕하세요. 실행자(executor)와 액터 홉핑(actor hopping)과 관련해 질문이 있어요. 액터에 실행자 개념까지 나오니 너무 헷갈리네요. 액터 홉핑이 일어나게 되면, 액터에 내장되어 있는 실행자(executor)가 전환되고, 이는 곧 액터에서 실행되는 스레드의 묶음이 바뀌는 것이니, (실행 컨텍스트는 물론) 스레드 컨텍스트 스위칭이 일어날 수 있다라고 보는 게 맞을까요? 메인 액터-일반 액터 간 홉핑은 메인 스레드와 협력형 스레드 풀 간의 전환이니, 실행 컨텍스트와 스레드 컨텍스트 전환이 무조건 일어나는 게 맞을까요? 일반 액터-일반 액터 간 홉핑은 모두 Swift 동시성이 기본으로 제공해주는 직렬 실행자(serial executor)에서 실행되고, 이 직렬 실행자는 협력형 스레드 풀에서 실행되는 것이니, 실행 컨텍스트 전환은 일어날 수 있어도 스레드 컨텍스트 스위칭은 일어날 수도 있고, 일어나지 않을수도 있다고 보는 게 맞을까요?(플레이그라운드에서 Thread.current로 찍어 실험을 해봤을 땐, 모두 동일한 스레드에서 실행되는 걸로 보입니다)