Thread 

여러 작업을 동시 처리하기


일반적으로 우리가 사용하는 운영체제(Operation System, OS)은 멀티 태스크를 지원한다. 그 덕분에 우리는 구글에서 자료를 찾으면서, 유튜브에서 강좌를 듣고, 동시에 비주얼 스튜디오에서 작업을 할 수 있으며 그와 동시에 오디오 재생 프로그램을 통해서 음악을 들을 수 있다. 이때 구글과 유튜브에 접속할 수 있게 해주는 브라우저, 코드 작업을 하는 비주얼 스튜디오, 음악을 재생한느 오디오 재생 프로그램이 각각 하나의 프로세스(Process)이다.


또 여기서 이 프로세스는 하나 이상의 스레드(Thread)로 이루어진다. 스레드는 프로세스를 여러 개의 조각으로 나눈 것으로, 한 OS에서 여러 프로세스가 작업하는 것처럼, 한 프로세스에서 여러 스레드가 동시에 작업을 처리할 수 있게 해준다. 방금 앞에서 든 예시 중에 오디오 재생 프로그램을 예시로 들자면, 오디오 프로그램은 하나의 프로세스으로, 그 안에서 여러 스레드로 나뉘어서 한 스레드는 음악을 재생하고, 또 다른 스레드는 가사를 보여주면서 음악 재생 시간에 맞춰서 싱크를 맞추는 등의 방식으로 동시에 여러 가지 작업을 동시에 처리하는 것이다.



스레드 생성/시작하기


그럼 이 스레드를 사용하기 위한 방법을 차근차근 배워보자.


using System.Threading;


스레드에 관련된 기능들은 System.Threading 네임스페이스에 포함되어 있다. System.Threading.* 처럼 일일이 네임스페이스를 입력해서 코드를 작성해줄 수도 있지만 가독성 문제와 작업 효율성을 위해서 using 선언을 해주자.


using System;

using System.Threading;


namespace ThreadTest

{

    class ThreadTestProgram

    {

        public static void Main(string[] args)

        {

            Run(0);

            Run(1);

        }


        public static void Run(int idx)

        {

            Console.WriteLine(string.Format("Run {0} Start"idx));

            for (int i = 0; i < 10; i++)

            {

                Console.WriteLine(string.Format("Run {0} :: {1}"idx, i));

            }

            Console.WriteLine(string.Format("Run {0} End"idx));

        }

    }

}


우선 스레드를 사용하지 않는 경우의 코드를 먼저 확인해보자. 위의 코드는 스레드를 전혀 사용하지 않고 Run() 함수가 두 번 연속 호출된다. 


 

이렇게 스레드를 사용하지 않고 Run() 함수를 두 번 호출하면 모두가 알다시피 코드는 순차적으로 진행해서 첫 번째 Run(0) 함수가 완전히 끝난 후에야 두 번째 Run(1) 함수가 동작한다.


using System;

using System.Threading;


namespace ThreadTest

{

    class ThreadTestProgram

    {

        public static void Main(string[] args)

        {

            Thread thread = new Thread(() => Run(0));

            thread.Start();

            Run(1);

        }


        public static void Run(int idx)

        {

            Console.WriteLine(string.Format("Run {0} Start"idx));

            for (int i = 0; i < 100; i++)

            {

                Console.WriteLine(string.Format("Run {0} :: {1}"idx, i));

            }

            Console.WriteLine(string.Format("Run {0} End"idx));

        }

    }

}


이번에는 스레드를 생성해서 첫 번째 Run(0) 함수를 스레드로 호출하게 했다. 그리고 반복문 10회로는 동시 실행을 판별하기 어려워서 반복 횟수를 100회로 늘렸다.


 

스레드를 사용한 후의 실행결과는 어느 함수가 끝나기 전에 두 함수가 동시에 진행되고 있음을 충분히 알 수 있다.


Thread thread = new Thread(() => Run(0));

thread.Start();

 

스레드를 사용하는 방법은 간단하게 Thread 객체를 생성하고 생성자의 매개변수로 스레드로 돌리고자 하는 함수를 넣어준 뒤 Start() 함수를 호출하면 된다. 스레드를 생성하기만 하고 Start() 함수를 호출하지 않으면 그 스레드는 동작하지 않는다.



스레드 양보하기


