게시글
질문&답변
2024.10.29
warning 뜸
네네 미래 님!그런 에러 메세지가 뜨는게 정상입니다 ! 잘못 구현되어 있기 때문에(?) (즉, 쓰레드 세이프하지 않게 구현되어 있기 때문에) 그 것을 아래의 changeNameSafelyPrintRightly() 코드로 고치면 되는 내용이기 때문에, 에러 메세지가 뜰 수 있어요. 궁금하신게 있으시면 또 언제든지 질문 주세요 ! 고맙습니다 :)
- 0
- 1
- 27
질문&답변
2024.10.23
강의자료 관련 질문
안녕하세요 Hamp 님.죄송하지만... 강의 자료만 필요하고, 교재는 필요하지 않다는 말씀이 무슨 말인지 제가 잘 이해하지 못했어요. ㅠ강의자료(코드자료)는 있으면 좋지만, 굳이 교재(강의 요약정리PDF)는 필요하지 않다는 말씀이신가요? 저도 아직 Part-2를 어떻게 해야할지 여전히 고민중이긴 하지만 ㅠㅠ, 현재로써는 교재(강의 요약정리 PDF) 파일은 따로 교재로 판매하고, Part-2 수업에서는 코드 자료만 제공할 생각이긴 합니다.(Part-2 강의를 구매하시면, 반드시 교재 자료 (PDF정리자료)는 구매할 필요가 없긴 합니다. 코드 자료는 드리니까요.) 궁금하신 부분이 해결 되셨는지 모르겠네요.. 🥲 고맙습니다. :)
- 0
- 2
- 84
질문&답변
2024.10.21
Task.sleep Non-blocking 추가 질문
안녕하세요. ekth020216 님.약간 내용을 다른 관점에서 잘 못 생각하신 것 같은데, 쓰레드가 비어있는 동안 다른 일처리를 우리가 직접적으로 시키는 것은 불가능해요. 예를 들어 2번 쓰레드에서 try await Task.sleep(for: .seconds(5)) 함수를 통해 5초동안 쓰레드를 양보한다고 말씀드렸는데, 해당 2번 쓰레드는 비어있는 동안 시스템(운영체제)이 알아서 비어있는 동안의 쓰레드를 사용한다는 의미이기 때문에.. 직접적으로 비어있는 동안 어떤 일처리를 시키겠다?는 이런식의 예제는 당연히 불가능합니다.쓰레드를 양보해서, 운영체제에게 맡겼으니.. (다른 처리해야할 작업들이 있다면) 운영체제가 알아서 처리한다는 게 핵심이예요. 그래서 큰 의미는 없지만, 예시로 한번 말씀드려보면,func performAsyncWork() async { print("작업 시작") Task { // 작업 생성 try await Task.sleep(for: .seconds(5)) // 5초 sleep print("Sleep 작업 완료 후 실행됨") } for i in 1...100000 { Task { // 또다른 작업 100,000개 생성 ⭐️ print("비동기 작업 중: \(i)") } } print("작업 종료") } Task { await performAsyncWork() }이런 코드가 있다고 치면, performAsyncWork라는 비동기 함수를 실행하면, 이 비동기 함수 안에서 작업을 생성하고 (제가 “작업 생성”이라고 주석해 놓은 부분) 5초동안 멈췄다가 그 다음 중 코드인 “Sleep 작업 완료 후 실행됨”을 출력할꺼예요. 그런데 작업(Task)을 생성하는 하단에 보시면, 반복문이 있는데, 반복문 내부에서 10만번의 비동기 작업을 또 생성하면서 출력을 합니다. (반복문 안에서 비동기 작업을 생성한다는 것은.. 뒤에 강의에서도 자세하게 또 말씀드리지만, 병렬적인 작업의 생성이죠.) 따라서 Task.sleep은 Non-blocking방식으로 실행된다고 말씀드렸는데, 예를 들어 만약에 2번 쓰레드 였다고 가정하면, (5초동안) 2번쓰레드를 양보한 동안에.. (2번 쓰레드는 지금 당장 할 일이 없으니) 아래 있는 반복문의 10만개의 여러 작업이니.. 반복문의 코드들이 2번 쓰레드를 사용할 수 있다는 것이죠. 결국, 이 Task(비동기 작업)를 생성하고 그 내부에서 동작하는 Non-blocking 방식은.. GCD와 마찬가지로 우리가 직접적으로 쓰레드를 사용하는 방식이 아니고, 추상화 되어 있어서.. 우리는 내부적으로 한차원 높은 관점에서 비동기적인 일처리를 Task로 보내면, Task내부에서 알아서 쓰레드를 양보하기도 하고, 사용하기도 하면서 운영체제와 알아서 소통하면서 처리해준다는 것이예요. 잘 생각해보시고, 이해가 안되시면 다시 질문 주세요 ! 고맙습니다. :)
- 0
- 2
- 86
질문&답변
2024.10.18
(기초-3) 가위바위보 앱 만들기 / 코드 설정하기 - 2 (기초 앱 12강)
네 안녕하세요! 민경님!아, 리셋버튼을 누르셨을때 viewDidLoad() 자체를 호출하시면 안됩니다.왜냐면, 내부적인 메커니즘 때문에.. 보시면 아시겠지만.. 또 상위 클래스의 super.viewDidLoad( ) 도 호출하고 있죠? (내부적으로 viewDidLoad의 경우.. 화면에 진입했을때 앱 내부에서 자동으로 알아서 한번만 호출되게 되어있고, 직접 호출하는 것은 (동작할지라도) 금지되어 있습니다.따라서, 아래처럼 셋팅 관련 코드들을 묶어서 메서드를 만들어 주시고,func reset() { mainLabel.text = "선택하세요" comImageView.image = UIImage(named: "ready.png") myImageView.image = UIImage(named: "ready.png") comLabel.text = "준비" myLabel.text = "준비" }viewDidLoad에서도 호출되게 만들고..override func viewDidLoad() { super.viewDidLoad() reset() }RESET 버튼을 눌렀을 때도 reset( )을 따로 호출해주는 것이 맞습니다. :) 고맙습니다. :)
- 0
- 1
- 36
질문&답변
2024.09.20
(기초-3)가위바위보 앱 만들기 - 기초 앱 강의 12강 질문입니다!
아, 네 HanMa_Man 님.아마, UIButton 스타일 때문에 문제가 발생하실꺼예요! (엑스코드 기본 설정이 바뀌어서요. 아마 이 내용을 제가 자주하는 질문에 넣어놓긴 했어요.)스토리보드에서 UIButton 스타일을 Default로 바꾸시면.. currentTitle 속성을 잘 가지고 오실 수 있으실 꺼예요. 버튼 스타일의 문제로 보여요.혹시 안되시면 다시 남겨주세요. 감사합니다 :)
- 0
- 2
- 44
질문&답변
2024.08.27
타이머 앱 강한 참조 사이클 해결 방법에 있습니다!
아, 네 저도 그림을 그려서 다시 생각해보니까..제가 좀 잘 못 설명을 드린 것 같네요!결론부터 말씀드리면, 이 경우에, 클로저 내부에서 반드시 [weak self]로 사용하셔야 (어떠한 경우에도) 메모리 누수가 발생하지 않습니다. 일단 중요한 포인트가 한개 있는데, Timer객체의 경우,Timer를 생성하고 스케줄링하면, Timer 객체는 런루프(RunLoop)에 추가되기 때문에, 런루프가 Timer에 대한 강한 참조를 유지하게 됩니다. (런루프가 강하게 가리키게 됩니다.) (런루프 관련된 내용은 뒤쪽 강의에서 설명해 드려요.)(쉽게 말씀드리면, Timer라는 객체는 그 특성이 자체가.. 내부적인 메커니즘에 의해 그 객체가 계속 반복적으로 동작하게 하기 위해서 관리되는 내부적인 동작구조가 있어서, 그것이 Timer를 관리하게 됩니다.)그림으로 대략 생각해보자면, 아래 그림과 같다고 보시면 됩니다.(사진)런루프라는 내부 메커니즘에 의해 Timer가 관리 된다는 것이죠.따라서, Timer의 경우 (반복적으로 사용하기로 했다면)속성을 var로 선언하던, weak var로 선언하던 지 간에 상관없이 timer를 invalidate시키기 전까지는 timer객체가 메모리에서 살아 있을 수 밖에 없습니다.(invalidate()를 호출해야만 Timer객체가 해제가 됩니다.)아래의 그림과 같은 방식으로 런루프에 의해서 관리가 되니, timer속성을 var로 선언하던, weak var로 선언하던 일단 Timer객체는 메모리에 있을 수 밖에 없습니다.(사진)그리고 Timer에서 클로저를 사용하기 때문에... 또 Timer가 내부에서 클로저를 소유하게 됩니다.(사진)따라서, 이 경우에 클로저 내부에서 self를 사용할때 weak self로 사용하지 않으면 메모리 누수가 발생할 수 있는 가능성이 높아집니다.(사진)위의 그림을 보시면 아시겠지만,Timer의 invalidate( ) 메서드를 호출하면 타이머가 해제가 될 수 있지만, invalidate( ) 을 호출하지 않는 경우엔 무조건 메모리 누수가 발생할 수 밖에 없으니, 가능성을 완전히 배제하시려면.. [weak self]로 선언해 주는 것이 맞습니다.(invalidate을 호출하지 않으면, Timer도 살아있고, 클로저도 살아 있고, 클로저가 강하게 가리키는 뷰컨트롤러도 해제가 되지 않기 때문에, 이 경우 무조건 [weak self]로 선언하는게 더 맞다고 보시면 됩니다.)따라서, Timer객체를 사용해서, 클로저에서 반복하는 코드를 사용하는 경우, 아래의 코드처럼 weak self와 옵셔널 바인딩 (guard let self = self else)을 같이 사용하시면 됩니다.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in guard let self = self else { return } if number > 0 { number -= 1 print(Float(number) / Float(60)) slider.value = Float(number) / Float(60) mainLabel.text = "\(number) 초" } else { mainLabel.text = "초를 선택하세요" number = 0 timer?.invalidate() AudioServicesPlaySystemSound(SystemSoundID(1000)) } }(사진)그래서 결론적으로는,(1) 타이머의 경우 런루프라고 하는 내부적인 메커니즘에 의해 관리 되므로 사실 정확하게는 var로 선언하던, weak var로 선언하던 크게 상관은 없고요. (어차피 var로 선언하더라도, invalidate 메서드 호출시 Timer는 무조건 해제가 될테니까요.)(2) 그런데, 클로저에서는 [weak self]로 선언을 안해주면 Timer에서 invalidate를 호출 안해주면 무조건 뷰컨트롤러까지 해제가 안되어서 메모리 누수가 발생할 수 있으니, 클로저에서는 반드시 [weak self]로 선언해주셔야 합니다.혹시나, 궁금증이 있으시면, 추가적으로 질문주셔도 됩니다.감사합니다. :)
- 0
- 2
- 65
질문&답변
2024.07.11
수업자료 markup 에 대해 궁금합니다!
네 신원 님!아... Xcode에서 Setting에 들어가셔서,Setting > Key Bindings > 직접"Show Rendered Markup"을 검색하셔서, 저 같은 경우엔.. "옵션 + M"으로 설정해 놓았는데보통 "옵션 + M"으로 설정해놓으시면, 쉽게 옵션 + M을 누르셨을때.. 쉽게 마크업을 수정하실 수 있으세요.(사진)감사합니다. :)
- 0
- 1
- 126
질문&답변
2024.07.03
75강 api 추출할때 질문있습니다 !
안녕하세요 범석님! 왜 그런지 모르겠으나..애플 서버의 일시적인 오류로 보입니다. 저도 에러로 나오는데.. 해당 "jazz" 단어를 "bts"로 바꾸었더니 잘 됩니다.단어를 바꿔서 한번 해보시겠어요?
- 0
- 1
- 115
질문&답변
2024.06.29
테이블 뷰 관련 질문있습니다!
hoon님."self-sizing tableview"이런식으로 검색해보시면, 많은 자료가 나옵니다.아래 같은 글들도 잘 참고해보시고요.https://baked-corn.tistory.com/124 감사합니다. :)
- 0
- 2
- 130
질문&답변
2024.06.25
활용앱 64강에서 초기 설정에 관한 질문입니다.
네 열심이 님.그림으로 바뀌어서, 제가 보여드리는 캡처에서 아래 - (마이너스) 버튼 누르셔서제거 하시면 됩니다.프로젝트 누르시고 > General > Supported Destinations > iPad > 마이너스 누르기(사진)감사합니다. :)
- 0
- 1
- 119