안녕하세요! 후르륵짭짭 입니다.
이번에는 Closure에 대한 저의 착오를 말씀 드릴려고 합니다.
전 Closure를 값을 받아 수행하는 함수라고 생각했습니다.
그러니깐 함수 내부에서 클로저를 수행한다고 하면, 아래의 순서대로 작동하는 줄 알았습니다.
func mistake(closure : () -> ()){
//(1)
closure()
}
mistake {
//(2)
print("Hello world")
}
그래서 closure()를 수행하면 trailing Closure 부분인 print("Hello world")를 수행하는 줄 알았습니다.
하지만 완전 반대 였습니다.
func mistake(closure : () -> ()){
//(2)
closure()
}
mistake {
//(1)
print("Hello world")
}
이렇게 수행 되는 거 였습니다.
즉, closure를 정의하고 closure함수가 수행 된다면 정의된 closure()를 수행하는 거 였습니다.
이건 제가 완전히 잘 못 closure를 생각한 부분이였습니다.
(지금까지 이렇게 생각한 스스로를 반성합니다 ㅠㅠ )
** 어떻게 알게 된거냐? **
이것을 제가 어떻게 알게 됐냐,,,
지금 RxSwift를 공부하고 있는데,, 예시로 준 코드가 이해가 안 됐습니다.
class 나중에생기는데이터<T>{
private let task : (@escaping (T) -> Void) -> Void
init(task : @escaping (@escaping (T) -> Void) -> Void) {
self.task = task
}
func 나중에오면(_ later : @escaping (T) -> Void) {
task(later)
}
}
func downloadJson(_ url : String) -> 나중에생기는데이터<String?>{
return 나중에생기는데이터 { (task) in
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
task(json)
}
}
}
}
let json : 나중에생기는데이터<String?> = downloadJson(MEMBER_LIST_URL)
json.나중에오면 { (json) in
self.editView.text = json!
self.setVisibleWithAnimation(self.activityIndicator, false)
}
이게 머냐,,,,,,, 와,,, 클로저 어렵다,,,,,
바로 이런 생각이 들었습니다.... 허허허허허허허허허허
먼소리지,,,,,
(그래서 바로 분석 들어가 버렸습니다.)
여기서 중요한 것은 바로 Closure가 어떤 방식으로 작동하냐가 중요합니다.
그리고 Closure의 가장 핵심역할이 무엇인가,,,,
클로저의 표현을 본다면 Nested function은 이름의 편리한 의미이고 함수의 부분을 code 블록으로 정의한 것이라고 되어 있습니다.
즉!!! Closure는 정의 함수 입니다!!!
따라서 아래 처럼 작동하는게 맞는 거였습니다.
func mistake(closure : () -> ()){
//(2)
closure()
}
mistake {
//(1)
print("Hello world")
}
그러면 저런 코드가 쉽게 이해가 됩니다.
하기 전에 더 좋은 예시를 보도록 하겠습니다.
class test3 {
typealias inputType = (String) -> Void
var task : (inputType) -> Void
init(){
self.task = { data in
print("test start")
data("ChapChap")
print("test finished")
}
print("Init finished")
}
func returnResult(input : inputType){
print("Okey start")
task(input)
}
func returnResult(){
print("Okey start")
let input : inputType = { data in
print("\(data), Hello world")
}
input("Chap")
// task(input)
}
}
func main3(){
let result = test3()
result.returnResult(input: { data in
print("\(data), Hello world")
})
//result.returnResult()
}
사실상 위에 코드랑 일치합니다.
** 하나씩 알아보도록 하자 **
그럼 하나씩 보도록 하겠습니다.
typealias inputType = (String) -> Void
var task : (inputType) -> Void
처음에 이렇게 클로저 타입으로 두개가 있습니다.
inputType은 string을 받아서 Void를 반환하고
task는 inputType을 받아서 Void를 반환합니다.
private let task : (@escaping (T) -> Void) -> Void
결국 @escaping (T) -> Void 는 inputType 이고 task가 task가 되는 겁니다.
그러면 다음 코드를 보도록 하겠습니다.
init(task : @escaping (@escaping (T) -> Void) -> Void) {
self.task = task
}
이렇게 되어 있습니다...
처음 이것을 봤을 때,,, task를 어떻게 정의한 거고,,,, 이건 머지??? 이런 생각을 들었습니다.
나중에생기는데이터 { (task) in
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
task(json)
}
}
}
그리고 이렇게 되어 있었습니다,,,,,
일단 대충,,, self.task가 task를 받았다고 생각하고 다음 코드를 봤습니다.
func 나중에오면(_ later : @escaping (T) -> Void) {
task(later)
}
json.나중에오면 { (json) in
self.editView.text = json!
self.setVisibleWithAnimation(self.activityIndicator, false)
}
흠,,,,, 이해가 되기 어려웠습니다. ㅎㅎㅎㅎ
task가 later 함수를 받았다고,,,,,
모르는게 투성이지만,, 그냥 포기하면 안될 거 같아서
혹시 몰라 일반적인 클로저로 만들어 봤습니다.
우에 나오는 나중에오면 함수를
//나중에오면
func returnResult(input : inputType){
print("Okey start")
let input : inputType = { data in
print("\(data), Hello world")
}
task(input)
}
이렇게 바꾸고
init함수를 아래 처럼 변경 해봤습니다.
init(){
self.task = { data in
print("test start")
data("ChapChap")
print("test finished")
}
print("Init finished")
}
그러니깐, 동일한 방식으로 작동 했습니다....
그래서 Closure를 다시 새롭게 정의 할 수 있었습니다.
Closure는 함수를 정의하는 것이 라고요
그럼 결국에 코드가 아래처럼 되는 겁니다.
class test{
typealias inputType = (String)->(Void)
var task : (inputType) -> Void
init(task : @escaping (inputType) -> Void ){
self.task = task
print("Init finished")
}
func returnResult(input : inputType){
print("Call task function")
task(input)
}
}
func main(){
let result = test(task:
{ (task) in
print("test start")
task("Hururuek")
print("test finished")
}
)
result.returnResult { (data) in
print(data)
}
}
즉, trailing 부분에서 클로저에 대한 정의를 내려주게 됩니다.
init(task : @escaping (inputType) -> Void ){
//(2)
self.task = task
print("Init finished")
}
let result = test(task:
//(1)
{ (task) in
print("test start")
task("Hururuek")
print("test finished")
}
)
이런 순서로 작동하게 됩니다.
따라서 task에는 우리가 test()에서 정의한 내용들이 들어가게 됩니다.
하지만 여기서 inputType도 클로저라는것을 잊으면 안됩니다.
따라서 test에서 정의한 task 또한 클로저이기 때문에 이부분에 대한 정의도 이뤄져야합니다.
func returnResult(input : inputType){
print("Call task function")
task(input)
}
result.returnResult { (data) in
print(data)
}
여기서 보면 resturnResult에 input이 있는데 그 input을 task에 넣어줍니다.
왜냐하면 task( inputType )이 들어가야하거든요.
그러면 우리는 지금 까지 알아본 것 처럼 input을 Trailing Closure에서 정의하고
그것을 task에 보내준겁니다.
그럼 결국 위의 내용을 요약하면 아래 처럼 될 수 있을 것 같습니다.
task = { task : (String)->(Void) in
print("test start")
data = { data : (String) -> Void in
print(data)
}
data("ChapChap")
print("test finished")
}
쫌 내용일 이해가 가셨으면 좋겠습니다 ㅎㅎㅎ.
Closure가 작동하는 순서만 이해가 됐다면, 어떤 코드를 봐도 이해가 될 겁니다!
소스 코드 :
github.com/HururuekChapChap/Swift_Study/tree/master/PlayGround/Closure%2BEx.playground
'Xcode > Swift - PlayGround' 카테고리의 다른 글
PlayGround) Codable ANY 타입 처리 방법 (0) | 2020.12.19 |
---|---|
PlayGround) DateFormatter를 이용해서 날짜를 변경하자 (2) | 2020.12.18 |
PlayGround) Notification을 이용해서 데이터를 전송하자! (0) | 2020.11.16 |
PlayGround) DispatchGroup이란??? (0) | 2020.11.08 |
PlayGround) IOS의 Unit Test에 대해 알아보자 (0) | 2020.11.05 |
댓글