안녕하세요, 강사님! 학습 내용과는 조금 다른 질문이라 조심스럽지만, 진로에 대한 고민이 커서 여쭤보고 싶습니다. 저는 보안학과에 재학 중이고, iOS 개발자로 취업하는 것을 목표로 하고 있습니다. 다만 최근 개발 시장이 전반적으로 어려워졌다는 이야기를 많이 접하고 있습니다. 특히 iOS 분야는 신입 채용이 매우 적고, 국내뿐 아니라 해외에서도 경력자들의 이직이 예전보다 쉽지 않다는 의견을 자주 보게 되었습니다. 또한 "지금 다시 시작한다면 iOS보다 백엔드로 커리어를 시작했을 것 같다"거나, "2026년 기준으로는 네이티브 앱 개발을 추천하지 않는다"는 현업 개발자들의 의견도 적지 않게 봤습니다. 물론 인터넷 의견이 전부 정답은 아니라는 것도 알고 있지만, 진로를 결정해야 하는 입장이다 보니 이런 이야기들을 접할 때마다 많이 흔들리게 됩니다. 그래서 강사님께 여쭤보고 싶습니다. 현재 시장 상황을 고려했을 때도 iOS 개발자를 목표로 준비하는 것이 충분히 의미 있는 선택이라고 생각하시는지, 혹은 지금 개발을 시작하는 입장이라면 미래를 위해 백엔드와 같은 다른 분야도 함께 고려해 보는 것이 좋다고 생각하시는지 강사님의 의견을 듣고 싶습니다. 강사님의 경험을 바탕으로 현실적인 조언을 해주시면 정말 감사하겠습니다.
Static property 'coin' is not concurrency-safe because it is nonisolated global shared mutable state 위와같은 에러가 아래의 코드에서 발생합니다. class KBank { static var coin: Int = 0 } 제시되는 해결법중 메인액터를 붙여서 해결은 하였는데요, 위와 같은 현상에 관한 것은 어떤 키워드로 학습하면 좋을지 여쭤봅니당 class KBank { @MainActor static var coin: Int = 0 } var regionOneKBank = KBank() var regionTwoKBank = KBank() var regionThreeKBank = KBank() KBank.coin class Banker { @MainActor func plus() { KBank.coin += 1 } @MainActor func minus() { KBank.coin -= 1 } } var banker1 = Banker() var banker2 = Banker() banker1.plus() banker1.plus() banker1.plus() banker1.plus() banker1.plus() banker1.plus() banker2.minus() KBank.coin // 다만 생성된 인스터스에서 바로 코인에 재할당하는것은 안되더라구요ㅠ_ㅠ
되게 간단한 질문인데요, 클로저 파트를 듣다보면 "함수를 클로저 형태로 만들었다.."라는 말씀을 종종 하시는데 이걸 콜백함수를 받는 함수라고 보면 되겠죠...? 함수자체는 콜백을 받는 함수로 만들고, 실행하는 구문쪽에서 클로저의 형태로 실행했다 =>로 이해해도 되려나요?.?
cal라는 함수를 옵셔널로 타입지정만 해둔채로, 타함수에서 다른 함수를 할당시키는 예제가 제 에디터에서는 에러가 나는데 원인 알 수 있을까요 func plus(n1: Int, n2: Int) -> Int { return n1 + n2 } func minus(n1: Int, n2: Int) -> Int { return n1 - n2 } var calc: ((Int, Int) -> Int)? func selectButton(type: String) { if type == "+" { calc = plus } else if type == "-" { calc = minus } } var target1 = 1 var target2 = 3 func showResult() { calc?(target1, target2) } Main actor-isolated var 'calc' can not be mutated from a nonisolated context ?
코드는 그대로인데 강사님의 화면과 조금 다르게 생겨서 질문 드립니다. 디자인이 달리보이는건 어떤 원인일까요? 이 모양 그대로 앱이 나오는거라면, 기본제공 라이브러리 뷰의 디자인을 균일하게 리셋을 시키는 방법이 있을까요? 화면에서 이모지가 네모박스안에 물음표로 나오고있는데 이것도 혹시 원인 알 수 있을까요?
4-4 강의에서 fetchSite() 부분에서 import SwiftUI import Combine func fetchSite() -> AnyPublisher<Data, URLError> { guard let url = URL(string: "https://www.naver.com") else { return Empty<Data, URLError>().eraseToAnyPublisher() // return Fail<Data, URLError>(error: URLError(.badURL)).eraseToAnyPublisher() } let publisher = URLSession.shared.dataTaskPublisher(for: url) .map { data, response in return data } .eraseToAnyPublisher() return publisher } let subscription = fetchSite() .sink { completion in switch completion { case .finished: print("완료") case .failure(let error): print("에러: \(error.localizedDescription)") } } receiveValue: { data in print(data) } print("123") 위와 같은 코드 실행 시 콘솔에 아무것도 안뜹니다 print("123") 은 잘 뜨는데 왜 그럴까요?
강의에서는 5:16 쯤 "프로토콜 자체가 글로벌 격리로 구현된 프로토콜의 경우, 동일한 소스파일(.swift) 인 경우 확장에 프로토콜을 채택하더라도 타입 전체가 글로벌 격리로 추론됨" 이라고 설명합니다. 하지만 동일한 파일에서 아래와 같이, extension 에 protocol 을 채택해도 타입 본체는 nonisolated context 인것을 확인할 수 있었습니다. @MainActor protocol GloballyIsolated { func updateUI(vc: UIViewController) } struct S { func updateUI2(vc: UIViewController) { // nonisolated context vc.view.backgroundColor = .red // 💥💥💥 Main actor-isolated property 'backgroundColor' can not be mutated from a nonisolated context } } extension S: GloballyIsolated { func updateUI(vc: UIViewController) { // MainActor context vc.view.backgroundColor = .red } } 설명하시는 시점과, 현재 시점의 동작이 달라진건가? 생각도 했었는데, 강의 6:58 쯤에서는 "GlobalProtocol 을 확장해서 채택한다하더라도 본체 자체는 비격리 된다" 라고 설명합니다. 즉, 동일한 코드에 대해 설명이 다른듯 하여 조금 헷갈리는 부분이 있어서요! 현재 시점에는 "프로토콜 자체가 글로벌 격리로 구현된 프로토콜의 경우, 동일한 소스파일(.swift) 인 경우 확장에 프로토콜을 채택하더라도 타입 전체가 글로벌 격리로 추론되지 않음 " 으로 동작하는것 같은데 이 부분에 대한 강의 자료가 잘못된건지, 아니면 제가 놓친 부분이 있는지 궁금합니다. 감사합니다.
강의 내용중에 아래 2가지가 가능하다고 했는데 1번 TextField("이메일을 입력해주세요", text: $store.email.sending(\.inputEmail)) 2번 TextField("이메일을 입력해주세요", text: Binding(get: { store.email }, set: { email in store.send(.inputEmail(email)) })) 2번 코드로 작성후 이메일주소를 수정후 저장 하니깐 아래와 같은 에러가 발생했습니다. A "forEach" at "AppStore/MyPageReducer.swift:96" received an action for a missing element. Action: MyPageStackReducer.Action.email(.inputEmail) This is generally considered an application logic error, and can happen for a few reasons: A parent reducer removed an element with this ID before this reducer ran. This reducer must run before any other reducer removes an element, which ensures that element reducers can handle their actions while their state is still available. An in-flight effect emitted this action when state contained no element at this ID. While it may be perfectly reasonable to ignore this action, consider canceling the associated effect before an element is removed, especially if it is a long-living effect. This action was sent to the store while its state contained no element at this ID. To fix this make sure that actions for this reducer can only be sent from a store when its state contains an element at this id. In SwiftUI applications, use "NavigationStack.init(path:)" with a binding to a store. 확인해 보니 pop시 Binding의 set 이 호출되서 이미 path에 없는 element를 호출하게 되서 해당 에러가 발생한다고 합니다. 그럼 TCA 환경에서는 1번을 꼭 사용해야 하는 건지요 ?
강의 내용처럼 EditNameReducer 에서 ReducerOf<Self> 를 사용하면 아래처럼 에러가 발생합니다. @Reducer 매크로 타입은 빌드시점이 되야 EditNameReducer 가 Reducer 타입인걸 알기 때문에 사용 못한다고 하는데 강의는 에러 없이 되서 xcode26.2 에서 왜 안되는지 알고 싶습니다. 결국 아래처럼 변경해서 사용 했습니다. var body: some Reducer <State, Action> { Reduce { state, action in return .none } }
18분 55초 쯤 예시를 보여주시면서 /// 2) (액터 외부) 동기 코드가 (일반) 비동기 코드에서 불려지면.. 어떤 (컨텍스트에 해당하는) 작업(Task) 내부에서 실행됨 ===> Task 격리 func readAny(with books: [Book]) async { if let book = books.randomElement() { readBook(with: book) } } Task { let account = LibraryAccount(idNumber: 1) /// 액터 let books = await account.booksOnLoan /// 액터내부의 (상태)값 가져오기 (Sendable타입) await readAny(with: books) /// 액터 외부에서 실행 (일반적으로 이런 구현은 옳지 않음) } 위와 같은 코드에서 Task 가 3번 스레드에서 돌아가고 있었다고 치면, account.booksOnLoan 을 가져오는건 2번 스레드로 갈 수 있다고 쳐도, await readAny(with:) 는 다시 Task 와 동일한 3번 스레드에서 돌아가게 될거다! 라고 말씀을 주셨는데요 사실 nonisolated async func 인 readAny 를 await 로 호출을 하는 순간, 해당 함수는 Task 의 스레드는 무관한게 아무 스레드에서나 돌아갈 수 있다고 생각을 했습니다 (실제로 Task 를 mainActor context 로 두고 해당 코드를 돌려봐도 readyAny 는 main 에서 돌아가지 않았구여!) 이해 편의상 돌려서 설명을 주시건가??? 생각이 들지만 혹시 정확히 어떤 의미로 요렇게 말씀을 주신건지 궁금합니다~ 감사합니다 저장
안녕하세요 앨런님! 19강에서 actor 를 이해 편의상 "특정한 하나의 스레드에서만 실행"되는 것처럼 그림을 첨부해주셨는데요 (물론 설명으로는 한번에 하나씩 실행되는건 맞지만 스레드를 특정할 수는 없다" 라고 말씀주셨지만요) 그러면 이해 편의상이 아니라, 좀 더 정확하게는 제가 이해한 내용이 맞을지 한번 확인 부탁드려도 될까요? 항상 좋은 강의 감사합니다. 기본적으로 actor 의 실행 스레드는 고정되어있지 않음 Actor 의 메서드나 데이터에 접근하면 serial executor 에 작업이 쌓일 것 이때 actor 는 선입 선출로 동작하지 않으므로, 우선 순위에 따라 실행 순서는 달라질 수 있음 Actor 의 작업이 실행될때 어느 스레드에서 실행할건지는 상관없고 중요하지도 않음. 한 작업이 끝나고 나면, serial executor 에서 대기하고 있던 작업은 어느 스레드에서든 heap 에 저장된 데이터 읽어서, 그 데이터 기반으로 작업하면 되는 것. 중요한건 데이터 경쟁이 발생하지 않게 serial executor 에 넣어서 actor 관련 작업은 한번에 하나의 스레드에서만 돌게 하는 매커니즘
actor TaskImageDownloader { /// (다운로드) 상태를 저장하기 위한 열거형 정의 enum DownloadState { case completed(UIImage) case loading(Task<UIImage, Error>) case failed } private(set) var cache: [String: DownloadState] = [:] func image(from url: String) async throws -> UIImage { /// 기존에 저장된 상태가 있는 경우 (캐시 먼저 확인) if let cachedState = cache[url] { switch cachedState { case .completed(let image): /// 이미지 리턴 return image case .loading(let task): /// 작업(Task)을 기다렸다가 ===> 이미지 리턴 return try await task.value case .failed: throw "이미지 다운로드 실패" } } /// 작업(Task)을 생성 let task = Task.detached<UIImage, Error> { let image = try await downloadImage(from: url) return image } /// 일단 (완료되지 않은) 작업 상태를 보관 cache[url] = .loading(task) do { /// 작업의 완료를 기다렸다가 ===> 완료되면 ===> 완료상태(이미지)로 바꿔서 보관 let image = try await task.value cache[url] = .completed(image) return image } catch { cache[url] = .failed // 에러 발생의 경우 throw "이미지 다운로드 실패" } } } Actor에서 Task vs Task.detached 사용 시 재진입 문제 질문 안녕하세요. 위 코드에서 Task.detached 대신 일반 Task {} 를 사용하면 재진입 문제가 발생할 수 있을 것 같아 질문드립니다. 제가 이해한 바로는: Task {} 를 사용하면 생성된 작업이 actor의 context를 상속받아 serial executor에서 실행됩니다 다운로드 작업 중 await 를 만나면 actor가 suspension되고, 이 시점에 다른 스레드에서 해당 actor에 재진입할 수 있습니다 따라서 동일한 URL에 대해 중복으로 다운로드 작업이 생성될 가능성이 있습니다 반면 Task.detached 를 사용하면 actor context와 독립적으로 실행되어 이러한 재진입 문제를 방지할 수 있는 것으로 이해했습니다. 제가 이해한 내용이 맞는지 확인 부탁드립니다. 감사합니다.