Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 59 additions & 15 deletions DiningCoach.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,16 @@
65F8854F2A32764900D151F1 /* View+LineHeightFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F8854E2A32764900D151F1 /* View+LineHeightFont.swift */; };
65F885532A327AF700D151F1 /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F885522A327AF700D151F1 /* SplashView.swift */; };
65F885592A341B6000D151F1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65F885582A341B6000D151F1 /* LaunchScreen.storyboard */; };
8E5A37902A5C27BB000281AC /* LoginApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E5A378F2A5C27BB000281AC /* LoginApi.swift */; };
8E5A37922A5C298B000281AC /* DCInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E5A37912A5C298B000281AC /* DCInterceptor.swift */; };
8EA4CBF12A5EDCEA00734ED3 /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EA4CBF02A5EDCEA00734ED3 /* TokenManager.swift */; };
8ECF90E92A4EEBEB00069225 /* LoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ECF90E82A4EEBEB00069225 /* LoginButton.swift */; };
8ECF90EC2A4EF6B500069225 /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 8ECF90EB2A4EF6B500069225 /* GoogleSignIn */; };
8ECF90EE2A4EF6B500069225 /* GoogleSignInSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 8ECF90ED2A4EF6B500069225 /* GoogleSignInSwift */; };
8EE137782A4B09FB00A0D665 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE137772A4B09FB00A0D665 /* LoginView.swift */; };
8EE1377B2A4B12B100A0D665 /* DCTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE1377A2A4B12B100A0D665 /* DCTextField.swift */; };
8EE3EFF62A5593E6005585C7 /* Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE3EFF52A5593E6005585C7 /* Login.swift */; };
8EE3EFF82A55AD54005585C7 /* Urls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE3EFF72A55AD54005585C7 /* Urls.swift */; };
8EF5C5B22A544DBF0025E589 /* NicknameInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EF5C5AE2A544DBF0025E589 /* NicknameInputView.swift */; };
8EF5C5B32A544DBF0025E589 /* TermsAgreementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EF5C5AF2A544DBF0025E589 /* TermsAgreementView.swift */; };
8EF5C5B42A544DBF0025E589 /* SigninCompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EF5C5B02A544DBF0025E589 /* SigninCompleteView.swift */; };
Expand Down Expand Up @@ -84,10 +89,15 @@
65F8854E2A32764900D151F1 /* View+LineHeightFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+LineHeightFont.swift"; sourceTree = "<group>"; };
65F885522A327AF700D151F1 /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = "<group>"; };
65F885582A341B6000D151F1 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
8E5A378F2A5C27BB000281AC /* LoginApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginApi.swift; sourceTree = "<group>"; };
8E5A37912A5C298B000281AC /* DCInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DCInterceptor.swift; sourceTree = "<group>"; };
8EA4CBF02A5EDCEA00734ED3 /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; };
8ECF90E82A4EEBEB00069225 /* LoginButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginButton.swift; sourceTree = "<group>"; };
8EE137742A4B06A400A0D665 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
8EE137772A4B09FB00A0D665 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
8EE1377A2A4B12B100A0D665 /* DCTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DCTextField.swift; sourceTree = "<group>"; };
8EE3EFF52A5593E6005585C7 /* Login.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Login.swift; sourceTree = "<group>"; };
8EE3EFF72A55AD54005585C7 /* Urls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Urls.swift; sourceTree = "<group>"; };
8EF5C5AE2A544DBF0025E589 /* NicknameInputView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NicknameInputView.swift; sourceTree = "<group>"; };
8EF5C5AF2A544DBF0025E589 /* TermsAgreementView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TermsAgreementView.swift; sourceTree = "<group>"; };
8EF5C5B02A544DBF0025E589 /* SigninCompleteView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SigninCompleteView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -215,7 +225,6 @@
A51E1C442A406B9900E4EBA3 /* Validation */,
A51E1C412A406A3300E4EBA3 /* Registration */,
A51E1C3B2A4061B900E4EBA3 /* API */,
A51E1C352A405DD200E4EBA3 /* Login */,
A526DDC22A092B0300B682BF /* DiningCoachApp.swift */,
A526DDC42A092B0300B682BF /* ContentView.swift */,
65F885502A327AE200D151F1 /* Scene */,
Expand All @@ -241,6 +250,7 @@
8EF5C5DB2A544EE40025E589 /* View+CornerRadius.swift */,
8EF5C5DC2A544EE40025E589 /* View+hideKeyboard.swift */,
65F8854E2A32764900D151F1 /* View+LineHeightFont.swift */,
8EA4CBF02A5EDCEA00734ED3 /* TokenManager.swift */,
);
path = Helper;
sourceTree = "<group>";
Expand All @@ -265,8 +275,8 @@
8EE137732A4B04AE00A0D665 /* Login */ = {
isa = PBXGroup;
children = (
8EE137772A4B09FB00A0D665 /* LoginView.swift */,
8ECF90E82A4EEBEB00069225 /* LoginButton.swift */,
8EE3EFF32A5593C2005585C7 /* View */,
8EE3EFF22A5593B2005585C7 /* Store */,
);
path = Login;
sourceTree = "<group>";
Expand All @@ -279,6 +289,31 @@
path = TextField;
sourceTree = "<group>";
};
8EE3EFF22A5593B2005585C7 /* Store */ = {
isa = PBXGroup;
children = (
A51E1C362A405DDF00E4EBA3 /* LoginStore.swift */,
);
path = Store;
sourceTree = "<group>";
};
8EE3EFF32A5593C2005585C7 /* View */ = {
isa = PBXGroup;
children = (
8EE137772A4B09FB00A0D665 /* LoginView.swift */,
8ECF90E82A4EEBEB00069225 /* LoginButton.swift */,
);
path = View;
sourceTree = "<group>";
};
8EE3EFF42A5593CE005585C7 /* Model */ = {
isa = PBXGroup;
children = (
8EE3EFF52A5593E6005585C7 /* Login.swift */,
);
path = Model;
sourceTree = "<group>";
};
8EF5C5B82A544E4A0025E589 /* UserInfo */ = {
isa = PBXGroup;
children = (
Expand All @@ -294,18 +329,14 @@
path = UserInfo;
sourceTree = "<group>";
};
A51E1C352A405DD200E4EBA3 /* Login */ = {
isa = PBXGroup;
children = (
A51E1C362A405DDF00E4EBA3 /* LoginStore.swift */,
);
path = Login;
sourceTree = "<group>";
};
A51E1C3B2A4061B900E4EBA3 /* API */ = {
isa = PBXGroup;
children = (
8EE3EFF42A5593CE005585C7 /* Model */,
A51E1C3C2A4061C800E4EBA3 /* AppDelegate.swift */,
8EE3EFF72A55AD54005585C7 /* Urls.swift */,
8E5A378F2A5C27BB000281AC /* LoginApi.swift */,
8E5A37912A5C298B000281AC /* DCInterceptor.swift */,
);
path = API;
sourceTree = "<group>";
Expand Down Expand Up @@ -458,13 +489,15 @@
files = (
8EF5C5B32A544DBF0025E589 /* TermsAgreementView.swift in Sources */,
8EE1377B2A4B12B100A0D665 /* DCTextField.swift in Sources */,
8E5A37902A5C27BB000281AC /* LoginApi.swift in Sources */,
8EF5C5C62A544E4A0025E589 /* HeightWeightInputView.swift in Sources */,
8EF5C5DE2A544EE40025E589 /* View+hideKeyboard.swift in Sources */,
8EF5C5C82A544E4A0025E589 /* MedicalConditionInputView.swift in Sources */,
8EF5C5D92A544EC10025E589 /* SelectButton.swift in Sources */,
8ECF90E92A4EEBEB00069225 /* LoginButton.swift in Sources */,
A51E1C3D2A4061C800E4EBA3 /* AppDelegate.swift in Sources */,
8EF5C5C22A544E4A0025E589 /* AllergyInputView.swift in Sources */,
8EE3EFF62A5593E6005585C7 /* Login.swift in Sources */,
8EF5C5C12A544E4A0025E589 /* ExerciseSleepInputView.swift in Sources */,
65F8854C2A3275B000D151F1 /* Color+Primary.swift in Sources */,
8EF5C5C42A544E4A0025E589 /* GenderBirthdayInputView.swift in Sources */,
Expand All @@ -474,18 +507,21 @@
8EE137782A4B09FB00A0D665 /* LoginView.swift in Sources */,
655D9B962A39DBED005E133E /* DCButton+Style.swift in Sources */,
A51E1C372A405DDF00E4EBA3 /* LoginStore.swift in Sources */,
8E5A37922A5C298B000281AC /* DCInterceptor.swift in Sources */,
A526DDC52A092B0300B682BF /* ContentView.swift in Sources */,
8EF5C5B42A544DBF0025E589 /* SigninCompleteView.swift in Sources */,
A51E1C432A406A4800E4EBA3 /* RegistrationStore.swift in Sources */,
8EF5C5C32A544E4A0025E589 /* UserInfoCompleteView.swift in Sources */,
65F8854F2A32764900D151F1 /* View+LineHeightFont.swift in Sources */,
8EF5C5C52A544E4A0025E589 /* PreferredFoodInputView.swift in Sources */,
8EE3EFF82A55AD54005585C7 /* Urls.swift in Sources */,
655D9B922A39DA0C005E133E /* DCButton.swift in Sources */,
655D9B8E2A39D815005E133E /* Color+Neutral.swift in Sources */,
A526DDC32A092B0300B682BF /* DiningCoachApp.swift in Sources */,
65F885532A327AF700D151F1 /* SplashView.swift in Sources */,
A51E1C462A406BAC00E4EBA3 /* Validation.swift in Sources */,
8EF5C5DA2A544EC10025E589 /* CheckButton.swift in Sources */,
8EA4CBF12A5EDCEA00734ED3 /* TokenManager.swift in Sources */,
655D9B942A39DA47005E133E /* DCButtonStyle.swift in Sources */,
8EF5C5DD2A544EE40025E589 /* View+CornerRadius.swift in Sources */,
);
Expand Down Expand Up @@ -617,10 +653,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = DiningCoach/DiningCoach.entitlements;
CODE_SIGN_STYLE = Automatic;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"DiningCoach/Preview Content\"";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = D8Z3QRBB63;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "DiningCoach/Supporting Files/Info.plist";
Expand All @@ -634,8 +672,10 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.DiningCoach;
PRODUCT_BUNDLE_IDENTIFIER = com.diningcoach.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "DiningCoach Development";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
Expand All @@ -652,10 +692,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = DiningCoach/DiningCoach.entitlements;
CODE_SIGN_STYLE = Automatic;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"DiningCoach/Preview Content\"";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = D8Z3QRBB63;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "DiningCoach/Supporting Files/Info.plist";
Expand All @@ -669,8 +711,10 @@
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.DiningCoach;
PRODUCT_BUNDLE_IDENTIFIER = com.diningcoach.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "DiningCoach Development";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
Expand Down
19 changes: 19 additions & 0 deletions DiningCoach/Sources/API/DCInterceptor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// DCInterceptor.swift
// DiningCoach
//
// Created by 이송미 on 2023/07/10.
//

import Foundation
import Alamofire

final class DCInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.headers.add(.accept("application/json"))
urlRequest.headers.add(.contentType("application/json"))

completion(.success(urlRequest))
}
}
63 changes: 63 additions & 0 deletions DiningCoach/Sources/API/LoginApi.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// LoginAPI.swift
// DiningCoach
//
// Created by 이송미 on 2023/07/10.
//