위의 스레드 실행 예시 이미지를 보면 스레드가 몇 번의 연산을 처리하고 잠시 다른 스레드에 처리 시간을 넘겨주고 다시 돌려받는 것을 알 수 있다. 스레드 프로그래밍에서는 이런 CPU 점유 상태를 다른 스레드에 언제 얼마동안 양보할 지를 알리는 함수가 있는데 이것이 바로 Thread.Sleep() 함수다.


Thread.Sleep(10);


Thread.Sleep() 함수는 해당 함수를 호출한 스레드가 매개변수의 시간만큼 쉬면서 다른 스레드에 처리 우선권을 양보하게 만든다. 매개변수의 시간 단위는 밀리세컨드(Milisecond)로 1000분의 1초에 해당한다. 즉 위 코드에 적힌 시간으로는 0.001초 동안 다른 스레드에 처리 우선권을 양보한다는 의미이다.


using System;

using System.Threading;


namespace ThreadTest

{

    class ThreadTestProgram

    {

        public static void Main(string[] args)

        {

            Thread thread0 = new Thread(() => Run(0));

            thread0.Start();

            Thread thread1 = new Thread(() => Run(1));

            thread1.Start();

        }


        public static void Run(int idx)

        {

            Console.WriteLine(string.Format("Run {0} Start"idx));

            for (int i = 0; i < 100; i++)

            {

                Console.WriteLine(string.Format("Run {0} :: {1}"idx, i));

                Thread.Sleep(10);

            }

            Console.WriteLine(string.Format("Run {0} End"idx));

        }

    }

}

 

이번에는 Run(0)와 Run(1) 함수를 모두 스레드로 호출했으며 반복문 중간에 Sleep() 함수를 추가했다.


 

이번 실행결과를 보면 Sleep() 함수를 사용하지 않을 때와는 다르게 허용된 시간에 최대한 몰아서 처리하지 않고 필요한 계산만 처리한 뒤에 바로 다른 스레드에게 처리 우선권을 넘기는 것을 확인할 수 있다.





스레드 중단하기


thread.Abort();

thread.Join();


작동 중인 스레드를 중지하는 방법은 두 가지가 있는데 Abort() 함수와 Join() 함수가 그것이다. 이 두 함수의 차이는 다음과 같다.


Abort() :: 함수의 종료를 보장하지 않고 어느 시점이던지 상관 없이 도중에 강제로 중단시킨다.

Join() :: 함수의 종료를 보장하며 스레드가 동작시키는 중인 함수의 끝에 도달하기를 기다린 다음에 스레드를 닫는다.


using System;

using System.Threading;


namespace ThreadTest

{

    class ThreadTestProgram

    {

        public static void Main(string[] args)

        {

            Thread thread0 = new Thread(() => Run(0));

            thread0.Start();

            Thread.Sleep(100);

            thread0.Abort();


            Thread thread1 = new Thread(() => Run(1));

            thread1.Start();

            Thread.Sleep(100);

            thread1.Join();

        }


        public static void Run(int idx)

        {

            Console.WriteLine(string.Format("Run {0} Start"idx));

            for (int i = 0; i < 100; i++)

            {

                Console.WriteLine(string.Format("Run {0} :: {1}"idx, i));

                Thread.Sleep(10);

            }

            Console.WriteLine(string.Format("Run {0} End"idx));

        }

    }

}


thread0은 Abort() 시키고 thread1은 Join() 시키는 코드를 작성한다음 컴파일 해보자.


 

Run(0)는 반복문이 동작하던 도중에 중단되고, Run(1)은 End까지 무사히 호출되고 종료된 것을 확인할 수 있다.


위듸 예시를 통해 알 수 있듯이 Abort() 함수의 경우에는 스레드를 작동 도중에 강제로 종료하기 때문에 스레드 강제 종료가 시스템에 심각한 영향을 끼치지 않는다는 보장이 있을 때만 사용하는 것이 좋다.


class ThreadTestProgram

{

    public static void Main(string[] args)

    {

        Thread thread0 = new Thread(() => Run(0));

        thread0.Start();

        Thread.Sleep(100);

        thread0.Abort();

    }


    public static void Run(int idx)

    {

        try

        {

            int runIdx = idx;

            Console.WriteLine(string.Format("Run {0} Start", runIdx));

            for (int i = 0; i < 100; i++)

            {

                Console.WriteLine(string.Format("Run {0} :: {1}", runIdx, i));

                Thread.Sleep(10);

            }

            Console.WriteLine(string.Format("Run {0} End", runIdx));

        }

        catch (Exception e)

        {

            Console.WriteLine(e);

        }

    }

}


