[Swift] @escaping ν΄λ‘œμ €

parkgyurimΒ·2022λ…„ 2μ›” 14일
5

Swift

λͺ©λ‘ 보기
1/8
post-thumbnail

μ •μ˜

ν΄λ‘œμ €λž€?

  • μ½”λ“œ λΈ”λŸ­ ν˜•νƒœμ˜ ν•¨μˆ˜ 객체
  • ν”νžˆ λ‹€λ₯Έ μ–Έμ–΄μ—μ„œμ˜ λžŒλ‹€μ‹μ™€ λΉ„μŠ·ν•œ λͺ¨μŠ΅
  • μ •μ˜λœ μˆœκ°„μ˜ 상황 (λ³€μˆ˜ λ˜λŠ” μƒμˆ˜μ˜ κ°’ λ“±) 을 μΊ‘μ³ν•˜λŠ” μ»¨νƒμŠ€νŠΈ 캑쳐 νŠΉμ„±
  • ν•¨μˆ˜μ˜ νŒŒλΌλ―Έν„° λ˜λŠ” ν•¨μˆ˜μ˜ λ°˜ν™˜κ°’μœΌλ‘œ μ‚¬μš©
  • μŠ€μœ„ν”„νŠΈμ—μ„œλŠ” ν΄λ‘œμ €λ₯Ό μΆ•μ•½ν•˜κ±°λ‚˜ ν•¨μˆ˜μ˜ κ΄„ν˜Έ λ’€ μž‘μ„±ν•˜λŠ” ν›„ν–‰ ν΄λ‘œμ € (trailing closure) λ₯Ό μž‘μ„±ν•  수 μžˆμ–΄ κ°„κ²°ν•˜κ³  가독성 높은 μ½”λ“œλ₯Ό 지 수 μžˆμŠ΅λ‹ˆλ‹€!

escaping ν΄λ‘œμ €λž€?

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns.

  • ν΄λ‘œμ €κ°€ ν•¨μˆ˜μ— 인자둜 μ „λ‹¬λμ§€λ§Œ ν•¨μˆ˜κ°€ μ’…λ£Œλœ λ’€ μ‹€ν–‰λ˜λŠ” 것을 escape - νƒˆμΆœ ν•œλ‹€κ³  ν•œλ‹€.

πŸ“Œ 인자둜 μ „λ‹¬λœ ν΄λ‘œμ €κ°€ ν•¨μˆ˜μ˜ λ°–μ˜ λ³€μˆ˜μ— μ €μž₯λ˜κ±°λ‚˜ ν•¨μˆ˜κ°€ μ’…λ£Œλœ λ’€ μ‹€ν–‰λ˜λŠ” ν΄λ‘œμ €λ₯Ό escaping ν΄λ‘œμ € 라고 ν•©λ‹ˆλ‹€.


@escaping

non-escaping closure

ν•œκ°€μ§€ μ˜ˆμ‹œλ‘œ νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬λœ ν΄λ‘œμ €κ°€ ν•¨μˆ˜ λ°–μ˜ λ³€μˆ˜μ— μ €μž₯λ˜λŠ” κ²½μš°κ°€ μžˆμŠ΅λ‹ˆλ‹€.

class testClass {
	var property : (() -> Void)?
    
	func closureFunc(_ closure : () -> Void ) {
		self.property = closure // Error 
    }
}

μœ„μ˜ ν΄λ‘œμ €λŠ” non-escaping ν΄λ‘œμ € 라고 λΆ€λ₯΄κ³ , ν•¨μˆ˜ λ°–μ˜ λ³€μˆ˜μ— 인자둜 μ „λ‹¬λœ ν΄λ‘œμ € λŒ€μž…μ„ μ‹œλ„ν•˜λ©΄ 컴파일 νƒ€μž„ μ—λŸ¬κ°€ λ°œμƒν•©λ‹ˆλ‹€.

escaping closure

ν•¨μˆ˜ λ°–μ—μ„œ 인자둜 μ „λ‹¬λœ ν΄λ‘œμ €λ₯Ό μ‚¬μš©ν•˜κ³  μ‹Άλ‹€λ©΄ @escaping ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

class testClass {
	var property : (() -> Void)?
    
	func closureFunc(_ closure : @escaping () -> Void ) {
		self.property = closure 
    }
}

μ£Όμ˜μ‚¬ν•­πŸ”₯

@escaping ν‚€μ›Œλ“œκ°€ 뢙은 νŒŒλΌλ―Έν„°μ— non-escaping ν΄λ‘œμ €λ₯Ό 전달할 수 μžˆμ§€λ§Œ

@escaping ν‚€μ›Œλ“œκ°€ 뢙지 μ•Šμ€ νŒŒλΌλ―Έν„°μ— escaping ν΄λ‘œμ €λ₯Ό 전달할 μˆ˜λŠ” μ—†μŠ΅λ‹ˆλ‹€.

ν™œμš©

As an example, many functions that start an asynchronous operation take a closure argument as a completion handler. The function returns after it starts the operation, but the closure isn’t called until the operation is completed the closure needs to escape, to be called later.

  • escaping ν΄λ‘œμ €λŠ” completion handler, 즉 ν•¨μˆ˜μ˜ 결과에 따라 λ‹€λ₯΄κ²Œ λ™μž‘ν•˜λ„λ‘ 비동기적 처리λ₯Ό μš”κ΅¬ν•˜λŠ” ν•¨μˆ˜μ—μ„œ ν™œμš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ν”νžˆ λΉ„λ™κΈ°λ‘œ μ‹€ν–‰λ˜λŠ” Http request의 completion hanlder에 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ„œλ²„μ— μš”μ²­μ„ 보내고 응닡에 따라 λ‹€λ₯Έ 행동이 ν•„μš”ν•œ 경우 μ‚¬μš©ν•©λ‹ˆλ‹€.

  • μ œκ°€ μž‘μ„±ν•œ μ˜ˆμ‹œ μ½”λ“œμž…λ‹ˆλ‹€!