import Foundation
import Alamofire

final public class LoginApi {
static let shared = LoginApi()
let loginSession: Session

init() {
let apiConfiguration = URLSessionConfiguration.default
apiConfiguration.tlsMinimumSupportedProtocolVersion = .TLSv12
loginSession = Session(configuration: apiConfiguration, interceptor: DCInterceptor())
}

}

extension LoginApi {
func loginWithSso(platformType: PlatformType, userAgent: String, accessToken: String, completion: @escaping (SsoResponse?, Error?) -> Void) {
loginSession.request(Urls.compose(path: Paths.requestSso), method: .post, parameters: ["platformType": platformType, "userAgent": userAgent, "accessToken": accessToken])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@songmilee
Method 호출부 코드가 너무 길어지면 parameter를 기준으로 적절하게 개행하시면 가독성이 더 높아질 것입니다

.responseDecodable(of: SsoResponse.self) { [weak self] response in
self?.handleResponse(response: response, completion: completion)
}
}

func loginWithEmail(email: String, password: String, completion: @escaping (SsoResponse?, Error?) -> Void) {
loginSession.request(Urls.compose(path: Paths.login), method: .post, parameters: ["email": email, "password": password])
.responseDecodable(of: SsoResponse.self) { [weak self] response in
self?.handleResponse(response: response, completion: completion)
}
}

func register(email: String, password: String, completion: @escaping (SsoResponse?, Error?) -> Void) {
loginSession.request(Urls.compose(path: Paths.register), method: .post, parameters: ["email": email, "password": password])
.responseDecodable(of: SsoResponse.self) { [weak self] response in
self?.handleResponse(response: response, completion: completion)
}
}

func refresh(userId: String, refreshToken: String, completion: @escaping (SsoResponse? , Error?) -> Void) {
loginSession.request(Urls.compose(path: Paths.refresh), method: .post, parameters: ["userId": userId, "refreshToken": refreshToken])
.responseDecodable(of: SsoResponse.self) { [weak self] response in
self?.handleResponse(response: response, completion: completion)
}
}
}

