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")
}
}
}