스레드를 Abort() 함수로 강제 종료할 때 해당 스레드 함수에서는 System.Threading.ThreadAbortException이라는 예외를 발생시킨다. 만약 스레드를 Abort() 시켰을 때, 리소스 정리 등의 뒤처리 작업이 필요한 경우라면 반드시 해당 스레드 함수에서 발생하는 ThreadAbortException 예외를 받아서 정리 작업을 진행하는 것이 좋다.



스레드 동기화(Thread Synchronization)


여러 개의 스레드를 두고 작동하는 프로그램의 경우에, 여러 스레드가 자원이나 변수 등을 공유하는 경우가 많다. 다음의 예시를 보자.


class ThreadTestProgram

{

    public class Villige

    {

        public int population = 1000

            

        public void AddVillager()

        {

            population++;


            for(int i = 0; i < population; i++)

            {

                for(int j = 0; j < population; j++)

                {


                }

            }

            // 추가된 주민에게 주민번호 주기

            Console.WriteLine(string.Format("새 주민의 주민번호 :: {0}", population));

        }

    }


    public static void Main(string[] args)

    {

        Villige manager = new Villige();

        for(int i = 0; i < 10; i++)

        {

            new Thread(new ThreadStart(manager.AddVillager)).Start();

        }

    }

}


작은 마을을 키우는 게임을 만든다고 가정했을 때, 마을에 새로운 마을 주민이 태어나거나 새로 들어오면 인구 수를 늘려주고 몇 가지 처리를 한 뒤에 주민번호를 매겨주는 AddVillager() 함수를 구현했다. 그리고 주민번호는 고유한 번호이기 때문에 각 주민 마다 번호가 중복되어서는 안된다고 가정해보자. 이 때 마을 주민이 동시에 추가될 수도 있기 때문에 스레드 처리를 한다.


그런데 플레이 도중에 마을에 10명의 주민이 동시에 추가되었다고 해보자. 그러면 현재까지 1000명의 주민이 있었으니 그 뒤에 추가되는 주민들의 번호는 1001, 1002, 1003, ..., 1009, 1010이 되기를 기대할 것이다.


 

하지만 실행결과는 새 주민들의 주민번호가 중복되어서 발급되어 버렸다. 이러한 문제를 스레드 세이프 하지 않다(Not thread-safe)라고 하는데 이 문제를 해결하기 위해서 필요한 것이 바로 스레드 동기화이다. 스레드 동기화는 하나의 공용된 자원이나 변수에 여러 개의 스레드가 접근할 때, 스레드들이 순서를 지켜서 사용하고 다른 스레드가 사용 중일 때는 사용하지 못하게 만드는 것이다.


class ThreadTestProgram

{

    public class Vilige

    {

        public int population = 1000;


        public object populationLock = new object();


        public void AddHuman()

        {

            lock (populationLock)

            {

                population++;


                for (int i = 0; i < population; i++)

                {

                    for (int j = 0; j < population; j++)

                    {


                    }

                }

                // 추가된 주민에게 주민번호 주기

                Console.WriteLine(string.Format("새 주민의 주민번호 :: {0}", population));

            }

        }

    }


    public static void Main(string[] args)

    {

        Vilige manager = new Vilige();

        for(int i = 0; i < 10; i++)

        {

            new Thread(new ThreadStart(manager.AddHuman)).Start();

        }

    }

}


스레드를 동기화하는 방법은 lock을 사용사는 것이다. 스레드 락을 하기 위한 객체를 하나 만들어서 lock()을 해주면 lock() { } 으로 묶어준 블럭이 한 스레드에서 실행되는 동안에는 같은 객체의 lock으로 묶인 스레드는 멈춘 상태로 해당 코드를 진행하지 못하게 된다.


 

스레드를 lock() 함수로 동기화하여 실행하면 새로 들어온 주민들의 주민번호가 겹치지 않고 정상적으로 매겨지게 된다.


이런 스레드 동기화에도 단점은 있는데 스레드 동기화되는 부분은 동시 처리가 안되고 한 스레드씩 작업을 진행하기 때문에 프로그램의 속도가 느려질 수 있다.


 

