Swift Language
제네릭
수색…
비고
일반 코드를 사용하면 사용자가 정의한 요구 사항에 따라 모든 유형에서 작동 할 수있는 유연하고 재사용 가능한 함수 및 유형을 작성할 수 있습니다. 중복을 피하고 그 의도를 명확하고 추상적으로 표현하는 코드를 작성할 수 있습니다.
Generics는 Swift의 가장 강력한 기능 중 하나이며 Swift 표준 라이브러리의 대부분은 일반적인 코드로 작성됩니다. 예를 들어 Swift의
Array
및Dictionary
유형은 모두 일반적인 컬렉션입니다.Int
값을 포함하는 배열 또는String
값을 보유하는 배열을 만들거나 Swift에서 만들 수있는 다른 유형의 배열을 만들 수 있습니다. 마찬가지로 지정된 유형의 값을 저장하기 위해 사전을 만들 수 있으며 그 유형에 대한 제한이 없습니다.출처 : Apple의 신속한 프로그래밍 언어
일반 자리 표시 자 유형 제한
generic 클래스의 형식 매개 변수가 프로토콜 을 구현하도록 할 수 있습니다 (예 : Equatable
class MyGenericClass<Type: Equatable>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func valueEquals(anotherValue: Type) -> Bool{
return self.value == anotherValue
}
}
MyGenericClass
새로 만들 때마다 type 매개 변수는 Equatable
프로토콜을 구현 Equatable
합니다 (type 매개 변수가 ==
사용하여 같은 유형의 다른 변수와 비교 될 수 있는지 확인)
let myFloatGeneric = MyGenericClass<Double>(value: 2.71828) // valid
let myStringGeneric = MyGenericClass<String>(value: "My String") // valid
// "Type [Int] does not conform to protocol 'Equatable'"
let myInvalidGeneric = MyGenericClass<[Int]>(value: [2])
let myIntGeneric = MyGenericClass<Int>(value: 72)
print(myIntGeneric.valueEquals(72)) // true
print(myIntGeneric.valueEquals(-274)) // false
// "Cannot convert value of type 'String' to expected argument type 'Int'"
print(myIntGeneric.valueEquals("My String"))
제네릭의 기본
제네릭 은 형식에 대한 자리 표시 자이므로 여러 유형에 적용 할 수있는 유연한 코드를 작성할 수 있습니다. Any
에 대해 generics를 사용하면 컴파일러가 강력한 유형 안전을 적용 할 수 있다는 장점이 있습니다.
일반적인 자리 표시자는 꺾쇠 괄호 <>
안에 정의됩니다.
일반 함수
함수 의 경우이 자리 표시자는 함수 이름 뒤에 위치합니다.
/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
return arc4random_uniform(2) == 0 ? a : b
}
이 경우 제네릭 자리 표시자는 T
입니다. 함수를 호출 할 때 Swift는 T
의 유형을 추측 할 수 있습니다 (단순히 실제 유형의 자리 표시 자 역할을 함).
let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)
함수에 두 개의 정수를 전달합니다. 따라서 Swift는 T == Int
유추합니다. 따라서 함수 서명은 (Int, Int) -> Int
로 추정됩니다.
제네릭이 제공하는 강력한 유형 안전성 때문에 함수의 인수와 반환은 모두 같은 유형이어야합니다. 따라서 다음은 컴파일되지 않습니다.
struct Foo {}
let foo = Foo()
let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'
일반 유형
클래스 , 구조체 또는 enum 과 함께 제네릭을 사용하려면 형식 이름 뒤에 일반 자리 표시자를 정의 할 수 있습니다.
class Bar<T> {
var baz : T
init(baz:T) {
self.baz = baz
}
}
이 일반 placeholder는 클래스 Bar
를 사용할 때 타입을 필요로합니다. 이 경우 이니셜 라이저 init(baz:T)
에서 유추 할 수 있습니다.
let bar = Bar(baz: "a string") // bar's type is Bar<String>
여기서 일반 placeholder T
는 String
유형으로 유추되어 Bar<String>
인스턴스를 만듭니다. 유형을 명시 적으로 지정할 수도 있습니다.
let bar = Bar<String>(baz: "a string")
유형과 함께 사용하면 주어진 일반 placeholder는 주어진 인스턴스의 전체 수명 동안 해당 유형을 유지하며 초기화 후에는 변경할 수 없습니다. 따라서 baz
속성에 액세스 할 때이 인스턴스에 대해 항상 String
유형이됩니다.
let str = bar.baz // of type String
일반적인 유형 전달
제네릭 형식을 전달할 때 대부분의 경우 예상되는 일반적인 자리 표시 자 유형에 대해 명시해야합니다. 예를 들어, 함수 입력으로 :
func takeABarInt(bar:Bar<Int>) {
...
}
이 함수는 Bar<Int>
만 허용합니다. 제네릭 자리 표시 자 유형이 Int
가 아닌 Bar
인스턴스를 전달하려고하면 컴파일러 오류가 발생합니다.
일반 자리 표시 자 이름 지정
일반적인 자리 표시 자 이름은 한 글자로만 제한되지 않습니다. 주어진 자리 표시자가 의미있는 개념을 나타내는 경우 설명이 포함 된 이름을 지정해야합니다. 예를 들어, 스위프트의 Array
이라는 일반적인 틀이 Element
특정의 요소 형을 정의, Array
인스턴스를.
public struct Array<Element> : RandomAccessCollection, MutableCollection {
...
}
일반 클래스 예제
Type
파라미터의 Type
를 가지는 범용 클래스
class MyGenericClass<Type>{
var value: Type
init(value: Type){
self.value = value
}
func getValue() -> Type{
return self.value
}
func setValue(value: Type){
self.value = value
}
}
이제 type 매개 변수를 사용하여 새 객체를 만들 수 있습니다.
let myStringGeneric = MyGenericClass<String>(value: "My String Value")
let myIntGeneric = MyGenericClass<Int>(value: 42)
print(myStringGeneric.getValue()) // "My String Value"
print(myIntGeneric.getValue()) // 42
myStringGeneric.setValue("Another String")
myIntGeneric.setValue(1024)
print(myStringGeneric.getValue()) // "Another String"
print(myIntGeneric.getValue()) // 1024
여러 유형 매개 변수로 제네릭을 만들 수도 있습니다
class AnotherGenericClass<TypeOne, TypeTwo, TypeThree>{
var value1: TypeOne
var value2: TypeTwo
var value3: TypeThree
init(value1: TypeOne, value2: TypeTwo, value3: TypeThree){
self.value1 = value1
self.value2 = value2
self.value3 = value3
}
func getValueOne() -> TypeOne{return self.value1}
func getValueTwo() -> TypeTwo{return self.value2}
func getValueThree() -> TypeThree{return self.value3}
}
그리고 같은 방식으로 사용됩니다.
let myGeneric = AnotherGenericClass<String, Int, Double>(value1: "Value of pi", value2: 3, value3: 3.14159)
print(myGeneric.getValueOne() is String) // true
print(myGeneric.getValueTwo() is Int) // true
print(myGeneric.getValueThree() is Double) // true
print(myGeneric.getValueTwo() is String) // false
print(myGeneric.getValueOne()) // "Value of pi"
print(myGeneric.getValueTwo()) // 3
print(myGeneric.getValueThree()) // 3.14159
일반 클래스 상속
일반 클래스는 상속 될 수 있습니다.
// Models
class MyFirstModel {
}
class MySecondModel: MyFirstModel {
}
// Generic classes
class MyFirstGenericClass<T: MyFirstModel> {
func doSomethingWithModel(model: T) {
// Do something here
}
}
class MySecondGenericClass<T: MySecondModel>: MyFirstGenericClass<T> {
override func doSomethingWithModel(model: T) {
super.doSomethingWithModel(model)
// Do more things here
}
}
Generics를 사용하여 배열 함수 단순화
객체 지향 remove 함수를 작성하여 배열의 기능을 확장하는 함수입니다.
// Need to restrict the extension to elements that can be compared.
// The `Element` is the generics name defined by Array for its item types.
// This restriction also gives us access to `index(of:_)` which is also
// defined in an Array extension with `where Element: Equatable`.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
if let index = self.index(of: element ) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}
용법
var myArray = [1,2,3]
print(myArray)
// Prints [1,2,3]
이 함수를 사용하여 색인이 필요없는 요소를 제거하십시오. 그냥 제거 할 개체를 전달하십시오.
myArray.remove(2)
print(myArray)
// Prints [1,3]
유형 안전성 향상을 위해 제네릭 사용
제네릭을 사용하지 않고이 예제를 사용 해보자.
protocol JSONDecodable {
static func from(_ json: [String: Any]) -> Any?
}
프로토콜 선언은 실제로 사용하지 않는 한 괜찮아 보입니다.
let myTestObject = TestObject.from(myJson) as? TestObject
왜 결과를 TestObject
로 캐스팅해야합니까? 프로토콜 선언의 Any
반환 유형 때문입니다.
generics를 사용하면 런타임 오류를 유발할 수있는이 문제를 피할 수 있습니다.
protocol JSONDecodable {
associatedtype Element
static func from(_ json: [String: Any]) -> Element?
}
struct TestObject: JSONDecodable {
static func from(_ json: [String: Any]) -> TestObject? {
}
}
let testObject = TestObject.from(myJson) // testObject is now automatically `TestObject?`
고급 형식 제약 조건
where
절을 사용하여 제네릭에 대한 여러 유형 제약 조건을 지정할 수 있습니다.
func doSomething<T where T: Comparable, T: Hashable>(first: T, second: T) {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
인수 목록 다음에 where
절을 작성하는 것도 유효합니다.
func doSomething<T>(first: T, second: T) where T: Comparable, T: Hashable {
// Access hashable function
guard first.hashValue == second.hashValue else {
return
}
// Access comparable function
if first == second {
print("\(first) and \(second) are equal.")
}
}
확장은 조건을 만족하는 유형으로 제한 될 수 있습니다. 함수는 유형 조건을 만족하는 인스턴스에서만 사용할 수 있습니다.
// "Element" is the generics type defined by "Array". For this example, we
// want to add a function that requires that "Element" can be compared, that
// is: it needs to adhere to the Equatable protocol.
public extension Array where Element: Equatable {
/// Removes the given object from the array.
mutating func remove(_ element: Element) {
// We could also use "self.index(of: element)" here, as "index(of:_)"
// is also defined in an extension with "where Element: Equatable".
// For the sake of this example, explicitly make use of the Equatable.
if let index = self.index(where: { $0 == element }) {
self.remove(at: index)
} else {
fatalError("Removal error, no such element:\"\(element)\" in array.\n")
}
}
}