이건 개인적인 취향인데, 저는 테스트 코드에서 캐싱을 위한 프로퍼티는 옵셔널 바인딩을 하지 않고 강제 언래핑을 해요. 굳이 필요하지 않은 코드 같거든요. 이미 setUpWithError메소드에서 매번 프로퍼티에 값을 할당해주고 있는 상황에서 sut_marketItemList가 nil이라면 테스트 자체가 성립이 안되니까요.
실제 프로덕트에서 옵셔널 바인딩을 하는 경우라면 테스트를 해야겠지만, 지금은 프로퍼티로 캐싱을 하는데, 초기화를 할 수 없으니 옵셔널 프로퍼티를 선언한 것이라고 생각했거든요.
setUpWithError가 이니셜라이즈 역할 하는데
각각의 프로퍼티가 어떤 값을 가질지 값을 넣어줌
매번 값을 할당해주는 상황에서
nil이라면 테스트 자체가 성립이 안됨
= nil일 가능성이 없으니까
옵셔널 바인딩 안해줘도 됨
private enum CodingKeys: String, CodingKey {
case id, title, price, currency, stock, descriptions, thumbnails, images
case discountedPrice = "discounted_price"
case registrationDate = "registration_date"
}
이건 그냥 참고만 하셔도 되는데, 지금 CodingKeys를 써서 snake case를 camel case로 바꾸고 있네요. 이런 경우는 JsonDecoder의 keyDecodingStrategy 값을 convertFromSnakeCase로 줘도 됩니다.
https://developer.apple.com/documentation/foundation/jsondecoder/2949119-keydecodingstrategy?changes=latest_minor
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
참고 자료
Swift ) Key decoding strategy by ZeddiOS
클로저를 함수의 파라미터로 넣을 수 있는데, 함수 밖(함수가 끝나고)에서 실행되는 클로저 예를 들어, completionHandler로 사용(= 비동기로 실행)되는 클로저는 파라미터 타입 앞에 @escaping이라는 키워드를 명시해야 합니다.
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
위 함수에서 인자로 전달된 completionHandler
는 someFunctionWithEscapingClosure
함수가 끝나고 나중에 처리 됩니다. 만약 함수가 끝나고 실행되는 클로저에 @escaping
키워드를 붙이지 않으면 컴파일시 오류가 발생합니다.
@escaping
를 사용하는 클로저에서는 self
를 명시적으로 언급해야 합니다.
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure() // 함수 안에서 끝나는 클로저
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다.
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100
클로저 (Closures) - The Swift Language Guide (한국어)
클로저를 함수나 메소드의 인자값으로 사용할 때에는 용도에 따라 @escaping과 @autoescape 속성을 부여할 수 있습니다.
@escaping 속성은 인자값으로 전달된 클로저를 저장해 두었다가, 나중에 다른 곳에서도 실행할 수 있도록 허용해주는 속성입니다.
func callback(fn: () -> Void) {
fn()
}
callback {
print("Closure가 실행되었습니다.")
}
정의된 함수 callback(fn:)은 매개변수를 통해 전달된 클로저를 함수 내부에서 실행하는 역할
func callback(fn: () -> Void) {
let f = fn
f()
}
에러가 안나는데 ???
꼼꼼한 재은씨 책에서는 Non-escaping parameter 'fn' may only be called라고 에러가 난다는데...🤔
스위프트에서 함수의 인자값으로 전달된 클로저는 기본적으로 탈출불가(non-escape)의 성격을 가짐
모르겠음....
이해가 안됨 🤔
func callback(fn: () -> Void) {
func innerCallback() {
fn()
}
}
이것도 오류가 나야 한다는데 안남
func callback(fn: @escaping () -> Void) {
let f = fn // 클로저를 상수 f에 대입
f() // 대입된 클로저를 실행
}
callback {
print("Closure가 실행되었습니다.")
}
이제 입력된 클로저는 변수나 상수에 정상적으로 할당될 뿐만 아니라, 중첩된 내부 함수에 사용할 수 있으며, 함수 바깥으로 전달할 수도 있음. 말 그대로 탈출 가능한 클로저가 된 것.
출처
꼼꼼한 재은씨의 Swift 문법편
import Foundation
class Myclass {
var a = 10
func callFunc() {
withEscaping { self.a = 100 }
withoutEscaping { a = 200 }
}
var completionHandlers: [() -> Void] = []
func withEscaping(completion: @escaping () -> Void) {
completionHandler.append(completion)
}
func withoutEscaping(completion: () -> Void) {
completion()
}
}
let mc = MyClass()
mc.callFunc()
print(mc.a) // 200
mc.completionHandlers.first?()
print(mc.a) // 100
import Cocoa
class Myclass {
var a = 10
var completionHandlers: [() -> Void] = []
func callFunc() {
withEscaping { self.a = 100 }
withoutEscaping { a = 200 }
}
func withEscaping(completion: @escaping () -> Void) {
completionHandlers.append(completion)
}
func withoutEscaping(completion: () -> Void) {
completion()
}
}
let mc = Myclass()
mc.callFunc()
print(mc.a) // 200
mc.completionHandlers.first?()
print(mc.a) // 100
이미지를 배열에다가 넣어줘야지
셀 하나하나에 넣어줄 수 있음
파싱한 작업을 메소드 안에서 실행한 다음에
결과물을 외부의 배열에 넣어줘야 함
UIImage타입의 배열에
그냥 closure는 할 수가 없음
메소드 안에서만 존재해야 하니까
(escaping은 갖고 나갈 수 있음)
import Cocoa
var completionHandlers: [() -> Void] = []
// () -> Void 아무것도 반환하지 않는 클로저
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure() // 함수 안에서 끝나는 클로저
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithNonescapingClosure { x = 200 }
someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다. (값 캡쳐랑도 연관있음)
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.last?()
// ?는 옵셔널이라서. 값이 있을수도 있고 없고라서 붙임
// ()배열의 마지막 요소를 뜻하는거
// @inlinable public var last: Element? { get } 연산 프로퍼티 찾아보기
print(instance.x)
// Prints "100"
여기서의 핵심은 completionHandlers가 클로저로 이루어진 빈 배열이라는 것!! ⭐️
클로저는 모다? 코드 블럭이다!
그래서 completionHandlers를 프린트문으로 찍어보면 (Function)이라는 이름 없는 코드 블록으로 채워진 배열이 찍히는 걸 알 수 있음 !! ⭐️
참고 자료
Swift Escaping Closure 이해하기
[SWIFT] Escaping Closure(탈출 클로저)
우아한 형제들 iOS Networking 예시코드
class JokesAPIProvider {
let session: URLSession
init(session: URLSession = .shared) {
self.session = session
}
func fetchRandomJoke(completion: @escaping (Result<Joke, Error>) -> Void) {
let request = URLRequest(url: JokesAPI.randomJokes.url)
let task: URLSessionDataTask = session
.dataTask(with: request) { data, urlResponse, error in
guard let response = urlResponse as? HTTPURLResponse,
(200...399).contains(response.statusCode) else {
completion(.failure(error ?? APIError.unknownError))
return
}
if let data = data,
let jokeResponse = try? JSONDecoder().decode(JokeReponse.self, from: data) {
completion(.success(jokeResponse.value))
return
}
completion(.failure(APIError.unknownError))
}
task.resume()
}
}
네트워크 요청 작업이 있고
비동기적으로 이를 처리하고 이 처리가 끝난 후 동작하는 것을 Completion Handler에 명령하는 것.
Q. result가 뭘 가르치는지?
여기서 Result는 completionHandler의 인자값으로 클로저가 들어간 것...?? 🤔
if let data = data,
let jokeResponse = try? JSONDecoder().decode(JokeReponse.self, from: data) {
completion(.success(jokeResponse.value))
return
}
json 데이타를 성공적으로 decode해오면
completion을 실행하라는 의미?
data는 atomic하다
(atomic = 더 이상 쪼갤 수 없음)
⇒ 모든 데이터를 갖고 있어야 한다
Class
NSData provides methods for atomically saving their contents to a file, which guarantee that the data is either saved in its entirety, or it fails completely. An atomic write first writes the data to a temporary file and then, only if this write succeeds, moves the temporary file to its final location.
데이터를 작성해야 read할 수 있는데
write할 때 데이터는 임시파일에 해당 내용 작성함
이 작업이 끝까지 성공해야지만
해당 임시 파일을 finalDestination = 저장 경로로 이동해서
사용할 수 있음
jsonParsing을 하다가
만약에 실패를 하면
파싱된 데이터를 못 쓰는 것
근데 json을 파싱하다가
파싱하면서 바로바로 그거를 앱 UI에 적용하려고하면
안됨
why? 데이터를 다 받지를 않았으니까
Thanks to 제임스 👍
참고 자료
atomic/non atomic