Generic
- 타입에 유연하게 대응하기 위한 요소이다.
- Type Parameter: 플레이스 홀더 같은 역할, 어떤 타입인지 타입의 종류는 알려주지 않음, 특정한 타입 하나라는 건 알 수 있음
- 한 가지 종류가 들어가는데 어떤 타입이 들어갈 지는 모른다.
- 제네릭으로 이루어진 함수 사용 시 T에 들어갈 타입은 모두 같아야 한다.
- UpperCased ex. <T> <K> <U>
- Type Constraints : 클래스/프로토콜 제약을 걸어줘야 한다.
struct DummyData<T> {
var name: T
}
let data = DummyData(name: "abc")
let intData = DummyData(name: 3)
let boolData = DummyData(name: true)
관련 오류
Generic을 나타내는 플레이스 홀더(Type Parameter)인 T를 써주고 왜 안쓰고 있니?
+ 연산을 해줄 수 없다
Type Constraints와 관련된 부분이다.
reduce 고차함수를 통해서 배열 내의 값을 더하는 연산을 해주기 위해서는 제네릭타입이
Numeric이라는 프로토콜을 따른다고 명시해야 한다.
이렇게 Numeric을 따른다고 제약설정을 해줄 시에,
String과 같은 Numeric을 채택하고 있지 않는 타입은 제네릭 함수를 사용할 수 없다.
또한, 제네릭 대신에 타입 캐스팅을 써주면 안되나? 라고 생각할 수 있다.
근데 Any 타입을 써주면 결국 연산을 위한 타입 캐스팅을 써줘야 하는데
Generic을 쓰면 들어올 수 있는 타입에 대한 제약을 미리 걸어둘 수 있기 때문에 더 효율적이라고 볼 수 있다.
제네릭을 사용한 화면 전환 코드
class SampleViewController: UIViewController {
func transitionViewController<T: UIViewController>(sb: String, id: String, vc: T) {
let sb = UIStoryboard(name: sb, bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: id) as! T
self.present(vc, animated: true)
}
}
class SampleViewController: UIViewController {
func transitionViewController<T: UIViewController>(sb: String, vc: T) {
let sb = UIStoryboard(name: sb, bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: String(describing: vc)) as! T
self.present(vc, animated: true)
}
}
2개의 제네릭을 사용한 경우
원래는 제네릭 함수 사용 시에는 T는 서로 같아야 하는데 같은 구조체에서 프로퍼티 별로 타입을 다르게 주고 싶을 경우에,
아래와 같이 두 개의 제네릭을 사용하면 된다. 그런데 보통 1~2개 정도의 제네릭을 사용한다.
예를 투두리스트를 위한 모델을 만들 경우에, 타입이 다른 경우 2개의 제네릭을 사용하면 된다.
struct DummyData<T, U> {
var mainContents: T
var subContents: U
}
let cast = DummyData(mainContents: "헤어몬", subContents: "주인공역")
let phoneNumber = DummyData(mainContents: "고래", subContents: 01042132366)
let todo = DummyData(mainContents: "밥먹기", subContents: false)
Swap Example, inout parameter
Swift 공식 문서에서 Generics 페이지의 첫 번째 단락에 나오는 swap은 변수에 대해 위치를 바꿔주는 기능이다.
(아마 혼자 봤다면 무슨 말인지 이해 못해서 헬렐레 거렸을 것이 분명함;)
여하튼,
이게 뭔지에 대해 알아보기 위해 과정을 살펴보아따.
// 변수에 대한 위치를 바꿔주는 기능
var fruit1 = "사과"
var fruit2 = "바나나"
swap(&fruit1, &fruit2) // & => inout parameter
print(fruit1, fruit2)
swap이란 메소드의 & -> 이건 inout parameter라는 거다.
변수에 대한 위치를 바꿔줄 때 붙여서 쓰는 것임
이게 어떻게 구현되는지 메소드를 만들어서 살펴보면
// 원래 a는 상수인데 바꾸려면 inout 형태로 불러줘야 한다.
func swapToInts(a: inout Int, b: inout Int) {
let tempA = a
a = b // a에는 b를 넣어서 보여주고
b = tempA // b에는 tempA값을 넣어서 보여주는데 tempA는 a값임
}
//swapToInts(a: &<#T##Int#>, b: &<#T##Int#>)
원래는 tempA는 상수로 선언해줬기 때문에 값변경해줄 수 없는데 바꾸기 위해서는 inout 형태로 불러줘야 한다.
이 경우에 swapToInts라는 메소드를 부르면 & 가 붙는 걸 볼 수 있음
func swapToSomething<T>(a: inout T, b: inout T) {
let tempA = a
a = b // a에는 b를 넣어서 보여주고
b = tempA // b에는 tempA값을 넣어서 보여주는데 tempA는 a값임
}
swapToSomething(a: &fruit1, b: &fruit2)
print(fruit1, fruit2)
그럼 타입에 제네릭을 넣어서 할 수 있겠지?
Equatable
왼쪽 요소와 오른쪽 요소를 비교해서 같은지 비교해서 보여준다는 프로토콜
Int, String 등 값을 비교할 수 있는 타입들은 다 Equatable을 채택하고 있어서 비교하는 연산이 가능한 것이다.
커스텀 클래스에서 등위 연산자를 사용하고 싶다면, Equatable을 채택해서 메소드를 구현해줘야 한다.
제네릭과 함께 사용하면 아래처럼 작성할 수 있다.
'⭐️ 개발 > iOS & Swift' 카테고리의 다른 글
[iOS] Realm 기본 설계 + Realm에 저장하고 가져오기 (0) | 2022.08.23 |
---|---|
[iOS] loadView() (0) | 2022.08.21 |
[iOS] UIView의 초기화 구문 다시 알자! (0) | 2022.08.19 |
[iOS] CodeBase로 가보자고 (0) | 2022.08.17 |
[iOS/Swift] Custom Framework를 만들면서 배우는 접근제한자 (0) | 2022.08.16 |