그리고 스레드의 동기화 구조가 복잡한 경우라면, 위의 이미지처럼 두 개의 스레드가 두 자원을 사용하려고 할 때, 스레드 1이 자원 1을 사용하며 자원 2가 풀리기를 기다리고 있고 스레드 2가 자원 2를 사용하며 자원 1이 풀리기를 기다려서 두 스레드가 멈춰버리는 데드락(Dead lock, 교착상태)이 발생할 수도 있다.


이렇게 스레드는 동시 처리를 하기에 유용한 방법이지만, 호출 순서를 보장할 수 없고 디버깅이 어려운 구조이기 때문에 잘못 사용할 경우 해결하기 어려운 문제를 발생시키기 쉽다. 그러므로 스레드를 사용할 때는 조심해서 사용해야만 한다.

반응형
  1. 질문충 2020.09.26 22:40

    4스레드짜리 시피유로 24스레드까지 만들어도 돌아가는데 기계적인 부분과는 무관한건가요?

    • wergia 2020.10.20 00:05 신고

      네, CPU 사양으로 표시되는 코어나 스레드 수와는 무관하게 메모리가 허용하는 양만큼 스레드를 만들 수 있다고 하네요.

유니티에서 파이어베이스 인증(Auth) 기능 이용하기



네트워크 게임을 제작할 때, 중요한 부분 중의 하나가 회원가입, 로그인, 로그아웃 등의 기능을 제공하는 것이다. 이 인증 기능들을 제공하고 관리하는 것을 직접 구현하기 위해서는 해야할 일이 많다. 회원들의 목록과 정보를 관리하는 데이터베이스도 만들어야 하고 서버에 유저들이 회원가입이 로그인을 위해서 입력한 정보를 보내고 그 결과를 돌려주는 기능들 역시 직접 구현해야만 한다. 하지만 구글의 파이어베이스에서는 사용자들이 필요로 하는 기능들을 이미 제공하고 있다.


지난 섹션에서 우리는 파이어베이스에서 테스트 프로젝트를 생성하고 유니티와 연결하는 작업을 진행했다. 이번 섹션의 예시는 지난 섹션 이후에서 부터 시작하도록 하겠다.



파이어베이스 시작하기로 제일 첫 화면으로 들어가면 지난 섹션에서 만든 테스트 프로젝트가 보일 것이다. 그 프로젝트를 선택하면 해당 프로젝트의 설정가능한 기능들의 목록을 볼 수 있다.



이 중에서 우리가 사용할 기능을 Authentication(인증)이다. 이 기능을 이용하면 별도의 회원정보를 위한 데이터베이스 구축이나 기능 구현없이 회원가입, 로그인, 로그아웃 등의 기능을 만들 수 있다. 이 authentication 메뉴를 선택해서 들어가보면 다음과 같은 화면을 볼 수 있다.



사용자 탭에서는 현재 제공하는 프로젝트에 가입된 사용자들의 목록을 보여주는데 현재는 가입된 유저가 없기 때문에 아무런 사용자도 나오지 않는다.



그 옆의 로그인 방법 탭에서는 로그인에 사용할 방법들을 설정할 수 있다. 단순하게 이메일과 비밀번호를 이용해서 로그인 하는 방법도 제공하지만 구글 아이디나 페이스북, 트위터등의 아이디를 통해서도 가입이 가능하도록 기능을 제공하고 있다. 현재는 프로젝트가 처음으로 만들어졌기 때문에 모든 로그인 방법에 대해서 "중지됨"이라고 표시되어 있다. 이번 섹션의 예제에서는 이메일을 통한 로그인 방법을 사용해보겠다.



이메일/비밀번호 항목을 선택해서 사용설정을 켜주고 저장한다. 다만 이 부분에서 주의할 점이 하나 있다. 파이어베이스 프로젝트에서 기능 옵션을 변경하였다면 google-services.json 파일의 내용이 유니티 프로젝트에 넣어줬던 파일과 다르게 변경되기 때문에 새롭게 다운받아서 넣어주어야 한다는 것이다. 그렇게 하지 않는다면 기존의 google-services.json 파일을 사용하고 있는 프로젝트는 변경된 파이어베이스 서버에 제대로 접속할 수 없게 된다.


파이어베이스 프로젝트의 기능 옵션을 변경해서 google-services.json 파일을 새로 다운로드 받아야 한다면 다음의 경로를 따르면 된다.



