English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Swift 일반화

Swift는 제네릭을 제공하여 유연하고 재사용 가능한 함수와 타입을 작성할 수 있습니다.

Swift 표준 라이브러리는 제네릭 코드로 구축되었습니다.

Swift의 배열과 딕셔너리 타입은 제네릭 집합입니다.

Int 배열을 생성하거나 String 배열을 생성할 수도 있으며, 또한 다른 Swift의 타입 데이터 배열도 될 수 있습니다.

다음 예제는 두 개의 Int 값을 교환하는 데 사용되는 비제네릭 함수 exchange입니다:

온라인 예제

// 두 변수를 교환하는 함수 정의
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numb1 = 100
var numb2 = 200
 
print("교환 전 데이터: \(numb1)와 (numb2)")
swapTwoInts(&numb1, (numb2)
print("교환 후 데이터: (numb1)와 (numb2)")

위 프로그램 실행 결과는 다음과 같습니다:

교환 전 데이터: 100와 200
교환 후 데이터: 200와 100

위 예제는 정수 Int 타입의 변수를 교환하는 데만 사용됩니다. 두 개의 String 값이나 Double 값을 교환하려면, 예를 들어 swapTwoStrings(_:_:)와 swapTwoDoubles(_:_:)와 같은 새로운 함수를 작성해야 합니다. 다음과 같이 보입니다:

String 과 Double 값 교환 함수

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

위 코드는 기능 코드가 동일하지만 타입이 다릅니다. 이 경우 일반적인 타입을 사용하여 코드를 반복적으로 작성하지 않을 수 있습니다.

일반적인 타입은 실제 타입 이름(예: Int, String, Double) 대신 대체 타입 이름(이곳에서는 알파벳 T로 표시)을 사용합니다.

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

swapTwoValues 뒤에占位符 타입 이름(T)이 따라옵니다. 괄호로 묶여 있습니다.<T>)。이 꼬리줄은 Swift가 T라는 실제 타입을 찾지 않도록 알립니다.

다음 예제는 두 Int와 String 값을 교환하는 일반적인 함수 exchange입니다:

온라인 예제

// 두 변수를 교환하는 함수 정의
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
 
var numb1 = 100
var numb2 = 200
 
print("교환 전 데이터: (numb1)와 (numb2)")
swapTwoValues(&numb1, (numb2)
print("교환 후 데이터: (numb1)와 (numb2)")
 
var str1 = "A"
var str2 = "B"
 
print("교환 전 데이터: (str1)와 (str2)")
swapTwoValues(&str1, (str2)
print("교환 후 데이터: (str1)와 (str2)")

위 프로그램 실행 결과는 다음과 같습니다:

교환 전 데이터:  100와 200
교환 후 데이터: 200와 100
교환 전 데이터: A와 B
교환 후 데이터: B와 A

일반적인 타입

Swift는 사용자 정의된 일반적인 타입을 정의할 수 있습니다.

직접 정의된 클래스, 구조체, 열거형은 어떤 타입에도 적용됩니다. Array와 Dictionary의 사용법과 같습니다.

다음으로 우리는 Stack (스택)이라는 일반적인 컬렉션 타입을 작성하겠습니다. 스택은 컬렉션의 끝에서만 새로운 요소를 추가할 수 있으며(입스택이라고 합니다), 끝에서만 요소를 제거할 수 있습니다(출스택이라고 합니다).

图片中从左到右解析如下:

  • 三个值在栈中。

  • 第四个值被压入到栈的顶部。

  • 现在有四个值在栈中,最近入栈的那个值在顶部。

  • 栈中最顶部的那个值被移除,或称之为出栈。

  • 移除掉一个值后,现在栈又只有三个值了。

以下示例是一个非泛型版本的栈,以 Int 型的栈为例:

Int 型的栈

struct IntStack {
    var items = [Int]()
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
}

这个结构体在栈中使用一个名为 items 的 Array 属性来存储值。Stack 提供了两个方法:push(_:) 和 pop(),用来向栈中压入值以及从栈中移除值。这些方法被标记为 mutating,因为它们需要修改结构体的 items 数组。

上面的 IntStack 结构体只能用于 Int 类型。不过,可以定义一个泛型 Stack 结构体,从而能够处理任意类型的值。

下面是相同代码的泛型版本:

泛型的栈

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
 
var stackOfStrings = Stack<String>()
print("문자열 요소가 스택에 입력됩니다: ")
stackOfStrings.push("google")
stackOfStrings.push("w3codebox)
print(stackOfStrings.items);
 
let deletetos = stackOfStrings.pop()
print("出栈元素: " + deletetos)
 
var stackOfInts = Stack<Int>()
print("整数元素入栈: ")
stackOfInts.push(1)
stackOfInts.push(2)
print(stackOfInts.items);

示例执行结果为:

문자열 요소가 스택에 입력됩니다: 
["google", "w3codebox"]
出栈元素: w3codebox
整数元素入栈: 
[1, 2]

Stack 基本上和 IntStack 相同,占位类型参数 Element 代替了实际的 Int 类型。

以上示例中 Element 在如下三个地方被用作占位符:

  • 创建 items 属性,使用 Element 类型的空数组对其进行初始化。

  • 指定 push(_:) 方法的唯一参数 item 的类型必须是 Element 类型。

  • 指定 pop() 方法的返回值类型必须是 Element 类型。

扩展泛型类型

当一个泛型类型被扩展时(使用extension关键字),在扩展的定义中并不需要提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

아래의 예제는 타입 Stack을 확장하여 topItem이라는 이름의 읽기 전용 계산형 속성을 추가합니다. 이 속성은 현재 스택의 상단 요소를 반환하며 스택에서 제거하지 않습니다:}}

제네릭

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}
 
