[Swift] Generics

임승섭·2023년 7월 12일
0

Swift

목록 보기
24/35

제네릭 (Generics)

제네릭 문법의 필요성

  • 제네릭이 없다면, 함수(class, struct, enum) 타입마다 모든 경우를 다 정의해야 한다
  • 인풋 타입만 다르고, 구현 내용이 완전히 동일할 때 사용
/*--------------Ex 1.--------------*/
// 정수 2개를 swap하는 함수의 정의
var num1 = 10
var num2 = 20

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
	let tmpA = a
    a = b
    b = tmpA
}

swapTwoInts(&num1, &num2)

// Double을 swap하려면 함수를 새로 정의해야 한다
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let tempA = a
    a = b
    b = tempA
}

// 마찬가지로 문자열을 swap하려면 또 새로 함수를 구현해야 한다 -> 불편함을 느낀다!



/*--------------Ex 2.--------------*/
// 배열의 원소들을 출력하는 함수
let numbers = [2, 3, 4, 5]
let scores = [3.0, 3.3, 2.4, 4.0, 3.5]
let people = ["Jobs", "Cook", "Musk"]

// 정수 배열의 출력
func printIntArray(array: [Int]) {
    for number in array {
        print(number)
    }
}

// Double 배열의 출력
func printDoubleArray(array: [Double]) {
    for number in array {
        print(number)
    }
}

// 마찬가지로 불편함이 느껴진다!! -> 제네릭으로 통합 가능

제네릭 함수의 정의

  • 타입 파라미터 <T>는 함수 내부에서 파라미터의 타입이나 리턴형으로 사용
  • 관습적으로 Type의 의미로 T를 사용하지만, 다른 문자도 사용 가능
  • 서로 다른 타입을 사용할 때는 <T, U> 이런 식으로 사용 가능
func swapTwoValues<T>(_ a: inout T, _ b: inout T) -> T {	// 리턴 타입으로 사용 가능
	let tmpA = a
    a = b
    b = tmpA
    
    return a	// 그냥 리턴 타입으로 쓸 수도 있는거 보여주려고
}

func printArray<T>(array: [T]) {
	for element in array {
    	print(element)
    }
}

스위프트 문법 내에서 제네릭의 사용 예시

// 배열
let arr: Array<String> = ["a", "b"]

// 딕셔너리
let dict: Dictionary<String, Int> = ["Alex": 25, "Michel": 18]

// 옵셔널
let opt: Optional<String>

// swap 함수
// swap(a: &T, b: &T)

제네릭 타입의 정의

  • class, struct, enum 타입 이름 뒤에 <T>를 추가하면, 제네릭 타입으로 선언된다

  • 속성의 자료형, 메서드의 파라미터형식, 리턴형을 타입 파라미터로 대체할 수 있다

/*-----------------struct-----------------*/
struct GenericMem<T> {
	var members: [T] = []
}

var member1 = GenericMember(members: ["Jobs", "Cook", "Musk"])
var member2 = GenericMember(members: [1, 2, 3])

// 주의할 점 : 이렇게 한 번 정의하면 타입이 고정이 된다
// (한 번 선언하면 메모리 구조가 결정이 된다!)
// member1 = GenericMember(members: [1, 2, 3])	// 	에러 발생



/*-----------------class-----------------*/
class GridPoint<A> {
	var x: A
    var y: A
    
    init(x: A, y: A) {
    	self.x = x
        self.y = y
    }
}

let aPoint = GridPoint(x: 10, y: 20)
let bPoint = GridPoint(x: 10.4, y: 20.5)



/*-----------------enum-----------------*/
// 열거형에서는 구체적인 연관값을 가질 때만 제네릭으로 정의할 수 있다
// 어차피 케이스는 선택 항목중에 하나일 뿐이기 때문에, 그걸 타입으로 정의할 일은 없다
enum Pet<T> {
	case dog
    case cat
    case etc(T)
}

let animal1 = Pet.etc("고슴도치")
let animal2 = Pet.etc(30)

제네릭 구조체의 확장

struct Coordinates<T> {
	var x: T
    var y: T
}

/*Ex 1*/
extension Coordinates {	// Coordinates<T> 라고 쓰지 않는다!
	// 튜플로 리턴하는 메서드
    func getPlace() -> (T, T) {
    	return (x, y)
    }
}
let place = Coordinates(x: 5, y: 5)
print(place.getPlace())


/*Ex 2*/
// where 절 추가 가능
// 타입이 Int형 일 때만 메서드가 적용!!
extension Coordinates where T == Int {
	func getIntArray() -> [T] {
    	return [x, y]
    }
}

let place2 = Coordinates(x: 3, y: 5)
place2.getIntArray()	// 정수형 일 때만 메서드가 나타난다!

타입 제약 (Type Constraint)

  • 타입 매개변수 이름 뒤에 콜론으로 "프로토콜" 제약 조건 또는 "단일 클래스" 배치 가능
/*----------------프로토콜 제약----------------*/
// <T: Equatable> 
// : Equatable이라는 프로토콜을 채택한 타입만 타입으로서 이 함수에서 사용이 가능하다!
// (Equatable 프로토콜 : == 이라는 메서드를 반드시 구현해야 하는 프로토콜 - 나중에 배운다)
func findIndex<T: Equatable>(item: T, array: [T]) -> Int? {
	for (index, value) in array.enumerated() {
    	if item == value {
        	return index
        }
    }
  	return nil
}



/*----------------클래스 제약----------------*/
class Person {}
class Student: Person {}

let person = Person()
let student = Student()

// Person의 상속 구조 상에 있는 타입만 사용 가능하다!
// 해당 타입을 상속한 클래스는 가능
func personClassOnly<T: Person>(array: [T]) {	
}

personClassOnly(array: [person, person])
personClassOnly(array: [student, student])
personClassOnly(array: [Person(), Student()])	// 바로 생성해서 넣어주기

프로토콜에서 제네릭 문법의 사용

  • 프로토콜 : 자격증 - 채택하는 타입에서 반드시 해야 할 일을 정의
    • 요구사항을 선언만 해주고, 그 내용을 정의하진 않았다.
  • 프로토콜에서 제네릭을 사용한다 : 어떤 메서드의 타입을 미리 정의하지 않는다
    • 채택하는 타입에서 그 타입을 정의하고 사용한다 -> 유연선 good
    • 한 가지 약속 : <T> (x) associatedtype T (o)
protocol RemoteControl {
	associatedtype T
    func changeChannel(to: T)
    func alert() -> T?
}

struct TV: RemoteControl {

	// 명시적으로 어떤 타입을 사용할 지 써주기
	typealiase T = Int		// 생략 가능
    
    func changeChannel(to: Int) {
    	print("TV 채널바꿈: \(to)")
    }
    
    func alert() -> Int? {
    	return 1
    }
}

class Aircon: RemoteControl {
	func changeChannel(to: String) {
    	print("Aircon 온도바꿈: \(to)")
    }
    
    func alert() -> String? {
    	return "1"
    }
}

연관 형식에 제약 추가

protocol RemoteControl2 {
	// <T: Equatable> 제약 추가
	associatedtype Element: Equatable	
    func changeChannel(to: Element)
    func alert() -> Element?
}

0개의 댓글