안녕하세요 후르륵짭짭입니다.
이번에도 프로토콜 관련된 내용입니다....
최근에 Swift를 공부 할 시간 말고 다른 쪽으로 공부해야해서,,, ㅠㅠ
이렇게 간간히 수업 때 배운 걸 적어야 할 듯 해요 ㅋㅋㅋ
2주만에 글을 적고 싶진 않았는데 ㅠㅠ 좀 더 노력해볼게요
www.youtube.com/watch?v=_AWDWdtaqKY&list=RD_AWDWdtaqKY&start_radio=1
<이 노래를 들으면 나름 위안이 되는 느낌 ㅎㅎㅎㅎ 노래방에서 부르기에도 딱 좋습니당! >
** 런타임 프로토콜 과 컴파일 프로토콜 **
(주의!!! 자세한 건 아니고 그냥 제가 알게 된거 적는 겁니다 ㅎㅎㅎㅎ)
런타임 프로토콜은 의역하면,,,, 프로토콜을 프로그램이 실행 할 때 준수하는 것을 의미합니다.
예를들어 다음 처럼 코드가 있다고 한다면
protocol Country{ var name : String{get} var travel : Bool{get set} //compute Property var time : Int{get} } struct Japan : Country{ var name: String var travel: Bool{ get{ return self.time < 5 } set{ self.time = newValue == true ? 2 : 5 } } var time: Int init(name : String, time : Int){ self.name = name self.time = time } } struct USA : Country{ var name: String var travel: Bool{ get{ return self.time < 8 } set{ self.time = newValue == true ? 7 : 10 } } var time: Int init(name : String, time : Int){ self.name = name self.time = time } }
미국과 일본이라는 인스턴스는 Country라는 프로토콜을 준수하고 있습니다.
(참고로 compute Property는 자기 자신의 값을 반환하는 것이 아니라 다른 프로퍼터의 값을 변경해주거나 다른 프로퍼티의 값을 조리 해서 값을 반환하는 것을 의미합니다.)
let countries : [Country] = [ Japan(name: "니혼", time: 2), USA(name: "천조국", time: 10) ]
이렇게 배열 [Country 프로토콜]을 생성해줍니다.
아주 잘 들어가지고 아래 처럼 World 클래스를 생성해서 출력 해주도록 하겠습니다.
class World{ var countries : [Country] init(countries : [Country]){ self.countries = countries } lazy var printInfos : (String) -> () = { input in print(input) for item in self.countries{ print("\(item.name) / \(item.travel)") } } } let world = World(countries: countries) world.printInfos("Let's go") //결과 Let's go 니혼 / true 천조국 / false
world.prntInfos() 를 해주면 잘 출력이 됩니다.
여기서 런타임 프로토콜이란, 특정지어진 타입이 아니라 프로토콜을 모두 포함하는 것을 런타임 프로토콜이라 합니다.
예를들어, 위에 처럼 배열 [Country 프로토콜] 같은 것을 의미합니다. (정확한건 아닙니다 ㅎㅎㅎ)
(참고 사항) - 항상 ㅂㄷㅂㄷ 했던 Closure에 대해서 새로 알게 된거
보통 클로저를 작성하면 아래 처럼 작성하게 됩니다.
lazy var printInfos : (String) -> () = { input in print(input) for item in self.countries{ print("\(item.name) / \(item.travel)") } }
이렇게 아니면 아래 처럼 작성하져,,,
lazy var printInfos2 = { (input : String) -> Void in print(input) for item in self.countries{ print("\(item.name) / \(item.travel)") } }
그런데 가끔은 이상한게 있어요,,,
View를 코드로 만들 때,,, 아래 처럼 만듭니다.
let view : UIView = { let view = UIView() return view }()
흠,,, 이건 Closure인가?? 아닌가?? 생각이 들 때가 많은데,, 일단 클로저 입니다.
let view : UIView = { () -> UIView in let view = UIView() return view }()
결국 이거랑 같은 겁니다.
근데 아직 뒤에 () 것이 있어서 판단이 애매합니다.
let view : UIView = { (매개변수 - 받은 값) -> UIView in let view = UIView() return view }(인자 - 입력값)
이렇게 인자를 클로저 밖에서 주는 겁니다.
결국 우리가 평소에 보는 클로저는 아래와 같지만 인자를 바로 줄 때 사용합니다.
즉, 아래 방식이 정의를 내리고 후에 사용하는 거라면 위에 방식은 정의를 내린 동시에 사용 하는 겁니다.
let view : () -> UIView = { () -> UIView in let view = UIView() return view }
그럼 저런 방법은 언제 쓰냐??? 하면,,,,
나중에 천천히 사용 할때는 당연히 정확한 인자랑 반환 값을 적어줘야하겠죠? 나중에 기억을 하기 위해서라도
그런데 바로 사용 할 때는, 어차피 인자를 바로 줄 것이고 땜에 반환 값만 적어 주면 되져?
let view : UIView = {}() -> 바로 사용 할 때 let view : () -> UIView = {} -> 나중에 사용 할 때
이렇게 되는 겁니다.
let result_1 : (Int) -> (Int) = { input in return input + 10 } let result_2 : Int = { (input : Int) in return input + 10 }(10) print(result_1(20)) // 30 print(result_2) // 20
만약에 아래처럼 함수를 호출 한다면
result_2(20) //Cannot call value of non-function type 'Int' //비함수 유형의 값을 'Int'로 호출할 수 없습니다.
와 같은 오류가 나옵니다!
그러니 result_2 같은 방식은 정의 후 바로 사용 할 때 사용 하는 겁니다!
하지만 만약 아래 처럼 타입을 특정 지어주지 않고 사용한다면
let countries2 = [ Japan(name: "니혼", time: 2), USA(name: "천조국", time: 10) ] //Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
이렇게 명확한 타입을 추가해주거나 [Any] 타입으로 해줄래요??
이런 식으로 에러가 발생한다.
이런걸 컴파일 프로토콜이라 한다.
그래서 이렇게 동일한 타입으로 바꿔줘야합니다!
let countries2 = [ Japan(name: "니혼", time: 2), Japan(name: "일본", time: 2) // USA(name: "천조국", time: 10) ]
** 런타임 프로토콜 VS 컴파일 프로토콜 **
지금 까지 런타입 프로토콜에 대해 알아봤는데, 추가적으로 더 알아보면
func processCountry(country : Country , complete : ((Country) -> Void)){ print("\(country.name) / \(country.travel)") complete(country) } extension Japan{ func printHello(){ print("곤니찌와") } }
이렇게 Country 프로토콜을 매개변수로 받는 processCountry를 생성하고
Japan 인스턴스에 함수를 추가 했을 때,
processCountry(country: Japan(name: "니혼", time: 2), complete: { country in if let japan = country as? Japan{ japan.printHello() } })
런타임 프로토콜은 다음 처럼 다운 캐스팅을 해서 구체적인 기능을 사용 할 수 있습니다.
반면 컴파일 프로토콜은 이미 타입이 정해져버리기 때문에
func processCountry_Gen<C : Country>(country : C , complete : ((C) -> Void)){ print("\(country.name) / \(country.travel)") complete(country) } extension USA{ func printHello(){ print("핼로우") } }
위에 처럼 해주며 C 에는 Country 프로토콜을 준수하는 특정한 타입이 들어가게 되고
complete에 C는 특정한 타입이 나가게 됩니다.
processCountry_Gen(country: USA(name: "미쿡", time: 10), complete: { C in C.printHello() }) processCountry_Gen(country: Japan(name: "니혼", time: 2), complete: { C in C.printHello() })
따라서 런타임 프로토콜과 달리 다운캐스팅을 할 필요 없이
바로 기능을 사용 할 수 있습니다.
정리를 한다면
런타임 프로토콜은 프로토콜을 만족하는 다양한 타입을 한번에 관리하는 것이 가능하다.
컴파일 타임 프로토콜은 구체적인 타입에 대한 기능을 별도의 캐스팅 없이 사용 가능하다.
** 20.04.25 - 잡 생각 **
헤롱헤롱~~~
감사합니다.
'Xcode > Swift - PlayGround' 카테고리의 다른 글
PlayGround) RxMvvm에서 Input과 Output에서 의문점 (0) | 2021.09.03 |
---|---|
(Swift) Closure - Capturing Value (0) | 2021.09.03 |
PlayGround) Generic - Closure의 확장 (1) | 2021.04.11 |
PlayGround) 제너릭에 대해 알아보자 (0) | 2021.03.27 |
PlayGround) Protocol에 대해서 좀 더 깊게 알아가기 (0) | 2021.02.14 |
댓글