extension Stack {
    var topItem: Element? {
       return items.isEmpty ? nil : items[items.count - 1]
    }
}
 
var stackOfStrings = Stack<String>()
print("문자열 요소가 스택에 입력됩니다: ")
stackOfStrings.push("google")
stackOfStrings.push("w3codebox)
 
if let topItem = stackOfStrings.topItem {
    print("스택의 상단 요소는: \(topItem).")
}
 
print(stackOfStrings.items)

예제에서 topItem 속성은 Element 타입의 옵셔널 값을 반환합니다. 스택이 비어 있을 때 topItem은 nil을 반환하며, 스택이 비어 있지 않을 때 topItem은 items 배열의 마지막 요소를 반환합니다.

위 프로그램 실행 결과는 다음과 같습니다:

문자열 요소가 스택에 입력됩니다: 
스택의 상단 요소는: w3codebox.
["google", "w3codebox"]

기존 타입을 확장하여 관련 타입을 지정할 수도 있습니다.

예를 들어 Swift의 Array 타입은 append(_:), count 속성, Int 타입 인덱스 값을 받는 인덱스로 요소를 검색하는 메서드를 제공합니다. 이 세 가지 기능은 Container 프로토콜의 요구사항을 충족하므로 Array를 확장할 때 Array가 이 프로토콜을 채택하면 됩니다.

다음 예제에서는 빈 확장을 생성하면 됩니다:

extension Array: Container {}

타입 제약

타입 제약은 특정 클래스를 상속해야 하는 타입 매개변수를 지정하거나 특정 프로토콜을 따르거나 프로토콜 구성을 따르는 것을 지정합니다.

타입 제약 문법

형제형 매개변수 이름 뒤에 타입 제약을 쓰고, 콜론으로 구분하여 타입 매개변수 체인의 일부로 사용할 수 있습니다. 이러한 타입 제약의 기본 문법은 다음과 같습니다. (형제형 타입의 문법과 같습니다):

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

上面这个函数有两个类型参数。第一个类型参数 T,有一个要求 T 必须是 SomeClass 子类的类型约束;第二个类型参数 U,有一个要求 U 必须符合 SomeProtocol 协议的类型约束。

온라인 예제

제네릭

// 非泛型函数,查找指定字符串在数组中的索引
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            // 找到返回索引值
            return index
        }
    }
    
}
 
 
3codebox
if let foundIndex = findIndex(ofString: "w3codebox
    print("w3codebox 的索引为 \(foundIndex)")
}

索引下标从 0 开始。

위 프로그램 실행 결과는 다음과 같습니다:

w3codebox 的索引为 3

关联类

Swift 中使用 associatedtype 关键字来设置关联类型示例。

下面实例定义了一个 Container 协议,该协议定义了一个关联类型 ItemType。

Container 协议只指定了三个任何遵从 Container 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。

// Container 프로토콜
protocol Container
    associatedtype ItemType
    // 컨테이너에 새로운 요소를 추가
    mutating func append(_ item: ItemType)
    // 컨테이너의 요소 수를 얻습니다
    var count: Int { get }
    // Int 타입의 인덱스를 사용하여 컨테이너의 각 요소를 검색
    subscript(i: Int) -> ItemType { get }
}
// Stack 结构体遵从 Container 协议
struct Stack<Element>: Container {
    // Stack<Element>의 원래 구현 부분
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 프로토콜의 구현 부분
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
var tos = Stack<String>()
tos.push("google")
tos.push("w3codebox)
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)

위 프로그램 실행 결과는 다음과 같습니다:

["google", "w3codebox, "taobao"]
3

Where 语句

类型约束能够确保类型符合泛型函数或类的定义约束。

你可以在参数列表中通过where语句定义参数的约束。

你可以写一个where语句,紧跟在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。

온라인 예제

아래의 예제는 allItemsMatch라는 제네릭 함수를 정의하여 두 개의 Container 예제가 동일한 순서의 동일한 요소를 포함하고 있는지 확인합니다.

모든 요소가 일치하면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

제네릭

// Container 프로토콜
protocol Container
    associatedtype ItemType
    // 컨테이너에 새로운 요소를 추가
    mutating func append(_ item: ItemType)
    // 컨테이너의 요소 수를 얻습니다
    var count: Int { get }
    // Int 타입의 인덱스를 사용하여 컨테이너의 각 요소를 검색
    subscript(i: Int) -> ItemType { get }
}
 
// // Container 프로토콜을 따르는 패턴화된 TOS 타입
struct Stack<Element>: Container {
    // Stack<Element>의 원래 구현 부분
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 프로토콜의 구현 부분
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}
// 확장, Array를 Container로 사용
extension Array: Container {}
 
func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
        
        // 검사 두 개의 컨테이너가 동일한 수의 요소를 가지고 있는지
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        // 각 쌍의 요소가 일치하는지 확인
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // 모든 요소가 일치하면 true를 반환
        return true
}
var tos = Stack<String>()
tos.push("google")
tos.push("w3codebox)
tos.push("taobao")
 
var aos = ["google", "w3codebox, "taobao"]
 
if allItemsMatch(tos, aos) {
    print("모든 요소 일치")
} else {
    print("요소 불일치")
}

위 프로그램 실행 결과는 다음과 같습니다:

모든 요소를 일치시키기