그 다음의 작업은 유니티 프로젝트에서 이루어질 것이다. 이전 섹션에서 걸어줬던 작업에 필요한 파이어베이스 유니티 SDK를 다운로드 받을 수 있는 링크를 다시 걸겠다.


https://firebase.google.com/docs/unity/setup


다운로드 받은 SDK의 압축을 풀어보면 필요한 SDK들이 기능별로 유니티 패키지로 묶어있는 것을 볼 수 있다. 우리는 이 중에서 FirebaseAuth.unitypackage를 유니티 프로젝트에 임포트해야 한다.



유니티 에디터의 프로젝트 뷰에서 우클릭한 이후에 Import Package > Custom Package를 선택한다.




아까 받은 SDK 중에서 FirebaseAuth.unitypackage를 임포트한다.



필요한 SDK를 임포트하고 나면 프로젝트 창이 다음처럼 될 것이다.



이렇게 되면 파이어베이스의 인증 기능을 사용하기 위한 준비가 모두 끝난 것이다.



1. 회원가입기능 만들기

우선은 이메일과 비밀번호를 이용한 회원가입 기능을 제작해보겠다. 다음과 같이 UGUI를 이용해서 화면을 구성해보자.



이메일과 비밀번호를 입력할 수 있는 InputField와 회원가입, 로그인을 할 수 있는 버튼과 마지막에 결과를 알려줄 텍스트를 만들었다. 다음 과정은 인증 과정을 관리할 오브젝트를 만드는 것이다. 씬에 AuthObject를 만들고 그 오브젝트에 FirebaseManager라는 스크립트를 만들어서 붙여준다.



FirebaseManager의 코드 내용은 다음과 같다 :


using UnityEngine;
using UnityEngine.UI;

public class FirebaseManager : MonoBehaviour
{
    // 이메일 InputField
    [SerializeField]
    InputField emailInput;
    // 비밀번호 InputField
    [SerializeField]
    InputField passInput;
    // 결과를 알려줄 텍스트
    [SerializeField]
    Text resultText;

    // 인증을 관리할 객체
    Firebase.Auth.FirebaseAuth auth;

    // Use this for initialization
    void Awake ()
    {
        // 인증을 관리할 객체를 초기화 한다.
        auth = Firebase.Auth.FirebaseAuth.DefaultInstance;
    }
   
    // 회원가입 버튼을 눌렀을 때 작동할 함수
    public void SignUp()
    {
        // 회원가입 버튼은 인풋 필드가 비어있지 않을 때 작동한다.
        if(emailInput.text.Length != 0 && passInput.text.Length != 0)
        {
            auth.CreateUserWithEmailAndPasswordAsync(emailInput.text, passInput.text).ContinueWith(
                task =>
                {
                    if(!task.IsCanceled && !task.IsFaulted)
                    {
                        resultText.text = "회원가입 성공";
                    }
                    else
                    {
                        resultText.text = "회원가입 실패";
                    }
                });
        }
    }

    // 로그인 버튼을 눌렀을 때 작동할 함수
    public void SignIn()
    {
        // 로그인 버튼은 인풋 필드가 비어있지 않을 때 작동한다.
        if (emailInput.text.Length != 0 && passInput.text.Length != 0)
        {
            auth.SignInWithEmailAndPasswordAsync(emailInput.text, passInput.text).ContinueWith(
                task =>
                {
                    if (task.IsCompleted && !task.IsCanceled && !task.IsFaulted)
                    {
                        Firebase.Auth.FirebaseUser newUser = task.Result;
                        resultText.text = "로그인 성공";
                    }
                    else
                    {
                        resultText.text = "로그인 실패";
                    }
                });
        }
    }
}


코드의 작성을 마쳤다면 유니티 에디터의 오브젝트에 필요한 오브젝트를 넣어주어야 한다. FirebaseManager에는 각 Input Field와 결과 텍스트를 넣어주고 회원가입 버튼과 로그인 버튼의 OnClick 이벤트에 AuthObject를 끼워넣고 각자 호출해야하는 함수를 설정해주어야 한다.



이상으로 유니티에서 인증 기능에 필요한 모든 것을 만들었다.