func refrshToken(completion : @escaping (Result<Bool, Error>) -> ()) {
        let url = baseURL + "/token"

        AF.request(url,
                   method: .get,
                   interceptor: authorizationInterceptor()
        ).responseJSON { response in
            guard let statusCode = response.response?.statusCode else { return }
            switch statusCode {
                case 200 :
                    print("Refresh token success (\(statusCode))")
                    completion(.success(true)) // βœ…
                default :
                    completion(.success(false)) // βœ…
            }
            print(response)
        }
        
        ...
        
}


////////

		...
        
        refrshToken { result in
                switch result {
                    case .success(true) : // βœ…
                        completion(.retryWithDelay(TimeInterval(1.0)))
                    case .success(false) : // βœ…
                        print("Token refresh error - Expired refresh token error")
                        userInfo = nil
                        loginType = nil
                    case let .failure(error) :
                        print("Retry error : " + error.localizedDescription)
                }
            }
       

κ°•ν•œ μˆœν™˜ μ°Έμ‘°

An escaping closure that refers to self needs special consideration if self refers to an instance of a class. Capturing self in an escaping closure makes it easy to accidentally create a strong reference cycle.

  • escaping ν΄λ‘œμ €κ°€ 클래슀 λ‚΄λΆ€μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό μ°Έμ‘°ν• λ•ŒλŠ” κ°•ν•œ μˆœν™˜ μ°Έμ‘°λ₯Ό μΌμœΌν‚¬ 수 있기 λ•Œλ¬Έμ— νŠΉλ³„ν•œ μ£Όμ˜κ°€ ν•„μš”ν•©λ‹ˆλ‹€!

ν΄λ‘œμ €μ˜ μ»¨νƒμŠ€νŠΈ 캑처 (Context caputre) λΌλŠ” νŠΉμ„±λ•Œλ¬Έμ— ν΄λ‘œμ €μ—μ„œ 클래슀 λ‚΄λΆ€μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό μ°Έμ‘°ν• λ•ŒλŠ” κ°•ν•œ μˆœν™˜ μ°Έμ‘° λ₯Ό 일으켜 λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό λ°œμƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

κ·Έλ ‡κΈ° λ•Œλ¬Έμ— ν΄λ‘œμ € λ‚΄λΆ€μ—μ„œλŠ” μˆœν™˜ μ°Έμ‘°λ₯Ό λ°©μ§€ν•˜κΈ°μœ„ν•΄ [weak self] ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

  • weak reference (μ•½ν•œ μ°Έμ‘°) λŠ” μ‹€μ œ 객체λ₯Ό μ†Œμœ ν•˜μ§€ μ•Šμ•„ 레퍼런슀 카운트λ₯Ό μ¦κ°€μ‹œν‚€μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • μ΄λ•Œ 참쑰쀑인 객체λ₯Ό strong ν•˜κ²Œ μœ μ§€ν•˜μ§€ μ•ŠκΈ°λ•Œλ¬Έμ— weak referenecκ°€ 객체에 λŒ€ν•œ μ°Έμ‘°λ₯Ό μœ μ§€ 쀑이여도 deallocate 될 수 μžˆμŠ΅λ‹ˆλ‹€. 즉, weak referenceλŠ” optional νƒ€μž…μœΌλ‘œ nil을 λŒ€μž…ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • λ°˜λŒ€λ‘œ μƒκ°ν•˜λ©΄ non-escaping ν΄λ‘œμ €μ—μ„œλŠ” ν•¨μˆ˜μ˜ scope λ‚΄μ—μ„œ λͺ¨λ“  μž‘μ—…μ΄ 이루어지고 μ’…λ£Œλ˜λ―€λ‘œ weak refernceλ₯Ό ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.
var userInfo : UserInfo? // βœ…
var loginType : LoginType? // βœ…

...
                        
	refrshToken { [weak self] result in
		switch result {
			case .success(true) :
				completion(.retryWithDelay(TimeInterval(1.0)))
			case .success(false) :
				print("Token refresh error - Expired refresh token error")
				self?.userInfo = nil // βœ…
				self?.loginType = nil // βœ…
			case let .failure(error) :
				print("Retry error : " + error.localizedDescription)
		}
	}
       

마무리

μ˜€λŠ˜μ€ μŠ€μœ„ν”„νŠΈλ₯Ό λ”μš± ν™œμš©μ μœΌλ‘œ λ§Œλ“€μ–΄μ£ΌλŠ” escaping ν΄λ‘œμ €μ— λŒ€ν•΄ μ•Œμ•„λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

ν‹€λ¦° 정보 λ˜λŠ” κΆκΈˆν•œ 점이 μžˆλ‹€λ©΄ λŒ“κΈ€ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€! μ½μ–΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€β€ΌοΈ

2개의 λŒ“κΈ€

comment-user-thumbnail
2023λ…„ 3μ›” 1일

κΉ”λ”ν•œ 정리 κ°μ‚¬ν•©λ‹ˆλ‹€:)

1개의 λ‹΅κΈ€