extension LoginApi {
private func handleResponse(response: DataResponse<SsoResponse, AFError>, completion: @escaping (SsoResponse?, Error?) -> Void) {
switch response.result {
case .success(let result):
completion(result, nil)
case .failure(let error):
completion(nil, error)
}
Comment on lines +55 to +60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@songmilee
completion closure의 parameter type을 Result<SsoResponse, Error>으로 사용하면 코드가 더 깔끔해 질 것 같습니다

}
}

29 changes: 29 additions & 0 deletions DiningCoach/Sources/API/Model/Login.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Login.swift
// DiningCoach
//
// Created by 이송미 on 2023/07/05.
//

import Foundation

public enum PlatformType: String, Codable {
case kakao, google, apple
}

struct SsoResponse: Codable {
let userId: Int64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@songmilee

  • 특별한 경우가 아니라면 정수 자료형은 Int를 사용하는게 좋습니다. Int는 시스템 아키텍처 종류에 따라 Int64, Int32 등으로 사용됩니다.
  • Swift는 type safe한 언어라서, Int64는 Int와 충돌을 일으켜 불필요한 conversion이 필요하게 되는 단점도 있습니다.

let newUser: Bool?
let accessToken: String
let refreshToken: String
}

struct User: Codable {
let id: Int64
let userName: String
let firstName: String
let email: String
let password: String
let phone: String
let userStatus: Int32
}
27 changes: 27 additions & 0 deletions DiningCoach/Sources/API/Urls.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Urls.swift
// DiningCoach
//
// Created by 이송미 on 2023/07/05.
//

