티스토리 뷰
1. Inheritance
상속에 대해 알아보겠습니다.
상속 관계에 있는 클래스들은 상속 계층을 구성합니다. 클래스 계층에서 가장 위에 있는 클래스를 Base Class 또는 Root Class 라고 합니다. 바로 아래 계층의 클래스는 Base Class 를 상속합니다. 상속 관계에서 위에 있는 클래스를 Super Class 라고 하거나 Parent Class 라고 합니다. 그리고 아래쪽에 있는 클래스는 Sub Class 또는 Child Class 라고 합니다.
Objective-C 에서는 모든 클래스가 NSObject 라는 클래스를 상속해야 하지만 Swift 에서는 이런 제약이 없습니다.
여러 Sub Class 가 하나의 Super Class 를 상속하는 것은 문제가 없지만, 하나의 Sub Class 가 두 개 이상의 Super Class 를 상속하는 것은 문제가 됩니다. 이것을 다중 상속이라고 하는데 Swift 에서는 지원되지 않습니다. 다중 상속과 유사한 패턴은 Protocol 을 통해 구현됩니다.
다른 클래스를 상속하는 것을 Subclassing 이라고 합니다. Sub Class 는 Super Class 에 선언되어 있는 멤버들을 상속합니다. 마치 Sub Class 에 선언한 것처럼 자유롭게 사용이 가능합니다.
Super Class 에서 상속 받은 멤버가 Sub Class 에 맞지 않다면 구현을 수정하는 것도 가능합니다. 이것을 Overriding(재정의) 라고 합니다.
Super Class 에서 상속은 하면서 재정의를 금지하는 것도 가능합니다.
// 문법
// class ClassName: SuperClassName {
// }
class Figure {
var name = "Unknown"
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Circle: Figure {
var radius = 0.0
}
// Circle 클래스에는 생성자를 구현하지 않았지만
// Circle 클래스가 Figure 클래스를 상속 받았기 때문에
// name 파라미터를 가지는 생성자를 사용할 수 있습니다.
let c = Circle(name: "Circle")
c.radius
// Super Class 의 멤버에도 자유롭게 접근할 수 있습니다.
c.name
c.draw()
final 키워드를 통해 상속을 금지할 수 있습니다.
// 문법
// final class ClassName: SuperClassName {
// }
// 다른 클래스를 상속하는 것은 가능하지만,
// 다른 클래스가 final class 를 상속 받는 것은 금지됩니다.
final class Rectangle: Figure {
var width = 0.0
var height = 0.0
}
// final class 는 상속이 금지된 클래스로 아래 코드는 에러가 납니다.
//class Square: Rectangle {
//
//}
2. Overriding
상속 받은 멤버를 Sub Class 에 맞게 변경하는 것으로 Sub Class 에서 Super Class 와 동일한 멤버를 구현하는 것입니다. Overriding 이 가능한 멤버는 method, 속성, subscripts, 생성자입니다.
Overriding 은 두 가지 방식으로 구현됩니다.
1. Super Class 구현을 기반으로 새로운 코드을 추가하거나
2. Super Class 구현을 무시하고 완전히 새롭게 구현합니다.
class Figure {
var name: String
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Circle: Figure {
var radius = 0.0
var diameter: Double {
return radius * 2
}
override func draw() {
super.draw() // 먼저 상위 구현을 호출
print("🔵") // 새롭게 구현
}
}
let c = Circle(name: "Circle")
c.draw()
속성을 overriding 하는 경우에는 메소드와 조금 다른 방식을 사용합니다. 속성을 overriding 할 때는 계산 속성을 구현하거나 프로퍼티 옵저버를 사용합니다.
먼저 계산 속성을 구현 하는 방식을 알아보겠습니다.
class Oval: Circle {
override var radius: Double {
get {
return super.radius
}
set {
super.radius = newValue
}
}
override var diameter: Double {
get {
return super.diameter
}
set {
super.radius = newValue / 2
}
}
}
계산 속성으로 구현할 때 주의할 점이 있습니다.
- 읽기와 쓰기가 가능한 속성은 읽기 전용으로 오버라이딩 하는 것은 허용되지 않습니다. 반드시 읽기와 쓰기가 모두 가능한 계산 속성으로 overriding 하여야 합니다.
- 읽기 전용 속성을 overriding 할 때 get block, set block 모두 구현 가능하지만 읽기 전용 속성의 값을 변경할 수는 없고 다른 속성의 값을 변경하는 것은 가능합니다.
다음으로 Property Observer 를 사용하여 구현하는 방법을 살펴보겠습니다.
class Oval: Circle {
// 프로퍼티 옵저버로 구현하기
// super 의 속성이 변수 저장 속성으로 되어 있는 경우 가능합니다.
// 읽기 전용 속성의 경우에는 값이 변하지 않는 속성이므로 프로퍼티 옵저버로 오버라이딩 할 수 없습니다.
override var radius: Double {
willSet {
print(newValue)
}
didSet {
print(oldValue)
}
}
}
overriding 또한 final 키워드를 통해 금지할 수 있는데 이것이 상속 자체의 금지를 의미하는 것은 아닙니다.
3. Upcasting and Downcasting
먼저 몇가지 class 들을 선언하겠습니다.
class Figure {
var name: String
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Rectangle: Figure {
var width = 0.0
var height = 0.0
override func draw() {
super.draw()
print("◼️ \(width) x \(height)")
}
}
class Square: Rectangle {
}
let f = Figure(name: "Unknown")
f.name
let r = Rectangle(name: "Rect")
r.width
r.height
r.name
Upcasting 이란, 서브클래스 인스턴스를 슈퍼클래스 형식으로 저장하는 것을 말합니다.
let s: Figure = Square(name: "Square")
s.name
Downcasting 이란, Upcasting 된 인스턴스를 원래 형식으로 처리하기 위해 필요합니다. Upcasting 과 달리 에러가 발생할 수 있고 항상 성공하는 것도 아닙니다. Downcasting 에는 Typecasting 연산자를 사용합니다.
// Figure 타입인 s 를 Rectangle 타입으로 Downcasting 했습니다.
let downcastedS = s as! Rectangle
downcastedS.width
downcastedS.height
downcastedS.name
class Rhombus: Square {
var angle = 45.0
}
// 원본 클래스보다 아래쪽에 있는 클래스로 Downcasting 하는 것은 허용되지 않습니다.
// let dr = s as! Rhombus -> 에러
4. Type casting
인스턴스 형식을 확인하거나 다른 형식의 인스턴스를 처리할 때 사용합니다. Type casting 의 연산자는 두가지가 있는데 먼저 Type check operator 를 알아보겠습니다.
(1) Type Check Operator
문법은 다음과 같습니다.
expression is Type
is 연산자라고 부르기도 합니다. Type Check 연산자의 경우 런타임에 타입을 확인합니다.
두 피연산자의 형식이 동일한 경우 true 를 리턴하며, 왼쪽 피연산자의 형식이 오른쪽 피연산자와 동일한 상속 계층에 있고 오른쪽 피연산자가 슈퍼클래스일 때도 true 가 리턴됩니다. 나머지 경우에는 false 가 리턴됩니다.
let num = 123
num is Int // true
num is Double // false
num is String // false
class Figure {
var name: String
init(name: String) {
self.name = name
}
func draw() {
print("draw \(name)")
}
}
class Triangle: Figure {
override func draw() {
super.draw()
print("🔺")
}
}
class Rectangle: Figure {
var width = 0.0
var height = 0.0
override func draw() {
super.draw()
print("◼️ \(width) x \(height)")
}
}
class Square: Rectangle {
}
class Circle: Figure {
var radius = 0.0
override func draw() {
super.draw()
print("🔴")
}
}
let t = Triangle(name: "Triangle")
let r = Rectangle(name: "Rectangle")
let s = Square(name: "Square")
let c = Circle(name: "Circle")
r is Rectangle // true
r is Figure // true
r is Square // false
(2) Type Casting Operator
expression as Type
위와 같이 작성하면 Compile Time Cast 로 컴파일 타임에 결과가 확정됩니다. 따라서 캐스팅에 성공하면 에러가 발생하지 않고 캐스팅에 실패하면 컴파일 에러가 발생합니다.
expression as? Type
expression as! Type
위 두가지 경우는 Run Time Cast 방식으로 런타임에 결정되기 때문에 컴파일 타임에는 결과를 알 수 없습니다. as? 의 경우 캐스팅에 성공하면 캐스팅된 인스턴스를 리턴하고 실패하면 nil 을 리턴합니다. as! 의 경우 강제 추출 연산자와 마찬가지로 캐스팅에 실패할 경우 crash 가 발생합니다. 따라서 forced cast 는 가능하면 사용하지 않는 것이 좋습니다.
주로 다운캐스팅을 할 때나 값 형식을 다른 형식으로 전환할 때 사용합니다. 왼쪽 피연산자의 형식이 오른쪽 형식과 호환된다면 오른쪽 형식으로 캐스팅 된 인스턴스를 리턴합니다. 새로운 인스턴스가 리턴되는 것은 아닙니다. 이미 존재하는 인스턴스에서 오른쪽 피연산자 형식에 있는 멤버만 접근할 수 있는 임시 인스턴스를 리턴합니다.
// 구조체로 구현된 String 자료형은 클래스로 구현된 NSString 자료형으로 호환됩니다.
let nsstr = "str" as NSString
// "str" as Int - 컴파일 에러 발생
t as? Triangle // true
t as! Triangle // true
var upcasted: Figure = s
// Upcasting 의 경우 항상 성공하기 때문에 Compile cast 를 사용할 수 있습니다.
// upcasted = s as Figure
// 하지만 Downcasting 에는 Compile cast 를 사용할 수 없습니다.
upcasted as? Square // true
upcasted as! Square // true
upcasted as? Rectangle // true
upcasted as! Rectangle // true
upcasted as? Circle // nil
// upcasted as! Circle - 에러
// 실제로 Downcasting 을 할 경우 아래와 같이 사용하는 것이 좋습니다.
if let c = upcasted as? Circle {
}
// 배열의 경우 동일한 형식의 값들만 저장할 수 있는데,
// 모든 요소가 동일한 상속 계층에 있다면 가장 인접한 슈퍼 클래스로 Upcasting 되어 저장이 됩니다.
let list = [t, r, s, c] // list 의 타입은 [Figure]
// item 은 Figure 형식이지만 각 인스턴스에서 overriding 한 메소드가 호출됩니다.
// 이것을 다형성(Polymorphism)이라고 합니다.
// Upcasting 되어있는 인스턴스를 통해 메소드를 호출하더라도 실제 형식에서 overriding 된 메소드가 호출된다는 것입니다.
for item in list {
item.draw()
if let c = item as? Circle {
c.radius
}
}
// 결과
draw Triangle
🔺
draw Rectangle
◼️ 0.0 x 0.0
draw Square
◼️ 0.0 x 0.0
draw Circle
🔴
5. Any & AnyObject
범용 자료형으로 Any 는 모든 형식을 저장할 수 있고, AnyObject 는 모든 클래스 형식을 저장할 수 있습니다.
// Any 로 지정하면 형식에 관계 없이 모든 데이터를 저장할 수 있습니다.
var data: Any = 1
data = 2.3
data = "String"
data = [1, 2, 3]
data = NSString() // 값 형식, 참조 형식 상관 없이 모두 저장 가능합니다.
// AnyObject 에는 참조 형식만 저장할 수 있습니다.
var obj: AnyObject = NSString()
// obj = 1 -> 값 형식을 저장하는 것은 에러가 발생합니다.
// 이 외에도 Any 로 시작하는 다양한 형식들이 있는데
// Swift 에서는 이를 Type-Erasing Wrapper or Type-Erased Wrapper 라고 합니다.
// Any 와 AnyObject 는 형식에 대한 정보를 가지고 있지 않습니다.
// 형식에 대한 정보가 없기 때문에 속성이나 메소드에 접근할 수 없습니다.
// 그래서 인스턴스를 사용하기 위해서는 타입 캐스팅이 필요합니다.
if let str = data as? String {
print(str.count)
} else if let list = data as? [Int] {
}
// Type Casting Pattern
// switch 문과 type casting 연산을 함께 수행하는 패턴을 말합니다.
// 범용 형식으로 저장되었거나 Upcasting 된 인스턴스를 매칭 시킬 때 주로 사용합니다.
switch data {
case let str as String:
print(str.count)
case let list as [Int]:
print(list.count)
case is Double:
print("Dounle Value")
default:
break
}
6. Overloading
하나의 형식에서 동일한 이름을 가진 다수의 멤버를 구현할 때 사용합니다. 함수, 메소드, subscript, 생성자에서 오버로딩을 지원합니다. overloading 규칙을 살펴보면 다음과 같습니다.
- 규칙1: 함수 이름이 동일하면 파라미터 수로 식별
- 규칙2: 함수 이름, 파라미터 수가 동일하면 파라미터 자료형으로 식별
- 규칙3: 함수 이름, 파라미터가 동일하면 Argument Label 로 식별
- 규칙4: 함수 이름, 파라미터, Argument Label 이 동일하면 리턴형으로 식별
// 1번 함수
func process(value: Int) {
print("process Int")
}
// 2번 함수: 1번 함수와 규칙 2에 의해 식별됩니다.
func process(value: String) {
print("process String")
}
// 3번 함수: 2번 함수와 규칙 1에 의해 식별됩니다.
func process(value: String, anotherValue: String) {
}
// 4번 함수: 2번 함수와 규칙 3에 의해 식별됩니다.
func process(_ value: String) {
}
process(value: 0)
process(value: "str")
process("str")
보통의 경우 규칙 3번까지만 사용하고 리턴형으로 식별하는 것은 피하는 것이 좋습니다.
func process(value: Double) -> Int {
return Int(value)
}
func process(value: Double) -> String {
return String(value)
}
let result: Int = process(value: 12.34)
// 타입 추론으로 형식을 지정하고 싶은 경우, let result = process(value: 12.34) as Int 를 사용합니다.
위에서는 함수에 대해서만 알아보았는데 메소드에서도 살펴보도록 하겠습니다.
struct Rectangle {
func area() -> Double {
return 0.0
}
static func area() -> Double {
return 0.0
}
}
let r = Rectangle()
r.area()
Rectangle.area()
인스턴스 메소드와 타입 메소드는 함수 이름, 파라미터, Argument Label, 리턴형이 모두 동일하더라도 호출 방식에 차이가 있어 완전히 구분되기 때문에 문제없이 구현 가능합니다.
'swift > 문법' 카테고리의 다른 글
21. Swift 문법 - Extensions (0) | 2020.10.26 |
---|---|
20. Swift 문법 - Initializer and Deinitializer (0) | 2020.10.25 |
18. Swift 문법 - Method and Subscript (0) | 2020.10.24 |
17. Swift 문법 - Property (0) | 2020.10.23 |
16. Swift 문법 - Structures and Classes (0) | 2020.10.22 |
- Total
- Today
- Yesterday
- string
- cloudfront
- search
- ECR
- CodePipeline
- Dynamic Programming
- Baekjoon
- 순열
- SWIFT
- CodeDeploy
- ionic
- DFS
- 에라토스테네스의 체
- EC2
- 조합
- Algorithm
- map
- java
- 소수
- CodeCommit
- AWS
- spring
- 프로그래머스
- programmers
- Combination
- 수학
- BFS
- array
- permutation
- sort
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |