포스트

Final (2)

어제 구현했던게 튜터님에게 피드백을 받으면서, 나도 그렇고 팀원들도 그렇고 튜터님의 한가지 질문에 아무도 대답을 하지 못한게 있었다.

비밀번호 찾기기능은 어떻게 하실거에요?

바로 이거였다.

그냥 가입, 로그인만 생각했지 해당부분은 고려를 아예 하지 않은 상태였다.

그래서 소셜로그인을 구현하려고한다.

하지만 중요포인트라면 MVC가 아닌 MVVM으로 해당방식을 구현한다는것.

아마 빡셀것이다.

Social Login

1. Apple Login 구현

우선 버튼을 다음과 같이 바꾼다.

1
2
3
4
5
6
let appleLoginButton: ASAuthorizationAppleIDButton = {
        let button = ASAuthorizationAppleIDButton(type: .signIn, style: .black)
        button.cornerRadius = 25
        button.addTarget(self, action: #selector(appleButtonDidTapped), for: .touchUpInside)
        return button
    }()

사실 버튼의 구성엔 아무런 고민이 없었다.

그다음에 ViewModel에 대해서 어떻게 함수를 정의해야할까? 라는 고민이 생겼다.

참고자료를 보니 ViewModel에 여러가지를 상속하는데,

처음에 내가 고민했던것과 같았다.

또한 글을 보면서 튜터님이 프로토콜을 사용해서 가능 할 것 같다고 하셨는데, 지금 내가 참고하는글이 딱 그것에 대해 구현을 하고 있다.

이걸 정리하면서 제대로 정리하면, 나머지 소셜로그인도 문제가 없을것 같아서 하나하나 제대로 좀 정리를 하면서 쓸 생각.

1. protocol 구현

1
2
3
4
5
6
7
8
9
10
11
protocol LoginInput {
    func appleLoginDidTapped ()
}

protocol LoginOutput {
    var loginPublisher: PassthroughSubject<Void, Error> { get set }
}

protocol LoginViewModelIO: LoginInput, LoginOutput {
    
}

이전에 Udemy에서 봤을때의 강의에선 struct를 사용하여 Input과 output에 대해 정의를 하고 시작했는데, 여기글과, 튜터님은 프로토콜에 대해 언급을 하셨다.

여러 방법으로 해보는것도 나에게는 아주 좋기에 이번엔 프로토콜로 해본다.

우선 input, output 각각에 대해 프로토콜을 정의 해주었다.

input은 로그인 버틀을 탭했을때 실행할 함수,

output은 view, viewmodel간의 데이터바인딩이다.

2. 함수 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SignViewModel: NSObject, ASAuthorizationControllerDelegate, LoginViewModelIO {
    
    var loginPublisher = PassthroughSubject<Void, any Error>()
    
    func appleLoginDidTapped() {
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        request.requestedScopes = [.fullName, .email]
        
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        
        controller.performRequests()
    }
}

controller의 delegate 설정을 하자마자 발생하는 에러

CleanShot 2024-06-01 at 16 30 20@2x

바로 이녀석…

이전에도 뭐하다가 해당 에러를 발견했는데, 에러를 제대로 읽지않고 바로 fix를 치면서 여러가지 함수를 구현해야한다고 하면서 여러 함수가 나열이 되었다.

그때의 기억이 살아나, 이번엔 제대로 읽어보았다.

바로 NSObject를 상속하라는것.

보통은 Delegate의 주체가 VC였기에 해당부분에 있어 문제가 없었으나, ViewModel의 경우엔 그런것에 대해 상속을 받고있는 게 없었기에 발생한 문제,

그래서 NSObject를 상속해 준다.

그렇다면 여기서 드는 의문

NSObject란 무엇인가?

CleanShot 2024-06-01 at 16 44 15@2x 바로 이런녀석이라한다.

NSObject는 object-c 및 swift 프로그래밍 언어의 기본 클래스이며, 대부분의 다른 클래스의 루트 클래스를 역할을 한다. 어플리케이션의대부분의 객체에 필요한 기본 동작을 정의하는 클래스이다. 객체를 생성, 복사, 비교 및 메모리에서 해제하는 메서드를 제공 즉 이녀석이 근본.

그래서 NSobject를 상속하기로 결정. 그러면 위에 에러도 사라진다. 만약 NSobject를 상속하지않으면 수없이 많은 함수들을 구현해줘야한다.

즉 너 상속 안할거면 너가 필요한부분 직접 커스터마이징 하라는것…

controller.presentationContextProvider = self을 적으니 관련 프로토콜 ASAuthorizationControllerPresentationContextProviding 이녀석을 채택해야하고

CleanShot 2024-06-01 at 17 07 19@2x

UIView가 필요하다.

하지만 view를 사용하기위해선 UIkit을 import 해야하는데 이건 ViewModel의 취지와는 다르다.

어떻게 해야할지 고민을 하다 참고글을 읽어보니 여기선 해당부분을 사용하지 않았다.

하지만 해당 고민을 GPT에는 이녀석을 다음과 같이 처리했다.

1
2
3
4
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
     
        return UIApplication.shared.windows.first!
    }

우선은 이방법을 한번 사용해보는걸로!.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension SignViewModel: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding{
    
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
     
        return UIApplication.shared.windows.first!
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: any Error) {
        loginPublisher.send(completion: .failure(error))
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { return }
        loginPublisher.send()
        
    }

}

우선은 파베에 넣지않고 작동 확인 후 파베에 정보를 저장 할 예정

3. 의존성 주입

해당부분은 어제 튜터님께 얼추 배웠기에 그것을 기반으로 의존성 주입을 해보도록 한다.

우선 View에서 부터 시작.

1
2
3
4
5
var appleTapped: (() -> Void)? 

@objc func appleButtonDidTapped() {
        appleTapped?()
    }

Apple 버튼이 탭 되었을때 클로저를 작동.

ViewModel에는 다음과 같이 해준다.

1
2
3
4
5
6
private let signManager: SignManager
    
    init(signManager: SignManager) {
        self.signManager = signManager
    }
    

VC는 이렇게

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private lazy var greetingBodyView: GreetingBodyView = {
        let view = GreetingBodyView()
        view.appleTapped = appleTapped
        return view
    }()

var appleTapped: (() -> Void)!
var viewModel: SignViewModel!
    
    convenience init(appleTapped: @escaping () -> Void, viewModel: SignViewModel) {
        self.init()
        self.appleTapped = appleTapped
        self.viewModel = viewModel
    }

이제 view에서 클릭되는 버튼이 viewmodel의 appleLoginDidTapped를 실행하게만 하면 될것같다.

SceneDelegate에서 해당부분을 구현한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
private lazy var signManager = SignManager() // added
private lazy var signViewModel = SignViewModel(signManager: signManager) // added
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        let window = UIWindow(windowScene: windowScene)
        let greetingVC = GreetingViewController(appleTapped: { [weak self] in // modified
            self?.signViewModel.appleLoginDidTapped()
        }, viewModel: signViewModel)
        self.window = window
        window.makeKeyAndVisible()

완료.

Jun-01-2024 20-00-30

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.