import Foundation

public struct Hosts {
public static let shared = Hosts()

public let base = "virtserver.swaggerhub.com/DININGCOACHTEAM/DiningCoach_API_v1/1.0.0" // "diningcoach.org"
}

public struct Paths {
public static let requestSso = "/api/v1/auth/sso"
public static let login = "/api/v1/auth/login"
public static let register = "/api/v1/auth/register"
public static let refresh = "/api/v1/auth/refresh"
}

public struct Urls {
public static func compose(_ host: String = Hosts.shared.base, path: String) -> String {
return "https://\(host)\(path)"
}
}
41 changes: 41 additions & 0 deletions DiningCoach/Sources/Helper/TokenManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// TokenManager.swift
// DiningCoach
//
// Created by 이송미 on 2023/07/12.
//

import Foundation


final class TokenManager {
public static let shared = TokenManager()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@songmilee
여기서 public이 꼭 필요한가요?

var token: SsoResponse? = nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@songmilee
아래에 구현 방식들을 보니, token은 computed property로 만들어 사용하는게 더 편해 보이네요

final class TokenManager {
    
    var token: SsoResponse? {
        get {
            guard let tokenData = UserDefaults.standard.data(forKey: key) else { return nil }
            return try? JSONDecoder().decode(SsoResponse.self, from: tokenData)
        }
        
        set {
            guard let data = try? JSONEncoder().encode(token) else  { return }
            UserDefaults.standard.set(data, forKey: key)
        }
    }
}

이런 형태의 computed property는 property wrapper를 사용하면 더 간단하게 사용할 수도 있습니다.

final class TokenManager {
    
    @SSOResponse
    var token: SsoResponse?
}


let key = "com.diningcoach.token"

init() {
if let tokenData = UserDefaults.standard.data(forKey: key) {
let token = try? JSONDecoder().decode(SsoResponse.self, from: tokenData)
self.token = token
}
Comment on lines +18 to +21
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@songmilee
guard문을 사용하면 depth가 깊어지지 않고 깔끔하게 코드를 작성할 수 있습니다.

init() {
    guard let tokenData = UserDefaults.standard.data(forKey: key) else { return }
    token = try? JSONDecoder().decode(SsoResponse.self, from: tokenData)
}

}

func setToken(token: SsoResponse) {
if let data = try? JSONEncoder().encode(token) {
UserDefaults.standard.set(data, forKey: key)
UserDefaults.standard.synchronize()

self.token = token
}
Comment on lines +25 to +30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@songmilee
여기도 guard문을 사용하면 더 간단하게 작성할 수 있어 보입니다

}

func getToken() -> SsoResponse? {
return token
}

func deleteToken() {
UserDefaults.standard.removeObject(forKey: key)
self.token = nil
}
}
Loading