<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>내일도이렇게</title>
    <link>https://jmkim.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 18 May 2026 15:55:54 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>내일도이렇게</managingEditor>
    <image>
      <title>내일도이렇게</title>
      <url>https://tistory1.daumcdn.net/tistory/2997295/attach/25fec2d79b714c9ab1dc34dde91f3f06</url>
      <link>https://jmkim.tistory.com</link>
    </image>
    <item>
      <title>iOS 애플 로그인 연동</title>
      <link>https://jmkim.tistory.com/173</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; Project &amp;gt; Signining &amp;amp; Capablilities&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sign in With Apple 추가&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FHJXI/btsDHmEMIqg/RNSiMQ7uHXQbRmIGt1aX31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FHJXI/btsDHmEMIqg/RNSiMQ7uHXQbRmIGt1aX31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FHJXI/btsDHmEMIqg/RNSiMQ7uHXQbRmIGt1aX31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFHJXI%2FbtsDHmEMIqg%2FRNSiMQ7uHXQbRmIGt1aX31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1496&quot; height=&quot;150&quot; data-origin-width=&quot;1496&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Apple Developer - Certificates, Identifiers &amp;amp; Profiles&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플 로그인 추가할 앱에 Sign In with Apple 추가&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7MsZI/btsDGY5wsLX/0SMvkxEboydzDoyH2YcCDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7MsZI/btsDGY5wsLX/0SMvkxEboydzDoyH2YcCDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7MsZI/btsDGY5wsLX/0SMvkxEboydzDoyH2YcCDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7MsZI%2FbtsDGY5wsLX%2F0SMvkxEboydzDoyH2YcCDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2016&quot; height=&quot;152&quot; data-origin-width=&quot;2016&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 애플 로그인 버튼 추가&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNmdG4/btsDGlzA0yW/oxqclNeOkpHnF3qdQN71k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNmdG4/btsDGlzA0yW/oxqclNeOkpHnF3qdQN71k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNmdG4/btsDGlzA0yW/oxqclNeOkpHnF3qdQN71k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNmdG4%2FbtsDGlzA0yW%2FoxqclNeOkpHnF3qdQN71k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;174&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 코드 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ASAuthorizationControllerPresentationContextProviding&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인증 컨트롤러를 어느 컨트롤러나 뷰에 표시할지 결정하는데 사용&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705814835646&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import AuthenticationServices

extension Root.ViewController: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -&amp;gt; ASPresentationAnchor {
        return self.view.window!
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1705815068065&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 애플 로그인 버튼 실행 
appleLoginButton.rx.tap
         .bind {
            self.startSignInWithAppleFlow()
         }.disposed(by: disposeBag)

// 애플 로그인 요청 
func startSignInWithAppleFlow() {
        let nonce = randomNonceString()
        currentNonce = nonce
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = sha256(nonce)
        
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
 }
    
 private func sha256(_ input: String) -&amp;gt; String {
        let inputData = Data(input.utf8)
        let hashedData = SHA256.hash(data: inputData)
        let hashString = hashedData.compactMap {
            return String(format: &quot;%02x&quot;, $0)
        }.joined()
        
        return hashString
 }
    
  // Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
 private func randomNonceString(length: Int = 32) -&amp;gt; String {
        precondition(length &amp;gt; 0)
        let charset: Array&amp;lt;Character&amp;gt; =
            Array(&quot;0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._&quot;)
        var result = &quot;&quot;
        var remainingLength = length
        
        while remainingLength &amp;gt; 0 {
            let randoms: [UInt8] = (0 ..&amp;lt; 16).map { _ in
                var random: UInt8 = 0
                let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &amp;amp;random)
                if errorCode != errSecSuccess {
                    fatalError(&quot;Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)&quot;)
                }
                return random
            }
            
