fun sendEmail(toEmail: String): String {
val fromEmail = "아까위에서2차인증받았던이메일"
val password = "아까위에서2차인증받았던 비밀번호"
val code = (100..10000).random().toString()
CoroutineScope(Dispatchers.IO).launch {
val props = Properties()
props.setProperty("mail.transport.protocol", "smtp")
props.setProperty("mail.host", "smtp.gmail.com")
props.put("mail.smtp.auth", "true")
props.put("mail.smtp.port", "465")
props.put("mail.smtp.socketFactory.port", "465")
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory")
props.put("mail.smtp.socketFactory.fallback", "false")
props.put("mail.smtp.ssl.enable", "true")
props.setProperty("mail.smtp.quitwait", "false")
// 구글에서 지원하는 smtp 정보를 받아와 MimeMessage 객체에 전달
val session = Session.getDefaultInstance(props, this@GMailSender)
// 메시지 객체 만들기
val message = MimeMessage(session)
message.sender = InternetAddress(fromEmail) // 보내는 사람 설정
message.addRecipient(Message.RecipientType.TO, InternetAddress(toEmail)) // 받는 사람 설정
message.subject =
"verfication code" // 이메일 제목
message.setText("저쪽 테이블에서 보낸 코드입니다. 아래 비밀번호를 인증창에 입력해주세요\n" + "<" + code + ">") // 이메일 내용
// 전송
Transport.send(message)
}
return code
}
사실 처음 기사시험을 공부할때나 학과 공부를 할때 이게 그래서 왜 필요한데에 관한 의문을 정말 많이 가졌다.
안드로이드를 공부하다보니, 자원의 효율적인 사용을 고민하게 되었고 그 결과 싱글톤 패턴이 왜 필요한지 어느정도 감을 갖게 되었다.
1)싱글톤 패턴이란?
어떤 클래스의 인스턴스가 하나임을 보장하는 전역에서 접근 가능한 디자인 패턴
처음부터 끝까지 한 번의 인스턴스를 생성함으로서 메모리의 효율적인 사용을 가능하게 함
2)왜 필요한데?
사실 왜 필요한지가 잘 납득이 안되어서 처음에는 이해가 잘 안되었다. 클래스의 인스턴스가 하나임을 보장하면 뭘 할 수 있는가에 관한 의문이 들었기 때문이다. 쉽게 얘기하면 데이터 베이스를 수정할 수 있는 인스턴스가 여러가지라면 어떨까? 당연히 문제가 생길것이다. 그러한 연유로 싱글톤 패턴을 사용한다.
3)어떻게 사용해?
이를 고민하기 전에 좀 더 익숙한 자바 코드로 한번 보자.
public class DBHelper {
private static DBHelper instance;
private DBHelper() {}
public static DBHelper getInstance() {
if(instance ==null) {
instance = new DBHelper();
}
return instance;
}
}
외부에서 인스턴스 못만들게 private으로 만들어 놓고
인스턴스가 있으면 그냥 리턴해주고 없으면 만들어서 리턴해준다. 즉 인스턴스가 무조건 하나 밖에 만들어 질 수 없는 구조이다
package com.example.coroutine
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello") // main coroutine continues while a previous one is delayed
}
코루틴을 공부하고 있는 입장이라면 아 출력값이 WORLD! HELLO 일리는 없겠구나 하고 대충 예상할 수 있지만
어쨌든간에 돌려보면
잘 나온다.
여기서 각각 요소들의 역할은 뭔가?
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
// --------------- async ---------------
사실 뭔지 잘 모르겠지만 return 값 자체가 coroutine이니까 코루틴을 만들어주는거구나. 정도로 이해했다.
launchis acoroutine builder. It launches a new coroutine concurrently with the rest of the code, which continues to work independently. That's whyHellohas been printed first.
delayis a specialsuspending function. Itsuspendsthe coroutine for a specific time. Suspending a coroutine does notblockthe underlying thread, but allows other coroutines to run and use the underlying thread for their code.
(코루틴을 특정 시간동안 연기 시켜준다. 코루틴의 연기(Suspending)는 근본적인 쓰레드를 차단하지는 않지만, 다른 코루틴들의 동작을 허락하고 근본적인 쓰레드를 그들의 코드에 이용한다. )
runBlockingis also a coroutine builder that bridges the non-coroutine world of a regularfun main()and the code with coroutines inside ofrunBlocking { ... }curly braces. This is highlighted in an IDE bythis: CoroutineScopehint right after therunBlockingopening curly brace.
If you remove or forgetrunBlockingin this code, you'll get an error on thelaunchcall, sincelaunchis declared only in theCoroutineScope:
Coroutines can be thought of as light-weight threads
코루틴은 경량 쓰레드로 생각할 수 있다. 이에 관하여 예제를 살펴보자면
fun main() = runBlocking {
repeat(100_000) { // launch a lot of coroutines
launch {
delay(5000L)
print(".")
}
}
}
컴퓨터 상태마다 다르겠지만 하나도 버벅임이 없다.
thread로 비교한번 해보자면
fun main() = runBlocking {
repeat(100_000) { // launch a lot of coroutines
thread {
Thread.sleep(5000L)
print(".")
}
}
}
확실히 차이가 있다!
fun main() = runBlocking { // this: CoroutineScope
GlobalScope.launch { // launch a new coroutine and continue
delay(3000L) // non-blocking delay for 3 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello") // main coroutine continues while a previous one is delayed
delay(2000L)
}
}
만약 위와 같은 코드가 있다면 어떤 결과를 낳을까?
결과값은 아마도 Hello! 만 뜰것이다
왜냐면 코루틴 자체가 3초뒤에 결과값을 출력하고 싶은데 전체가 2초뒤면 끝나기 때문이다.
그럼 얘를 어떤식으로 해결 할 수 있을까?
launch를 하게되면 job을 반환하게 되는데
fun main() = runBlocking { // this: CoroutineScope
val job =GlobalScope.launch { // launch a new coroutine and continue
delay(3000L) // non-blocking delay for 3 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello") // main coroutine continues while a previous one is delayed
job.join()
}
위와 같은 결과값을 나타낸다. join은 코루틴이 완료 된 후에 메인함수가 종료 되게끔 만들어준다.
근데 위와같은 방법은 문제가 많다. 가장 대표적으로는 앞으로 수많은 코루틴을 쓰게 될텐데
그때마다
val job = glbalscope~~~
val2 job = globalscop~~
이런식으로 관리를 해야하는데, 이는 굉장히 번거롭고 귀찮은 방법이다.
그렇다면 조금 더 세련된 방법은 없을까? 이에 관해 구글은 structured concurrency 라는것을 제공한다
structured concurrency 란?
Coroutines follow a principle ofstructured concurrencywhich means that new coroutines can be only launched in a specificCoroutineScopewhich delimits the lifetime of the coroutine. The above example shows thatrunBlockingestablishes the corresponding scope and that is why the previous example waits untilWorld!is printed after a second's delay and only then exits.