안녕하세요 강사님! 강의 복습 중에 runTest 관련 문의가 있어서 다음과 같이 질문을 남기게 됐습니다. 코루틴 테스트 심화 강의의 4:07초에 시작되는 부분을 보면 runTest를 호출해 실행되는 코루틴은 메인 스레드를 사용한다 라고 해주셨습니다. 제가 실제로 runTest 를 이용해 코드를 실행시켜 보니 Test worker 스레드를 사용하고 있다고 나왔습니다. 실행 코드 @Test fun `메인 스레드만 사용하는 runTest`() = runTest { println("[${Thread.currentThread().name}] 메인 시작") delay(100) println("[${Thread.currentThread().name}] 메인 종료") } 결과 [Test worker @kotlinx.coroutines.test runner#2] 메인 시작 [Test worker @kotlinx.coroutines.test runner#2] 메인 종료 실제 사용되는 스레드는 Test worker 스레드 이지만, 테스트 코드에서 실행될 때 Test worker 스레드가 메인 스레드 처럼 동작하기 때문에 메인 이라고 말씀해주신 걸까요?? 감사합니다!
개인앱을 해야하는 이유 영상에 원래 영상은 없고 자막만 나오나요??학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! 마크다운, 단축키를 이용해서 편리하게 글을 작성할 수 있어요. 먼저 유사한 질문이 있었는지 검색해보세요. 서로 예의를 지키며 존중하는 문화를 만들어가요. 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
안녕하세요. 강의를 듣다가 궁금한 점이 생겼는데, 일정 관리를 지라로 사용하시는 특별한 이유가 있으신가요? 아이디어 관리는 노션을 사용해서 하셨는데 노션에서도 간단한 스프린트와 일정관리를 제공함에도 불구하고 지라를 사용하신 이유가 궁금합니다. 제 사견으로는 1인개발이기 때문에 최대한 많은 툴 사용을 지양하여 정보의 파편화를 막는게 중요하지 않을까해서 질문 드립니다. 아직 제가 지라를 제대로 사용해보지 않아서 질문드리는 걸 수 있는데 노션에 통합관리하는 것 대비 지라를 사용하는 것이 어떠한 장점이 있는지 궁금합니다. 추가로 강사님께서 350개의 앱을 출시하셨다고 하셨는데 그러면 각 서비스마다 지라의 프로젝트를 전부 만들어서 관리하시나요? 아니면 하나의 "서비스개발" 프로젝트에서 전부 관리하시나요?
- 학습 관련 질문을 남겨주세요. 질문을 상세히 작성하면 더 좋습니다. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 먼저 좋은 강의 감사합니다! 혼자서 다음과 같이 마구마구 찍어보고있었습니다. 이때 저희 예상과 다르게 돌아가서 질문을 드립니다. 저의 가설로는 "withContext는 코루틴을 생성하지 않고 Context만 바꾸니 코루틴을 제어하고 추적하는데 사용되는 Job은 새로 만들어지지 않을 것이다. 따라서 job1과 job5는 같을 것이다." 이었습니다. 하지만 결과는 새로운 job을 만들고 job의 부모에 job1을 연결시키더군요. 동일한 코루틴에서 동작하는데 왜 새로운 Job을 만드는 것인가요? fun main() = runBlocking { val job1 = coroutineContext[Job] // 1 coroutineScope { val job2 = coroutineContext[Job.Key] val job3 = launch {} val job4 = launch {} println("job2 = ${job2}") // 2 println("job2 parent = ${job2?.parent}") println("job3 parent = ${job3.parent}") println("job4 parent = ${job4.parent}") } withContext(Dispatchers.Default) { val job5 = coroutineContext[Job.Key] println("job5 = ${job5}") println("job5 parent = ${job5?.parent}") } println("job1 = ${job1}") } // // job5 = DispatchedCoroutine{Active}@4311e223 // job5 parent = BlockingCoroutine{Active}@1c2c22f3 // job1 = BlockingCoroutine{Active}@1c2c22f3 coroutineScope도 주석 1, 2 부분이 같은 코루틴이라 job1이랑 job2가 동일하게 나올 거라 생각했는데 다르군요. 그렇다면 현재 상황에서 "하나의 코루틴에 여러 개의 job(?)을 가진 것 아닌가? 이러면 job으로 코루틴을 제어할 수 있나?" 라는 의문이 듭니다. job과 코루틴의 관계가 헷갈립니다 😱
대표님 안녕하세요. 개인 계정으로 앱을 운영중입니다. 명의가 같은 개인 계정이 3개 정도가 있는데요. 현재 1개의 개인 계정에서 2번 정지가 되었는데, 그래서 이 계정은 이제 안쓰려고 합니다. 근데 명의가 같은 다른 계정에서 추가적으로 1번 더 정지가 되면 총 3회 정지로 계정이나 제 명의 자체가 정지가 되게 될까요? 이거 명의 기반인지 계정 기반인 지 궁금합니다.
안녕하세요. 좀비님. 저는 개발은 좋은데 사회생활이 힘들어서 회사탈출 방법을 이것저것 시도해보는 과정중 좀비님 블로그를 발견하게 되었습니다. 1인 개발 이거 될까라는 미래에 대한 확신이 부족한 상태로 몇년이 흘렀는데 좀비님 글을 발견하게 된것이 너무나 큰 행운인것 같습니다. 여러가지 경험과 팁들을 풀어주셔서 너무 감사드립니다. 질문: 특정 앱 출시후 광고를 유지할지 버릴지 판단하는 좀비님만의 기준이 있으신지 문의드립니다. 예를 들어 2주동안 2000원/일 예산으로 각종 광고를 테스트해보고 손익분기점을 못 넘기면 이 앱은 버린다와 같은 좀비님만의 기준에 대해 문의드립니다. 혹시 공개하시기 불편한 내용이면 저도 삽질해가면서 저만의 기준을 만들어보겠습니다. 감사합니다.^^
안녕하세요? 강의를 잘 듣고 있는 수강생입니다! 다름이 아니라 프좀님께서는 여러 개발자 계정이 있는 걸로 말씀하신것으로 기억 하는데요, 여러 개발자에 대해서 개인 사업자 하나로 여러 계정등록을 하신것인지 아니면 사업자 N 개 를 내고 하시는 것인지 궁급니다. 저번 오프라인 행사에 참여를 했어야 하는데 아쉽게도 육아 때문에 참여하지 못해서 여기에서 여쭙게되었네요.
안녕하세요! 강의 너무 잘 듣고 있습니다. 애드몹 광고소스에 대한 내용이 없어서 추가 질문 드려봅니다. 혹시 애드몹을 붙일 때, eCPM을 높이기 위해서 다른 광고소스도 붙이는 작업을 하시나요? 만약 하신다면 어떤 광고소스를 주로 붙이시나요? 만약 안하신다면 그 이유는 무엇일까요?
- 학습 관련 질문을 남겨주세요. 질문을 상세히 작성하면 더 좋습니다. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 안녕하세요, coroutine 강의를 듣고 ktor server 공부를 하는 중에 delay 관련 질의가 생겨 글 남깁니다. delay 의 경우 coroutine 이 스레드를 양보하고, 일정 시간 후에 다시 스레드가 비어있으면 점유하는 형식으로 진행될텐데, 아래와 같은 경우 delay 이후 코드가 실행되지 않습니다. 사용자 → KTOR Server → suspend function call → loop { logic → 너무 많은 동작을 제한하기위한 delay } ktor 는 기본적으로 요청이 IO Dispatcher 를 사용하는 것 같은데, coroutine 에서 delay 이후 기능이 동작하지 않는 것에 대한 이유가 있을까요..? (강의랑 무관한 내용이라 죄송합니다 ㅠ.ㅠ) /* Routing.kt */ fun Application.configureRouting(searcher: BaseSearcher) { routing { post("/search") { val params = call.receive<Map<String, String>>() searcher.logging("Received POST request with params: $params") val result: List<BaseDTO> = searcher.search(params) searcher.logging("POST Result: $result") call.respond(HttpStatusCode.OK, mapOf("result" to result)) } get("/") { call.respondText("Hello World!") } } } /* Searcher.kt */ /** * 함수 이름 : search * 내용 설명 : 입력된 map<string, string> param 을 바탕으로 요청 전송 및 응답 반환 */ override suspend fun search(searchParam: Map<String, String>): List<SimpleSearchLandResultDTO> { // 별도의 Job 으로 분리해서 오류시 상위 전파 제한 return HttpClient(CIO).use { client -> val parseResultLst: MutableList<SimpleSearchLandResultDTO> = mutableListOf() try { val initRequestResultDto: SimpleSearchResultDTO = sendFormedRequest(client, SearcherConst.URL_MAIN_PAGE, false) // 기본 페이지 요청이 성공했을 때 다음 요청을 진행 if (initRequestResultDto.isSuccess) { val mutableSearchParam = searchParam.toMutableMap() delay(300) while (true) { val eachPageSearchResultDto: SimpleSearchResultDTO = sendFormedRequest(client, makeGetUrl(mutableSearchParam), true) // 실패한 경우 종료 if (!eachPageSearchResultDto.isSuccess) { logging("[search] 조회 중 오류가 발생하였습니다.") break } // 결과가 빈 경우도 종료 else if (eachPageSearchResultDto.landSearchResultLst.isEmpty()) { break } // 결과가 있는 경우엔 전부 더해줌 parseResultLst.addAll(eachPageSearchResultDto.landSearchResultLst) logging("[search] result(${eachPageSearchResultDto.landSearchResultLst[0].currentPage} : ${eachPageSearchResultDto.landSearchResultLst}") // 마지막 페이지인 경우 종료 if (eachPageSearchResultDto.landSearchResultLst[0].currentPage == eachPageSearchResultDto.landSearchResultLst[0].maxPage ) { break } else { // 다음 페이지 수집 진행 mutableSearchParam["currentPage"] = (eachPageSearchResultDto.landSearchResultLst[0].currentPage + 1).toString() delay(300) } } } } catch (e: Exception) { logging("[search] search 함수 중 오류가 발생하였습니다. param: $searchParam, exception: $e") } logging("[search] Result : $parseResultLst") // parseResultLst 반환 parseResultLst } } private suspend fun sendFormedRequest(client: HttpClient, url: String, isResultNeed: Boolean): SimpleSearchResultDTO { return try { val response = client.get(url) { headers { append(HttpHeaders.Cookie, cookie.toCookieString()) append(HttpHeaders.Accept, SearcherConst.DEFAULT_HTTP_ACCEPT) append(HttpHeaders.AcceptEncoding, SearcherConst.DEFAULT_HTTP_ACCEPT_ENCODING) append(HttpHeaders.Connection, SearcherConst.DEFAULT_HTTP_CONNECTION) append(HttpHeaders.AcceptLanguage, SearcherConst.DEFAULT_HTTP_ACCEPT_LANGUAGE) append(HttpHeaders.UserAgent, SearcherConst.DEFAULT_HTTP_USER_AGENT) } } cookie.putAll(response.headers[HttpHeaders.SetCookie]?.split(";")?.map { it.split("=") }?. filter { it.size == 2 }?.filter { it[1] != "/" && it[1].isNotEmpty() }?.associate { it[0] to it[1] } ?: mapOf()) if (response.status != HttpStatusCode.OK) { SimpleSearchResultDTO(false) } else { SimpleSearchResultDTO(true, if(isResultNeed) parseData(response.bodyAsText()) else mutableListOf()) } } catch(e: Exception) { logging("[sendFormedRequest] Error : $e", Level.ERROR) SimpleSearchResultDTO(false) } }
안녕하세요 TextField 커스텀 관련하여 여러 기능을 살펴보고 있는데요. 구글에서 찾아봐도 Cursor의 두께를 설정하는 프로퍼티를 찾지 못하여서 질문드립니다. Cursor를 커스텀하는 방법이 있나요? 두께를 줄이거나 높이를 낮추는 등의 커스텀을 진행하고 싶습니다. 감사합니다.
안녕하세요, 저는 3년차 Spring 개발자입니다. 때문에 앱 개발에 대한 지식이 부족한 상황이라서 해당 방법론이 다른 개발 환경에도 적용되는지 궁금합니다. 제가 토이 프로젝트로 Flutter를 사용하여 앱을 만든 적은 있는데 이번 강의를 들으면서 강의 내용을 적용하기 위해 Android 개발을 다시 학습 해야할 지 고민 입니다. 다른 개발 환경에도 적용할 수 있는지 제 상황에 Flutter와 Android 중 어떤 것을 추천하시는지 감사합니다!
도움부탁드려요. 강의를 전부시청하였습니다. 파이어베이스에 실수로 강의프로젝트를 삭제하였습니다. 그런이유로 작동이 잘안되어 문의 드립니다. 어떤이유인지 같은이름의 프로젝트를 생성불가하여 노트북도 새로 포맷하여 새롭게 설치 하였습니다. gmail을 새로운계정을 만든고,파이어베이어스 등록하였습니다. 프로젝트이름 같은이름으로 생성하였습니다. Authentication,Firestore Database는 강의내용과 같아서 정상적으로 등록하였습니다. 혹시나 싶어서.새로운 계정으로git과 github을 다시설치했습니다. 그러나, 파이어베이스에 스토리지가 일부 변경이 되어 답변내용중에 스토리지변경에 대한내용을 참고하라는 설명에 설치를 하게되었습니다. 기존에 강의를 불러서 firebase_options.dart 을 안드로이드,앱키등등을 새로생성된것으로 변경하였는데.앱이 정상작동이 안돼는데.. 어떻게 바꿔줘야 하는지 부탁드려요. 여기에서 넘어가지를 않습니다. 어떻게 하면 좋을까요.
안녕하세요. 강사님 코루틴 강의를 1번 완강하고, 최근에는 전체적으로 강의를 복습을 하고 있습니다. 복습을 하다가 CoroutineDispatcher 의 limitedParallelism 에 대해 몇가지 궁금한 점이 생겨서 질문을 남기게 됐습니다. Dispatchers.Default 관련 Dispatchers.Default 의 스레드풀 수를 넘어서는 숫자 혹은 스레드풀 수에 딱 맞게 스레드 수를 지정하여 limitedParallelism 함수를 실행하는 경우, Dispatchers.Default 의 모든 스레드들은 제가 지정한 해당 작업을 처리하기 위해 계속 작업을 하고, 해당 작업이 시작된 이후에 Dispatchers.Default 로 지정하여 실행한 코루틴 작업들은 제가 지정한 작업이 끝나기 전까지는 모두 대기하게 되는 것인가요?? 예를 들어, CPU 코어가 4개인 컴퓨터라면 Dispatchers.Default 의 스레드 풀에 들어있는 스레드 수는 4개가 될테고, 해당 상황에서 Dispatchers.Default.limitedParallelism(4) 혹은 스레드가 몇개 있는지 몰라 Dispatchers.Default.limitedParallelism(8) 이렇게 지정하는 경우에 아래 코드를 기준으로 하면 제가 먼저 시킨 작업( superCpuIntensiveTask() )이 끝나기 전에는 lightCpuIntensiveTask() 는 실행되지 않는 것일까요? (※ 멀티 스레드 작업이기 때문에 아래의 lightCpuIntensiveTask() 가 먼저 실행될 가능성도 있지만 아주 재수가 나쁘게 superCpuIntensiveTask() 가 먼저 실행이 된다면 어떻게 되는지가 궁금합니다) // CPU 코어가 4개인 컴퓨터 fun main() = runBlocking { launch(Dispatchers.Default.limitedParallelism(8)) { superCpuIntensiveTask() } launch(Dispatchers.Default) { lightCpuIntensiveTask() } println("Done") } 1번과 비슷한 질문입니다. CPU 코어가 4개인 컴퓨터에서 Dispatchers.Default.limitedParallelism 를 여러번 사용한 경우, 코드상으로 보면 사용해야 하는 스레드의 수가 전체 스레드풀 수를 넘어서는 숫자가 되는데 이 경우에는 어떻게 스레드를 나눠서 사용하는 걸까요 ? // CPU 코어가 4개인 컴퓨터 fun main() = runBlocking { launch(Dispatchers.Default.limitedParallelism(3)) { superCpuIntensiveTask() } launch(Dispatchers.Default.limitedParallelism(3)) { superCpuIntensiveTask2() } launch(Dispatchers.Default.limitedParallelism(2)) { CpuIntensiveTask() } println("Done") } Dispatchers.IO 관련 Dispatchers.IO.limitedParallelism 를 사용하면 새로운 Thread 집합을 만든다고 말씀해주셨습니다. 그림에서 표현해주신 것처럼 기존에는 개별로 존재하는 Thread들을 새로 그룹으로 묶어낸다고 이해를 하면 되는 것일까요 ?? Dispatchers.IO.limitedParallelism 으로 묶여있던 Thread들은 해당 코루틴이 끝나면 다시 그룹이 풀리는 것일까요 ?? 만약에 Dispatchers.IO.limitedParallelism 를 많이 사용하여 이미 기존의 스레드들 모두가 집합으로 구성이 되어있는 경우에는 신규로 집합을 만들 수가 없는 경우에는 해당 Dispatchers.IO.limitedParallelism 요청이 어떻게 되는지 궁금합니다. limitedParallelism 의 Thread 수 지정 해당 코드와 같이 Dispatchers.Default.limitedParallelism(4) Thread 수를 지정해야 할때, 어떤 기준으로 어떻게 Thread 수를 지정해야 좋을까요 ?? 강사님만의 팁이 있으시면 공유가 가능하실까요?? 어떻게 수를 정해서 넣어야 할지 감이 잘 잡히지 않습니다. 😢 1번과 관련된 질문입니다. Thread 수를 지정할 때 현재 Thread Pool에 존재하거나 남아있는 쓰레드가 몇개인지 충분히 고려를 하면서 코드를 짜야할까요 ?? 실수로 스레드 풀의 전체 Thread 수를 넘어선 요청을 하게 되면 어떻게 되는지 궁금합니다. 질문이 많아서 정말 죄송합니다. 🙇♂ 🙇♂ 🙇♂