            randoms.forEach { random in
                if remainingLength == 0 {
                    return
                }
                
                if random &amp;lt; charset.count {
                    result.append(charset[Int(random)])
                    remainingLength -= 1
                }
            }
        }
        
        return result
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ASAuthorizationControllerDelegate&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 사용자 인증에 대한 처리&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705815639273&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
  if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            guard let nonce = currentNonce else {
                fatalError(&quot;Invalid state: A login callback was received, but no login request was sent.&quot;)
            }
            guard let appleIDToken = appleIDCredential.identityToken else {
                print(&quot;Unable to fetch identity token&quot;)
                return
            }
            guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                print(&quot;Unable to serialize token string from data: \(appleIDToken.debugDescription)&quot;)
                return
            }
            
            let credential = OAuthProvider.credential(withProviderID: &quot;apple.com&quot;, idToken: idTokenString, rawNonce: nonce)
            let credentialResult = try await Auth.auth().signIn(with: oauth)
            
            // ...
            
     }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSviDV/btsDKJla9ZC/KzSE1OCU2ZBYKFYYZeiQA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSviDV/btsDKJla9ZC/KzSE1OCU2ZBYKFYYZeiQA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSviDV/btsDKJla9ZC/KzSE1OCU2ZBYKFYYZeiQA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSviDV%2FbtsDKJla9ZC%2FKzSE1OCU2ZBYKFYYZeiQA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;271&quot; height=&quot;450&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;1420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IOS/swift</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/173</guid>
      <comments>https://jmkim.tistory.com/173#entry173comment</comments>
      <pubDate>Sun, 21 Jan 2024 14:52:09 +0900</pubDate>
    </item>
    <item>
      <title>iOS 구글 로그인 연동</title>
      <link>https://jmkim.tistory.com/172</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 14 이상 / GoogleSignIn 7.0.0 버전에 대한 구글 로그인 연동&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. GoogleSignIn 설치&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;pod&amp;nbsp;'GoogleSignIn',&amp;nbsp;'7.0.0'&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 파이어베이스 구글 로그인 설정&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btmeGP/btsDKJlagWA/kbOtmqKTGWq8AuzKDT9eNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btmeGP/btsDKJlagWA/kbOtmqKTGWq8AuzKDT9eNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btmeGP/btsDKJlagWA/kbOtmqKTGWq8AuzKDT9eNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtmeGP%2FbtsDKJlagWA%2FkbOtmqKTGWq8AuzKDT9eNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;136&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. OAuth 클라이언트 ID 만들기&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/identity/sign-in/ios/start-integrating?hl=ko#get_an_oauth_client_id&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/identity/sign-in/ios/start-integrating?hl=ko#get_an_oauth_client_id&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705810054010&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;iOS 및 macOS용 Google 로그인 시작하기 &amp;nbsp;|&amp;nbsp; Authentication &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 의견 보내기 iOS 및 macOS용 Google 로그인 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/identity/sign-in/ios/start-integrating?hl=ko#get_an_oauth_client_id&quot; data-og-url=&quot;https://developers.google.com/identity/sign-in/ios/start-integrating?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/caOGRS/hyU5H6b5nN/V2k1Y2vTS9QMvz2HSXg2ok/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://developers.google.com/identity/sign-in/ios/start-integrating?hl=ko#get_an_oauth_client_id&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/identity/sign-in/ios/start-integrating?hl=ko#get_an_oauth_client_id&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/caOGRS/hyU5H6b5nN/V2k1Y2vTS9QMvz2HSXg2ok/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;iOS 및 macOS용 Google 로그인 시작하기 &amp;nbsp;|&amp;nbsp; Authentication &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 의견 보내기 iOS 및 macOS용 Google 로그인 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFcbbF/btsDKz3VOUD/L0kSGYBhIJIx6CyTAhD0aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFcbbF/btsDKz3VOUD/L0kSGYBhIJIx6CyTAhD0aK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFcbbF/btsDKz3VOUD/L0kSGYBhIJIx6CyTAhD0aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFcbbF%2FbtsDKz3VOUD%2FL0kSGYBhIJIx6CyTAhD0aK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;338&quot; height=&quot;298&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정한 프로젝트의 OAuth client 클릭하여 &lt;b&gt;클라이언트 ID 와 iOS URL 스키마&lt;/b&gt; 두가지 정보를 저장해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;Info.plist 에 행 추가하여 GIDClientId: 클라이언트 ID 를 추가한다.&amp;nbsp;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 &amp;gt; Info &amp;gt;URL Types&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 버튼으로 추가하여 URL Schemes 에 URL Schemes 에 iOS URL 스키마를 입력한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bggpRq/btsDKeeCEAp/Lax0s7azAFgykuaeGodXfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bggpRq/btsDKeeCEAp/Lax0s7azAFgykuaeGodXfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bggpRq/btsDKeeCEAp/Lax0s7azAFgykuaeGodXfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbggpRq%2FbtsDKeeCEAp%2FLax0s7azAFgykuaeGodXfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;818&quot; height=&quot;488&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 코드 구현하기&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1705811132002&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AppDelegate.Swift    

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -&amp;gt; Bool {
        return GIDSignIn.sharedInstance.handle(url)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1705811411066&quot; class=&quot;swift&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// 로그인 호출 
let googleResult = try await GIDSignIn.sharedInstance
                     .signIn(withPresenting: self ?? Root.ViewController(viewModel: Root.ViewModel()))


let authentication = googleResult.user
let authIdToken = authentication.idToken
            
let credential = GoogleAuthProvider.credential(withIDToken: authIdToken?.tokenString ?? &quot;&quot;, accessToken: authentication.accessToken.tokenString)
let credentialResult = try await Auth.auth().signIn(with: credential)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bah9BZ/btsDNJdTm9l/FWdqh2vI7x38XFhSV0A0D0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bah9BZ/btsDNJdTm9l/FWdqh2vI7x38XFhSV0A0D0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bah9BZ/btsDNJdTm9l/FWdqh2vI7x38XFhSV0A0D0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbah9BZ%2FbtsDNJdTm9l%2FFWdqh2vI7x38XFhSV0A0D0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;448&quot; height=&quot;245&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;1098&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8Yz3/btsDJk0J8wl/Ffh03mwa8aanhvrERjO1a0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8Yz3/btsDJk0J8wl/Ffh03mwa8aanhvrERjO1a0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8Yz3/btsDJk0J8wl/Ffh03mwa8aanhvrERjO1a0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8Yz3%2FbtsDJk0J8wl%2FFfh03mwa8aanhvrERjO1a0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;289&quot; height=&quot;1098&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;1098&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IOS/swift</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/172</guid>
      <comments>https://jmkim.tistory.com/172#entry172comment</comments>
      <pubDate>Sun, 21 Jan 2024 13:40:36 +0900</pubDate>
    </item>
    <item>
      <title>만보기 iOS 앱 개발기</title>
      <link>https://jmkim.tistory.com/171</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2022년 06월에 블록체인 서비스를 하고 있는 회사에 입사하여 혼자서 프로젝트 세팅부터 출시까지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만보기 iOS 앱 개발 대해 간략하게 정리하고자 한다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지원 버전, 언어&lt;/li&gt;
&lt;li&gt;아키텍처&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;UI 개발&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;라이브러리 관리&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;연동 작업 (푸시, 로깅 등)&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;배포(CI/CD)&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프로젝트&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 지원 버전, 언어&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;최소 지원 버전 iOS 13, Swift 5.7.2로 개발하여 Async/Await를 사용하여 API 비동기 처리하였습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1696839254038&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // View 에서 /me API 호출 

 Task {
        do {
           let userInfo = try await self.viewModel.getMe(authorization: authorization, deviceId: deviceId)
        } catch {
          // 에러 처리 
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1696839279139&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // ViewModel  
  func getMe(authorization: String, deviceId: String) async throws -&amp;gt; UserInfo {
        do {
            let result = try await self.userNetwork.getMe(authorization: authorization, deviceId: deviceId)
            return result
        } catch {
            throw error
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1696839409537&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// /me API 호출하는 메소드 
func getMe(authorization: String, deviceId: String) async throws -&amp;gt; UserInfo {
        
        let url = UserEndPoint.me.url
        var request = URLRequest(url: url)
        request.httpMethod = RequestMethod.get.rawValue
        request.addValue(authorization, forHTTPHeaderField: &quot;Authorization&quot;)
        request.addValue(deviceId, forHTTPHeaderField: &quot;x-device-id&quot;)
        
        do {
            let (data, response) = try await URLSession.shared.data(from: request)
            guard let response = response as? HTTPURLResponse else { throw ErrorModel.noResponse }
                        
            switch response.statusCode {
            case 200 ... 299 :
                let decoder = JSONDecoder()
          
                guard let result = try? decoder.decode(UserInfo.self, from: data)
                else {
                    throw ErrorModel.invalidJSON
                }
                return result
        // 상태 코드 처리 
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM 아키텍처 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UI 개발 방법&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StoryBoard 를 사용하지 않고 코드기반인 Snapkit으로 사용하였음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라이브러리 관리&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CocoaPods 으로 라이브러리 관리하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&amp;nbsp;사용한 라이브러리&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RxSwift: 비동기 처리&amp;nbsp;&lt;/li&gt;
&lt;li&gt;SwiftGen: 에셋 카탈로그 코드 처리&amp;nbsp;&lt;/li&gt;
&lt;li&gt;SnapKit: UI 코드 처리&lt;/li&gt;
&lt;li&gt;Firebase: 파이어베이스 관련&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Kingfisher: 이미지 다운로드 / 캐싱&lt;/li&gt;
&lt;li&gt;PanModal: 바텀 시트 UI&lt;/li&gt;
&lt;li&gt;lottie-ios: 로티 애니메이션 처리&amp;nbsp;&lt;/li&gt;
&lt;li&gt;swiftLint: 코딩 컨벤션을 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;연동작업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;푸시&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- OneSignal: 앱 푸시 처리에 대해 사용&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 로깅&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Firebase Analytics: 사용자 로그 이벤트를 Firebase에 로그 수집&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크래시분석&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Firebase Crashlytics: 앱 비정상 종료 데이터를 감지하여 어떤 부분이 비정상 되는지 파악할 수 있었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;광고 연동&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Adop:&amp;nbsp; 배너/비디오/전면 광고 플랫폼을 이용하여 광고 작업을 하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x9EfS/btsxkESJKH0/ThsZfwuV7DxJr6IM8RZ75k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x9EfS/btsxkESJKH0/ThsZfwuV7DxJr6IM8RZ75k/img.png&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;1666&quot; data-is-animation=&quot;false&quot; width=&quot;291&quot; height=&quot;599&quot; data-widthpercent=&quot;50.16&quot; style=&quot;width: 49.581%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x9EfS/btsxkESJKH0/ThsZfwuV7DxJr6IM8RZ75k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx9EfS%2FbtsxkESJKH0%2FThsZfwuV7DxJr6IM8RZ75k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;1666&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1tPxk/btsxq2McbxT/rmbSzLfAWGDkV5daJXsJI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1tPxk/btsxq2McbxT/rmbSzLfAWGDkV5daJXsJI1/img.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;1648&quot; data-is-animation=&quot;false&quot; width=&quot;252&quot; height=&quot;522&quot; data-widthpercent=&quot;49.84&quot; style=&quot;width: 49.2562%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1tPxk/btsxq2McbxT/rmbSzLfAWGDkV5daJXsJI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1tPxk%2Fbtsxq2McbxT%2FrmbSzLfAWGDkV5daJXsJI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;796&quot; height=&quot;1648&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에 Github Actions 을 사용하여 빌드 테스트 및 배포를 하였지만 스크립트 오류로 인해 지금은 사용 못하고 있어&amp;nbsp;&lt;br /&gt;추후에 개선할 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 수동으로 Xcode Archive로 데브/라이브앱 구분하여 아카이브 하여 TestFlight에 업로드를 진행하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선점&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD 개선&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SwiftUI&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처 연구&amp;nbsp;&lt;/p&gt;</description>
      <category>IOS</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/171</guid>
      <comments>https://jmkim.tistory.com/171#entry171comment</comments>
      <pubDate>Mon, 9 Oct 2023 16:46:13 +0900</pubDate>
    </item>
    <item>
      <title>iOS 환경별 Build 세팅</title>
      <link>https://jmkim.tistory.com/170</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 운영하다 보면 개발/라이브 버전 등 다양한 환경에 따라 Target을 만들 필요가 있는데 이에 대해 알아보고자 한다. &amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Product bundle Identifier 구분&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 고유 식별자인 Product bundle Identifier 를 디버그/릴리즈 버전을 다르게 지정한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 탐색기에서 앱 target &amp;gt; Build Settings &amp;gt; Product Bundle Identifier&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo2ybm/btr8XzTTH6b/NWcPD2Az0UGpSv9o18k8w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo2ybm/btr8XzTTH6b/NWcPD2Az0UGpSv9o18k8w1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo2ybm/btr8XzTTH6b/NWcPD2Az0UGpSv9o18k8w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo2ybm%2Fbtr8XzTTH6b%2FNWcPD2Az0UGpSv9o18k8w1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;725&quot; height=&quot;81&quot; data-origin-width=&quot;1182&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 앱 아이콘 구분&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버그/릴리즈 버전 앱 식별하기 위해 앱 아이콘 이미지를 다르게 지정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwcGUL/btr8LOkBCQW/kCQveO5HOK8ij6MiLWl7xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwcGUL/btr8LOkBCQW/kCQveO5HOK8ij6MiLWl7xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwcGUL/btr8LOkBCQW/kCQveO5HOK8ij6MiLWl7xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwcGUL%2Fbtr8LOkBCQW%2FkCQveO5HOK8ij6MiLWl7xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;86&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 탐색기 앱의 target &amp;gt; Build Settings &amp;gt; Primary App Icon Set Name 디버그/릴리즈 버전에 맞게 앱 아이콘을 지정한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o9mgW/btr8NEaxB90/O5vKlKYPbOBAoKUbDZziJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o9mgW/btr8NEaxB90/O5vKlKYPbOBAoKUbDZziJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o9mgW/btr8NEaxB90/O5vKlKYPbOBAoKUbDZziJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo9mgW%2Fbtr8NEaxB90%2FO5vKlKYPbOBAoKUbDZziJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;129&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Manage Schemes를 통해 Debug 용 Scheme 추가&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;Debug Schemes를 추가하므로 Signing 이 Debug/ Release 가 추가된 걸 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m825E/btr8LhmYcpY/3uwKgK6iTKC2McJXU2LPSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m825E/btr8LhmYcpY/3uwKgK6iTKC2McJXU2LPSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m825E/btr8LhmYcpY/3uwKgK6iTKC2McJXU2LPSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm825E%2Fbtr8LhmYcpY%2F3uwKgK6iTKC2McJXU2LPSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;285&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yytvr/btr821Qp0wd/Z8EuqXTJTX6z2zj84Bku1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yytvr/btr821Qp0wd/Z8EuqXTJTX6z2zj84Bku1k/img.png&quot; data-alt=&quot;디버그/릴리즈으로 앱이 구분이 된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yytvr/btr821Qp0wd/Z8EuqXTJTX6z2zj84Bku1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYytvr%2Fbtr821Qp0wd%2FZ8EuqXTJTX6z2zj84Bku1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;238&quot; height=&quot;154&quot; data-origin-width=&quot;161&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;디버그/릴리즈으로 앱이 구분이 된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IOS/ios</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/170</guid>
      <comments>https://jmkim.tistory.com/170#entry170comment</comments>
      <pubDate>Sun, 9 Apr 2023 21:31:19 +0900</pubDate>
    </item>
    <item>
      <title>Application 상태값</title>
      <link>https://jmkim.tistory.com/169</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 앱의 상태값은 UIApplication 의 appilcationState 를 통해 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UIApplication.State.active&amp;nbsp;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱이 활성화된 상태&amp;nbsp;&lt;/li&gt;
&lt;li&gt;앱이 실행중이며, 현재 사용자 인터페이스가 화면에 표시되고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UIApplication.State.inactive&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱이 비활성화 상태&lt;/li&gt;
&lt;li&gt;앱이 실행 중이지만, 현재 사용자 인터페이스가 화면에 표시되지 않는 상태&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UIApplication.State.background&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱이 백그라운드에 있는 상태&lt;/li&gt;
&lt;li&gt;앱이 백그라운드에서 실행 중이며, 사용자 인터페이스가 화면에 표시되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;푸시 메세지 터치시 앱 상태에 따라서 다르게 처리 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1681040150288&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], 
   fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -&amp;gt; Void) {
 
         if application.applicationState == .active  {
         
         } else if  application.applicationState == .background {
         
         } else if application.applicationState == .inactive {
         
         }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IOS/ios</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/169</guid>
      <comments>https://jmkim.tistory.com/169#entry169comment</comments>
      <pubDate>Sun, 9 Apr 2023 20:36:53 +0900</pubDate>
    </item>
    <item>
      <title>iOS ViewController 생명주기</title>
      <link>https://jmkim.tistory.com/155</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;1618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be3UV2/btrU0uNgQMp/mVgxPd9vnZJ9RbGHnSm0UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be3UV2/btrU0uNgQMp/mVgxPd9vnZJ9RbGHnSm0UK/img.png&quot; data-alt=&quot;View controller Life Cycle -&amp;amp;amp;nbsp;https://www.edwith.org/boostcourse-ios/lecture/16858/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be3UV2/btrU0uNgQMp/mVgxPd9vnZJ9RbGHnSm0UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe3UV2%2FbtrU0uNgQMp%2FmVgxPd9vnZJ9RbGHnSm0UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;540&quot; height=&quot;583&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;1618&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;View controller Life Cycle -&amp;amp;nbsp;https://www.edwith.org/boostcourse-ios/lecture/16858/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;loadView&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰 컨트롤러의 기본 view 생성하고 할당&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;viewDidLoad&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리에 처음 로드될 때 한번 호출&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;viewWillLayoutSubviews&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰의 바운드가 최종적으로 결정되는 최초 시점&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;viewDidLayoutSubviews&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서브 뷰의 레이아웃이 결정되고 난 후에 호출&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;viewWillAppear&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면이 나타나기 직전에 호출&amp;nbsp;&lt;/li&gt;
&lt;li&gt;화면이 나타날때마다 수행하는 작업&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;viewDidAppear&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면이 나타나면 호출&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;b&gt;viewWillDisapper&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;화면이 사라지기 직전에 호출&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp; &lt;b&gt;viewDidDisappear&lt;/b&gt;&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사라진 후에 호출&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※&amp;nbsp; 메서드들 사용할 때는 override 와 super 키워드를 작성해야한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1672574907809&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; override func viewDidLoad() {
     super.viewDidLoad()
 }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IOS/UIKit</category>
      <category>ios</category>
      <category>SWIFT</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/155</guid>
      <comments>https://jmkim.tistory.com/155#entry155comment</comments>
      <pubDate>Sun, 3 Jul 2022 15:52:55 +0900</pubDate>
    </item>
    <item>
      <title>[RxSwift] Subject 알아보기</title>
      <link>https://jmkim.tistory.com/154</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;PublishSubject&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 상태로 시작하여 새로운 값만을 subscriber 에 방출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독 이후에 발생하는 모든 이벤트를 전달받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxJW4R/btrzPgoE4fX/Osz4hXW1KUgV77hKDKc5Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxJW4R/btrzPgoE4fX/Osz4hXW1KUgV77hKDKc5Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxJW4R/btrzPgoE4fX/Osz4hXW1KUgV77hKDKc5Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxJW4R%2FbtrzPgoE4fX%2FOsz4hXW1KUgV77hKDKc5Qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;429&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649741086445&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let disposeBag = DisposeBag()

print(&quot;-------publishSubject-------&quot;)
let publishSubject = PublishSubject&amp;lt;String&amp;gt;()

publishSubject.onNext(&quot;여러분 안녕하세요?&quot;)

let 구독자1 = publishSubject
    .subscribe(onNext: {
        print($0)
    })
//    .disposed(by: disposeBag)

publishSubject.on(.next(&quot;1&quot;))
publishSubject.onNext(&quot;2&quot;)

let 구독자2 = publishSubject
    .subscribe {
        print(&quot;두번째 구독:&quot;, $0.element ?? $0)
    }

publishSubject.onNext(&quot;3&quot;)

구독자1.dispose()

publishSubject.onNext(&quot;4&quot;)
publishSubject.onCompleted()

publishSubject.onNext(&quot;5&quot;)

구독자2.dispose()


publishSubject
    .subscribe {
        print(&quot;세번째 구독:&quot;, $0.element ?? $0)
    }
    .disposed(by: disposeBag)

publishSubject.onNext(&quot;찍힐까요?&quot;)

-------publishSubject-------
1
2
3
두번째 구독: 3
두번째 구독: 4
두번째 구독: completed
세번째 구독: completed&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BehaviorSubject&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 초기값을 가진 상태로 시작하여, 새로운 subscriber 에게 초기값 또는 최신값을 방출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독 전에 발생한 최근 요소와 이후에 발생하는 모든것들을 전달 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxDCsg/btrzQFab6dS/YXp3Vgvj5Zdqo9G8H6BAa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxDCsg/btrzQFab6dS/YXp3Vgvj5Zdqo9G8H6BAa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxDCsg/btrzQFab6dS/YXp3Vgvj5Zdqo9G8H6BAa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxDCsg%2FbtrzQFab6dS%2FYXp3Vgvj5Zdqo9G8H6BAa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;460&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1649741275258&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;-------behaviorSubject-------&quot;)
enum SubjectError: Error {
    case error1
}

let behaviorSubject = BehaviorSubject&amp;lt;String&amp;gt;(value: &quot;초기값&quot;)

behaviorSubject.onNext(&quot;첫번째값&quot;)

behaviorSubject.subscribe {
    print(&quot;첫번째 구독:&quot;, $0.element ?? $0)
}
.disposed(by: disposeBag)

behaviorSubject.onError(SubjectError.error1)

behaviorSubject.subscribe {
    print(&quot;두번째 구독:&quot;, $0.element ?? $0)
}
.disposed(by: disposeBag)

let value = try? behaviorSubject.value()
print(value)

-------behaviorSubject-------
첫번째 구독: 첫번째값
첫번째 구독: error(error1)
두번째 구독: error(error1)
nil&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ReplaySubject&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼를 두고 초기화하며, 버퍼 사이즈 만큼의 값들을 유지하면서 새로운 subscriber 에게 방출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 구독 이전에 발생한 내용을 받고 싶다면 RelaySubject 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqF3HU/btrzQY8E8uZ/88sed2aqz2oNPMvLrB7h01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqF3HU/btrzQY8E8uZ/88sed2aqz2oNPMvLrB7h01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqF3HU/btrzQY8E8uZ/88sed2aqz2oNPMvLrB7h01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqF3HU%2FbtrzQY8E8uZ%2F88sed2aqz2oNPMvLrB7h01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;833&quot; height=&quot;515&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1649741500782&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;-------ReplaySubject-------&quot;)
let replaySubject = ReplaySubject&amp;lt;String&amp;gt;.create(bufferSize: 2)

replaySubject.onNext(&quot;a&quot;)
replaySubject.onNext(&quot;b&quot;)
replaySubject.onNext(&quot;c&quot;)

replaySubject.subscribe {
    print(&quot;첫번째 구독:&quot;, $0.element ?? $0)
}
.disposed(by: disposeBag)

replaySubject.subscribe {
    print(&quot;두번째 구독:&quot;, $0.element ?? $0)
}
.disposed(by: disposeBag)

replaySubject.onNext(&quot;d&quot;)
replaySubject.onError(SubjectError.error1)
replaySubject.dispose()

replaySubject.subscribe {
    print(&quot;세번째 구독:&quot;, $0.element ?? $0)
}
.disposed(by: disposeBag)

-------ReplaySubject-------
첫번째 구독: b
첫번째 구독: c
두번째 구독: b
두번째 구독: c
첫번째 구독: d
두번째 구독: d
첫번째 구독: error(error1)
두번째 구독: error(error1)
세번째 구독: error(Object `RxSwift.(unknown context at $12d722e70).ReplayMany&amp;lt;Swift.String&amp;gt;` was already disposed.)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fastcampus.co.kr/dev_online_iosapp&quot;&gt;https://fastcampus.co.kr/dev_online_iosapp&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://reactivex.io/documentation/subject.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://reactivex.io/documentation/subject.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pilgwon.github.io/blog/2018/10/08/Learn-Master-the-Basics-of-RxSwift-in-10-Minutes.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pilgwon.github.io/blog/2018/10/08/Learn-Master-the-Basics-of-RxSwift-in-10-Minutes.html&lt;/a&gt;&lt;/p&gt;</description>
      <category>IOS/RxSwift</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/154</guid>
      <comments>https://jmkim.tistory.com/154#entry154comment</comments>
      <pubDate>Tue, 19 Apr 2022 23:26:26 +0900</pubDate>
    </item>
    <item>
      <title>RxSwift 개념</title>
      <link>https://jmkim.tistory.com/139</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;RxSwift&lt;/b&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift 에서 ReactiveX를 적용시켜 비동기 프로그래밍을 직관적으로 작성할 수 있도록 도와주는 라이브러리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RxSwift 사용하는 이유&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;downloadJson 함수를 처리한 다음에 동기적으로 해야하는 작업들이 많아진다면 콜백 지옥이 발생하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649836801702&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// escaping closure를 사용해서 결과값 전달.
func downloadJson(_ url: String, _ completion: @escaping ((String?) -&amp;gt; Void))? {
    DispatcheQueue.global().async {
        let url = URL(string: url)!
        let data = try! Data(contentsOf: url)
        let json = String(data: data, encoding: .utf8)
        DispatchQueue.main.async {
            completion?(json)
        }
    }
}

@IBOutlet var activityIndicator: UIActivityIndicatorView!

@IBAction func onLoad() {
    editView.text = &quot;&quot;
    setVisibleWithAnimation(self.activityIndicator, true)

    downloadJson(MEMBER_LIST_URL) { json in
        self.editView.text = json
        self.setVisibleWithAnimation(self.activityIndicator, false)
        
        self.downloadJson(MEMBER_LIST_URL) { json in
            self.editView.text = json
            self.setVisibleWithAnimation(self.activityIndicator, false)
          
          
          self.downloadJson(MEMBER_LIST_URL) { json in
            self.editView.text = json
            self.setVisibleWithAnimation(self.activityIndicator, false)
            
            /...
            
    	   }   
    	}
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수를 없애기 위해 closure 대신 return 값을 받아서 처리할수 있게 변형한다면 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;class 나중에생기는데이터&amp;lt;T&amp;gt; {
    private let task: (@escaping (T) -&amp;gt; Void) -&amp;gt; Void
    
    init(task: @escaping (@escaping (T) -&amp;gt; Void) -&amp;gt; Void) {
        self.task = task
    }
    
    func 나중에오면(_ f: @escaping (T) -&amp;gt; Void) {
        task(f)
    }
}

func downloadJson(_ url : String) -&amp;gt; 나중에생기는데이터&amp;lt;String?&amp;gt; {
    return 나중에생기는데이터() { f in
        let url = URL(string: url)!
        let data = try! Data(contentsof: url)
        let json = String(data: data, encoding: .utf8)
        DispatchQueue.main.async {
            f(json)
        }
    }
}

@IBOutlet var activityIndicator: UIActivityIndicatorView!

@IBAction func onLoad() {
    editView.text = &quot;&quot;
    setVisibleWithAnimation(self.activityIndicator, true)
    
    
    let json: 나중에생기는데이터&amp;lt;String?&amp;gt; = downloadJson(MEMBER_LIST_URL)
    
    // task 를 실행하는데, task의 인자인 f라는 함수를 넘겨주고 있다.
    // f에 들어가는 인자는 json으로 받아서 사용.
    json.나중에오면 { json in
        self.editView.text = json
        self.setVisibleWithAnimation(self.activityIndicator, false)
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 아이디어로 나온게 RxSwift 인데 아래 두가지를 처리해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;비동기로 생기는 데이터를 Observable로 감써서 리턴하는 방법&lt;/li&gt;
&lt;li&gt;Observable 로 오는 데이터르 받아서 처리하는 방법&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Observable 을 이용해서 만들면 클로저 없이 리턴형식으로 비동기를 처리할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 비동기로 생기는 데이터를 Observable로 감써서 리턴하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Observable의 생명주기&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. create&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. subscribe&amp;nbsp; - subscribe 가 있을 때 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. onNext&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. onCompleted / onError&amp;nbsp; ( 끝 )&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp; Disposed&amp;nbsp; &amp;nbsp;- dispose 를 동작하면 재사용 불가능&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;func downloadJson(_ url: String) -&amp;gt; Observable&amp;lt;String?&amp;gt; {

    // 비동기로 생기는 데이터를 observable로 감싸서 return
    return Observable.create() { emitter in
        let url = URL(string: MEMBER_LIST_URL)!
        
        let task = URLSession.shaerd.dataTask(with: url) { (data, _, err) in
        
            guard err == nil else {
                emitter.onError(err!)
                return
            }
            
            if let data = data, let json = String(data: data, encoding: .utf8) {
                emitter.onNext(json)
            }
            
            emitter.onCompleted()
        }
        
        task.resume()
        
        // dispose가 불렸을 때 task를 cancel
        return Disposables.create() {
            task.cancel()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Observable 로 오는 데이터를&amp;nbsp; 받아서 처리하는 방법&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;@IBAction func onLoad() {
    editView.text = &quot;&quot;
    setVisibleWithAnimation(self.activityIndicator, true)
    

    let observable = downloadJson(MEMBER_LIST_URL)
    
    let disposable = observable.subscribe { event in
        switch event {
        case .next(let json):
           
            DispatchQueue.main.async {
                self.editView.text = json 
                self.setVisibleWithAnimation(self.activityIndicator, false)
            }
        case .error(let error):
            break
        case .completed:
            break
        }
    }
    
    disposable.dispose()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;https://www.youtube.com/watch?v=iHKBNYMWd5I&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IOS/RxSwift</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/139</guid>
      <comments>https://jmkim.tistory.com/139#entry139comment</comments>
      <pubDate>Wed, 13 Apr 2022 17:46:55 +0900</pubDate>
    </item>
    <item>
      <title>[RxSwift] Single,Maybe,Completable 알아보기</title>
      <link>https://jmkim.tistory.com/153</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Single&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;success 또는 error 이벤트를 한 번만 방출될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649733687694&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var disposeBag = DisposeBag()

enum TraitsError: Error {
    case single
    case maybe
    case completable
}

print(&quot;-------Single1-------&quot;) // 성공시 
Single&amp;lt;Result&amp;lt;String, TraitsError&amp;gt;&amp;gt;.just(.success(&quot;✅&quot;))
    .subscribe(onSuccess: {
        print($0)
    }, onFailure: { _ in
        print(&quot;error&quot;)
    }, onDisposed: {
        print(&quot;disposed&quot;)
    })
    .disposed(by: disposeBag)

print(&quot;-------Single2-------&quot;) // 실패시
Observable&amp;lt;Result&amp;lt;String, TraitsError&amp;gt;&amp;gt;.just(.failure(.single))
    .asSingle()
    .subscribe(onSuccess: {
        print(&quot;success: \($0)&quot;)
    }, onFailure: {
        print(&quot;error: \($0.localizedDescription)&quot;)
    }, onDisposed: {
        print(&quot;disposed&quot;)
    })
    .disposed(by: disposeBag)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Single 를 이용한 decode 예제&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1649733946263&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;------single------&quot;)
struct SomeJSON: Decodable {
    let name: String
}

enum JSONError: Error {
    case decodingError
}

let json1 = &quot;&quot;&quot;
    {&quot;name&quot;:&quot;hello&quot;}
&quot;&quot;&quot;


func decode(json: String) -&amp;gt; Single&amp;lt;SomeJSON&amp;gt; {
    Single&amp;lt;SomeJSON&amp;gt;.create { observer in
        let disposable = Disposables.create()
        
        guard let data = json.data(using: .utf8),
              let json = try? JSONDecoder().decode(SomeJSON.self, from: data) else {
            
            observer(.failure(JSONError.decodingError))
            return disposable
        }
        
        observer(.success(json))
        return disposable
    }
}

decode(json: json1)
    .subscribe{
        switch $0 {
        case .success(let json):
            print(json.name)
        case .failure(let error):
            print(error)
        }
    }
    .disposed(by: disposeBag)
    
------single------
hello&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Completable&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;comleted 이벤트 또는 error 이벤트를 방출될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649734506082&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;-------Completable-------&quot;)
Completable.create { completable in
    completable(.completed)
    return Disposables.create()
}
.subscribe(onCompleted: {
    print(&quot;completed&quot;)
}, onError: {
    print(&quot;error: \($0.localizedDescription)&quot;)
}, onDisposed: {
    print(&quot;disposed&quot;)
})
.disposed(by: disposeBag)


-------Completable-------
completed
disposed&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Maybe&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;success, completed , error 모두 방출될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649750671113&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;-------Maybe1-------&quot;)
Maybe&amp;lt;Result&amp;lt;String, TraitsError&amp;gt;&amp;gt;.just(.success(&quot;✅&quot;))
    .subscribe(onSuccess: {
        print(&quot;success: \($0)&quot;)
    }, onError: {
        print(&quot;error: \($0.localizedDescription)&quot;)
    }, onCompleted: {
        print(&quot;completed&quot;)
    }, onDisposed: {
        print(&quot;disposed&quot;)
    })
    .disposed(by: disposeBag)
 
 -------Maybe1-------
success: success(&quot;✅&quot;)
disposed&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fastcampus.co.kr/dev_online_iosapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://fastcampus.co.kr/dev_online_iosapp&lt;/a&gt;&lt;/p&gt;</description>
      <category>IOS/RxSwift</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/153</guid>
      <comments>https://jmkim.tistory.com/153#entry153comment</comments>
      <pubDate>Tue, 12 Apr 2022 12:38:38 +0900</pubDate>
    </item>
    <item>
      <title>[RxSwift] Observable  알아보기</title>
      <link>https://jmkim.tistory.com/152</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Observable&amp;lt;T&amp;gt;&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;T 형태의 데이터 snapshot 을 전달 할 수 있는 일련의 이벤트를 비동기적으로 생성하는 기능&amp;nbsp;&lt;/li&gt;
&lt;li&gt;하나 이상의 observers 가 실시간으로 어떤 이벤트에 반응&lt;/li&gt;
&lt;li&gt;세 가지 유형의 이벤트만 방출&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1649727013737&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Event&amp;lt;Element&amp;gt; { 
    case next(Element)  // next element 
    case error(Swift.Error)  // sequence failed with error 
    case completed  // sequence terminated successfully
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Observable 생명주기&amp;nbsp;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Observable 은 어떤 구성요소를 가지는 next 이벤트를 계속해서 방출할 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;error&amp;nbsp; 이벤트를 방출하여 완전 종료될 수 있다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;complete 이벤트를 방출하여 완전 종료 될 수 있다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;just, of, from&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1649729105177&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;-------just-------&quot;)
Observable.just(1)   // 하나의 요소만 가능
    .subscribe(onNext: {
        print($0)
    })
-------just-------
1


print(&quot;-------of1-------&quot;)
Observable.of(1, 2, 3)
    .subscribe(onNext: {
        print($0)
    })
-------of1-------
1
2
3

print(&quot;-------of2-------&quot;)
Observable.of([1, 2, 3])
    .subscribe(onNext: {
        print($0)
    })
-------of2-------
[1, 2, 3]


print(&quot;-------from-------&quot;) // 배열만 받는다.
Observable.from([1, 2, 3])
    .subscribe(onNext: {
        print($0)
    })
    .dispose()
-------from-------
1
2
3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;range&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1649729970240&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;------range------&quot;) // 범위
Observable.range(start: 1, count: 9)
    .subscribe(onNext: {
        print(&quot;2*\($0)=\(2*$0)&quot;)
    })
    
    
------range------
2*1=2
2*2=4
2*3=6
2*4=8
2*5=10
2*6=12
2*7=14
2*8=16
2*9=18&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;create&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1649730826321&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(&quot;------create1------&quot;)
Observable.create { observer -&amp;gt; Disposable in
    observer.onNext(1)
    observer.onCompleted()
    observer.onNext(2)
    return Disposables.create()
}
.subscribe(onNext: {
    print($0)
}, onError: {
    print($0)
}, onCompleted: {
    print(&quot;completed&quot;)
}, onDisposed: {
    print(&quot;disposed&quot;)
})
.disposed(by: disposeBag)
  
// onCompleteted 호출 전까지 구독이 된다.
 
------create1------
1
completed
disposed&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fastcampus.co.kr/dev_online_iosapp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://fastcampus.co.kr/dev_online_iosapp&lt;/a&gt;&lt;/p&gt;</description>
      <category>IOS/RxSwift</category>
      <author>내일도이렇게</author>
      <guid isPermaLink="true">https://jmkim.tistory.com/152</guid>
      <comments>https://jmkim.tistory.com/152#entry152comment</comments>
      <pubDate>Tue, 12 Apr 2022 11:58:14 +0900</pubDate>
    </item>
  </channel>
</rss>