하지만 여기서 테스트할 때 주의해야 할 점은 유니티 에디터 상에서 테스트하는 경우에는 모든 통신의 결과가 성공했다고 가정했다고 진행되기 때문에 제대로된 성공이나 실패 여부를 알고자 한다면 안드로이드로 빌드하여 모바일 상에서 테스트하는 것이 좋다. 또한 유니티 에디터 상에서 테스트된 것은 파이어베이스에 등록되지 않기 때문에 여기서 회원가입에 성공했다고 하더라도 Authentication의 사용자 목록에서는 나오지 않을 것이다. 또한 주의할 점은 만약 제대로된 이메일 주소가 아니거나 비밀번호의 길이가 너무 짧다면 회원가입에 실패할 수도 있다.


모바일 상에서 테스트하여 성공적으로 회원가입이 된다면 Authentication의 사용자 목록에 다음과 같이 출력될 것이다.



로그인에 성공한 유저를 로그아웃 시키고자 한다면 다음의 코드를 활용하면 된다.


auth.SignOut();



반응형
  1. 개양반 2018.01.06 18:06

    유니티 에디터 상에서도 로그인을 할 수 있는 방법은 없을까요?
    구글로그인 만들고 있는데
    매번 빌드해서 확인하는게 매우 번거롭네요 ㅠ

  2. 도화시 2020.04.22 18:31

    결과 리스트 값이 안변합니다ㅏ....... 뭐가 문제일까요 ㅠ !!인스펙터 text값에서는 변하는데!!
    정작 유니티 에디터에서는 '결과' 이값을 계속 유지합니다...
    빌드하고 봐도 그대로에요..ㅠㅠ

    • wergia 2020.04.22 21:45 신고

      일단 에디터에서는 무조건 성공으로 나오고 서버 리스트에는 안뜨는게 정상입니다.
      하지만 빌드해서 안되는 부분은 여러가지 요소가 문제가 될 수 있는데 일단 의심해볼 수 있는 부분은 로그인 제공업체를 설정하는 곳에 제대로 설정 되었는지 확인해보셔야될 것 같습니다.
      로그인 방식으로 사용하고자 하는 방식을 선택해서 사용설정을 해보세요.

    • wergia 2020.04.22 23:37 신고

      버튼을 눌렀는데도 결과라는 텍스트가 계속 뜬다면 코드작성 도중에 조건을 잘못 설정했거나, 버튼 이벤트에 함수를 등록을 빼먹었을 수도 있습니다.

    • 도화시 2020.04.22 23:43

      답변 감사합니다!! 안돼서 여기저기 서치해보다가 유니티허브에도 질문글 올렸습니다 ㅜㅜ 말씀하신대로 설정부분에서 꼬인건가 싶어서 다시 처음부터 해보려합니다.. 계정은 만들어지긴해요! 텍스트 결과값이 안바뀌어서 만들어졌는지 확인하려면 파이어베이스 들어가서 직접확인해야합니다 ㅎㅎ.. https://cafe.naver.com/unityhub

    • wergia 2020.04.23 14:05 신고

      음 리스트에는 올라가는데 결과 문자열만 안바뀌는 부분이라면 확실히 중간 조건문이 잘못들어가있거나 인스펙터뷰에서 프로퍼티가 빠져있을 확률이 크네요

  3. Gamja 2020.09.17 11:12

    안녕하세요 글 잘 읽었습니다 :), 좀 궁금한것이 현재 유니티 사용하구요, 모바일환경에서 파이어베이스로 Google 로그인 후, Sign out 하고 재 로그인 시도할때 브라우저에 전에 로그인했던 계정이 캐싱되어 있어서 다른계정을 선택해 로그인을 하지 못하고 있습니다. 직접 브라우저 들어가서 구글 로그아웃 하지 않는이상 안되는데요, 혹시 방법이 있을까요?

    • wergia 2020.10.20 00:08 신고

      그런 부분까지는 제가 깊게 테스트를 해보지 못해서 알기 어렵네요.
      그리고 대부분의 사용자는 안드로이드 모바일 환경에서 구글 계정을 단일로 쓰는 경우가 많아서 다른 계정으로 로그인 하는 경우가 많을까요?

  4. 123 2021.08.30 02:31

    궁금한게 있는데요
    소셜 계정 연동을 이용하는경우 굳이 파이어베이스를 이용할 필요가 있을까요??
    예를들어 구글 소셜계정을 이용하면 이미 구글 클라우드로 인증을 할텐데 그 이후에 굳이 파이어베이스 인증을 다시 해야하나 싶네요

    물론 위 예제는 계정을 만드는 예제니까 납득이 되지만요

+ Recent posts