Swift 문법 정리 2

찬솔·2025년 4월 30일

iOS 스터디 자료로 만든 문법 정리

Optinal - 옵셔널

Optinal을 사용하는 이유?

  • Swift에서 변수나 상수는 기본적으로 값을 반드시 가져야 함
  • 그런데 어떤 값이 “없을 수도 있다”는 가능성을 표현하고 싶을 땐 Optional을 사용
import Foundation

// var a: Int 
// a = nil // a에는 nil을 넣을 수 없음. 무조건 초기화를 시켜줘야함

let stock: Int? = nil // 값이 없음.(NULL과 비슷함) 타입을 항상 명시적으로 표시해야함
print(stock)

let str: String? = nil

let optionalStr: String? // ?만 붙여도 옵셔널을 선언할 수 있음(nil)

let a:Int? = nil
let b = a
b

let c: Optional<Int> = nil

var num: Int?
print(num)

num = 123
print(num) // wrapping 되어 있어서 값을 넣어도 전달되지 않음
print(num!) // 강제로 값을 추출. (Unwrapping)

let n = 123
print(n)

num = 123
let before = num

let after = num!
  • Swift는 안전한 코드를 지향해서 nil을 허용하지 않는 타입과 구별하기 위해 Optional 타입을 만듦.
  • Optional 타입을 사용할 땐 값을 꺼내서 써야 함(Unwrapping)

Optinal Binding(Unwrapping)

import Foundation
// 옵셔널의 값이 존재하는지를 검사한 뒤, 존재한다면 그 값을 다른 변수에 대입 (추출)
var num: Int? = 123

if let n = num { // n은 옵셔널 값이 아님. num의 값이 존재 하므로 n의 num값 대입. 대입하는 편이 오류가 적고 안정적임.
    print(n) // (명시적해제 - 비강제해제
}

if num != nil { // 옵셔널 값 추출. 옵셔널의 값이 존재하면 강제로 추출 (명시적해제 - 강제해제)
    print(num!)
}

var str: String? = "Swift"
guard let str else {
    print(str!)
    fatalError() // gaurd 리턴문
}

let a: Int? = 12
let b: String? = "str"

if let a, let b, b.count < 5 { // b의 글자수가 5보다 작으면 a,b 출력
    print(a,b)
}

if let a, b!.count < 5{ // a는 새로 선언되어 옵셔널 값이 아니지만 b는 옵셔널이기 때문에 추출 할 때 !를 붙여서 강제로 추출해서 비교.
    print(a,b) // b를 새로운 상수로 선언하면 b의 길이를 비교하는 구문에서의 b에서 !를 안붙여도 됨
}

if let b, a! > 3, b.count < 5 { // 비교 구문이 아니면 무조건 새로 상수 선언
    print(a,b) //새로 선언하지 않고 비교연산에서 강제 추출 할 경우 업래핑된 상태라 출력값이 래핑된 상태로 출력이 됨.
}

if let aNum = a , let bNum = b , bNum.count < 5 { // 값을 대입해서 하는것이 가장 안정적임
    print(aNum,bNum)
}

if let aNum = a , let bNum = b , b!.count < 5 { // 비교연산에 한하여 b를 강제 추출 할 수 있음
    print(aNum,bNum)
}

print(a!, b!) // 이렇게 바로 강제 추출도 가능함.

if let a, let b { //새로 선언해주면 강제 추출 하지 않아도 사용가능
    print(a,b)
}

if a == 12 { // 비교연산자를 통한 옵셔널값의 묵시적해제. 하지만 출력이나 연산에서는 해제가 되지 않음
    print("a = 12")
    print(a!+1) // 이렇게 !를 붙여야 연산가능
}

let a1: Int! = 12
print(a1+1) // 연산할때나 비교할때 묵시적 해제가 되어 사용가능
  • ! → 강제 언래핑(값이 있다는 가정하에 언래핑 하는거라 오류날 위험 이 큼)
  • 옵셔널 바인딩 (if let / guard let) - 안전한 방식

Struct / Class

import Foundation

struct SPerson { // 구조체
    var name: String // 값을 저장하지 않아도 됨.
    var age: Int
    
    func speak(){
        print("Hello")
    }
}

let s = SPerson(name: "Steve", age: 50)

let name = "Pual" // 오류가 나지 않음. 접근방식이 다름. 구조체의 name이 아님.

s.name
s.age

s.speak()

class CPerson { // 값을 먼저 저장해 주어야함
    var name: String = "Steve"
    var age: Int = 50
    func speak() {
        print("Hello")
    }
}

let c = CPerson()
c.name
c.age
  • Struct는 변수에 값을 저장하지 않아도됨
  • Class는 변수에 값을 저장해야함

Value-vs-Reference

import Foundation

struct PositionValue { // 값형식
    var x = 0.0
    var y = 0.0
}

class  PositionObject { // 참조형식
    var x = 0.0
    var y = 0.0
}

var v = PositionValue()
v.x
v.y

var o = PositionObject()
o.x
o.y

var v2 = v
v2.x
v2.y

var o2 = o
o2.x
o2.y

v2.x = 12 // v2는 바뀌지만 v는 바뀌지 않음. (v2는 복사본)
v2.y = 34

v.x
v.y

v2.x
v2.y

o2.x = 12 // o2도 바뀌고 o도 바꿈. 새로운 복사본을 만들지 않고 원본을 그대로 전달함. 참조를 전달함(o의 메모리 주소 전달)
o2.y = 34

o.x
o.y
o2.x
o2.y
  • Class는 참조 형식이라 Class 속성의 값을 변경하면 모두 변경됨

StoredProperties - 저장 프로퍼티

  • lazy: 실제로 필요할 때 초기화를 시켜줌(지연저장)
import Foundation

struct Person {
    let name: String = "Chan Sol"
    var age: Int = 26
}

var p = Person()
p.name
p.age

p.age = 30 // 구조체의 가변성은 속성의 가변성에 영향을 줌

struct Image {
    init() {
        print("new image")
    }
}

struct BlogPost {
    let title: String = "Title"
    let content: String = "Content"
    var notLazy: Image = Image() // 지연저장하지 않아서 print 실행됨
    lazy var attachment: Image = Image() // 지연저장. 이 속성은 초기화가 되지 않음. 초기화자에서 실행 및 초기화가 되지않음.
    // 지연 저장 되었기때문에 print가 실행되지않음
    let date: Date = Date()
    
    // 위 Date속성이 초기화되어서 formattedDate속성은 초기화 될수 없음. 따라서 lazy를 사용해서 지연 시켜야함
    lazy var formattedDate: String = { // lazy는 무조건 var로 선언
       let f = DateFormatter()
        f.dateStyle = .long
        f.timeStyle = .medium
        return f.string(from: date)
    }()
}

var post = BlogPost() // 구조체에서 지연저장 속성을 사용하려면, 반드시 변수에 저장해야함.
post.attachment // 이렇게 접근해야지 초기화됨(print실행)

lazy를 쓰는이유?

  • 초기화 비용이 크거나 무거운 객체
  • 꼭 필요한 경우에만 초기화하고 싶을 때
  • 초기화 순서 상, 다른 속성이 먼저 초기화돼야 할 때 (ex. formattedDate)

ComputedProperties - 연산 프로퍼티

  • 값을 저장하지 않고, 계산해서 리턴하거나 계산해서 설정(다른 값을 기반)
import Foundation

class Person {
    var name: String
    var yearOfBirth: Int
    
    init(name: String, year: Int) {
        self.name = name
        self.yearOfBirth = year
    }
    
    var age: Int { // 현재 나이를 계산
        get { // age를 읽을 때 실행
            let calendar = Calendar.current
            let now = Date()
            let year = calendar.component(.year, from: now)
            return year - yearOfBirth // 현재 연도에서 태어난 연도를 뺌
        }
        set { // age에 새 값을 넣을 때 실행
            let calendar = Calendar.current
            let now = Date() // 현재 날짜 및 시간
            let year = calendar.component(.year, from: now) // 현재의 년도를 호출하는 메소드
            yearOfBirth = year - newValue // 현재 연도에서 나이를 뺌
        }
    }
}

let p = Person(name: "James", year: 2002) // get 블록이 실행.
p.age 
p.yearOfBirth
// 이후에 age 값을 변경하면 set 블록이 실행되서 클래스 및 속성의 value 모두 변경
p.age = 50 // 현재 연도에서 age즉 현재 나이를 뺀값이 yearOfBirth에 들어감. set 블록 실행. (newVlaue 값을 넣어줌)
p.yearOfBirth

Self - 클래스나 구조체, 열거형 안에서 현재 인스턴스 자체를 가리킬 때 사용(자기자신)

import Foundation

struct/*class*/ Size {
    var width = 0.0
    var height = 0.0
    
    mutating func reset(value: Double) {
//        width = value
//        height = value
        self = Size(width: value, height: value) // 위 코드와 같이 사용 할 수 있음 (class에서 사용불가. 값형식에서만 사용가능)
    }
    
//    func calcArea() -> Double {
//        return width * height // self 생략
//    }
//    
//    var area: Double{
//        return calcArea() // self 생략
//    }
//    
//    func update(width: Double, height: Double) {
//        self.width = width // 파라미터 이름이 같아서 self 생략 불가능
//        self.height = height
//    }
//    
//    func doSonthing() {
//        let c = { self.width * self.height } // 클로저 안에 있으므로 self 생락 불가능
//    }
//    
//    static let unit = ""
//    
//    static func doSomthing() {
//        //self.height 형식 메소드에서 인스턴트 메소드를 직접 접근하는거는 불가능함.
//        unit // self 생략가능. 타입 멤버(static)
//    }
}

Closure - 클로저(이름 없는 함수)

import Foundation

// 함수가 클로저를 리턴함
func makeCounter() -> () -> Int {
    var count = 0 // 이 변수는 클로저 안에서 캡처(capture)됨
    
    // 이 아래 클로저는 () -> Int 타입
    // 외부 함수가 끝나도 클로저는 count 값을 기억하고 있음
    let counter = {
        count += 1 // 외부의 count를 계속 증가시킴
        return count
    }

    return counter // 함수는 클로저를 리턴하고 종료
}

// makeCounter()를 호출하면 클로저를 하나 만들어서 리턴함
let counter1 = makeCounter() // 여기서 count = 0이 캡처됨

print(counter1()) // 1 → 클로저 내부 count가 1 됨
print(counter1()) // 2 → 클로저 내부 count가 2 됨
print(counter1()) // 3 → 계속 유지됨 (클로저가 count를 기억하고 있기 때문)

// 새로운 클로저를 만들면 count는 새로 생김!
let counter2 = makeCounter() // 또 다른 클로저, 또 다른 count = 0
print(counter2()) // 1 → 새롭게 시작됨
  • 클로저는 코드를 변수처럼 저장할 수 있는 함수
  • 클로저가 리턴되더라도 변수는 계속 살아있음
  • 비동기 처리, 콜백, 상태 저장 등에 자주 사용됨

InstanceMethods - 기본적으로 흔히 사용하는 메서드

import Foundation

class Sample {
    var data = 0 // 인스턴스 멤버
    nonisolated(unsafe) static var sharedData = 123 // 타입멤버
    func doSomething() {
        print(data) // 같은 인스턴스(클래스 안에 있음) 그냥 사용 가능
        
        Sample.sharedData // 인스턴스 멤버에서 타입멤버에 접근 할 때는 타입이름으로 접근해야함
    }
    
    func call() {
        doSomething() // 같은 인스턴스 멤버이기 때문에 self를 생략가능 (self.doSomething)
    }
}

let a = Sample()
a.data
a.doSomething() // 반드시 인스턴스 이름을 통해서 호출 해야함
Sample.sharedData = 10
a.call()

struct Size {
    var width = 0.0
    var height = 0.0
    
    mutating func enlarge() { // 가평식에서 속성을 바꾸는 메소드를 구현 할 때는 mutating으로 선언해야함(중요)
        width += 1.0
        height += 1.0
    }
}

var s = Size() // mutating 메소드를 호출 할때는 인스턴스 메소드를 변수로 선언해야함
s.enlarge()
  • 구조체는 값이기 때문에 구조체 안에서 속성을 바꾸려면 mutating을 써야함(Class는 안써도됨)
  • Static을 붙이면 인스턴스 없이도 어디서나 사용할 수 있음

Initializer - 초기화자(생성자)

import Foundation

class Position {
    var x: Double
    var y: Double
    // var z = 0.0 // 잘 사용하지 않음
    init() { // 파라미터가 없는 초기화자
        x = 0.0
        y = 0.0
    }
    
    init(value: Double) { // 파라미터가 있는 초기화자
        x = value
        y = value
    }
}

let a = Position() // 파라미터가 없는 초기화자 호출
a.x
a.y

let b = Position(value: 100) // 파라미터가 있는 초기화자 호출
b.x
b.y

class SizeObj {
    var width = 0.0
    var height = 0.0
    
    init(width: Double, height: Double) {
        self.width = width // self 사용
        self.height = height
    }
   convenience init(value: Double) { // 간편초기화자
//        width = value
//        height = value
        self.init(width: value, height: value)
    }
}

struct SizeValue {
    var width = 0.0
    var height = 0.0
}

let s = SizeValue()
SizeValue(width: 0.0, height: 0.0) // 구조체의 특별한 Initializer

ClassInitializers

import Foundation

class Position {
    var x: Double
    var y: Double
    
    init(x: Double, y: Double) { // Degignated Initializer. 모든 속성을 초기화. 필수
        self.x = x
        self.y = y
    }
    
    convenience init(x: Double) { // Convenience Initializer. 초기화 하고 싶은 속성만 초기화. 필수X
        self.init(x: x, y: 0.0)
    }
}

class Figure {
    var name: String
    
   /*required*/ init(name: String) {
        self.name = name
    }
    
    func draw() {
        print("draw \(name)")
    }
    
    convenience init() { // name을 안받아오면 name을 "Unknown" 으로 초기화해주는 초기화자
        self.init(name: "Unknown")
    }
}

class Rectangle: Figure { // 상속된 초기화자가 일부 속성만 초기화해서 오류.
    var width: Double // = 12
    var height: Double// = 34 // 이렇게 초기화자에서 초기화되지 않은 속성들도 초기화 해줘야함.
    
    init(name: String, width: Double, height: Double) {
        self.width = width
        self.height = height
        super.init(name: name) // 수퍼 클래스에 있는 초기화자에서 초기화하는 속성이 있으면 수퍼 클래스의 초기화자로 넘겨야함.
    }
    
    override init(name: String) {
        self.width = 0
        self.height = 0
        super.init(name: name)
    }
}

class Rectangle2: Figure { // Required Initializer
    var width: Double = 12
    var height: Double = 34
    init() {
        width = 0.0
        height = 0.0
        super.init(name: "unknown")
    }
    
 //   required init(name: String) { 수퍼클래스의 초기화자와 똑같은 형태로 구현. 수퍼클래스 초기화자초기화자 선언할 때 required를 앞에 붙임
//        width = 0.0
//        height = 0.0
//        super.init(name: "name")
//    } // 서브 클래스를 상속한 다른 클래스들이 다시 이 메소드(상속자)를 구현하도록 강제함.
}

Inheritance - 상속

import Foundation

class Figure {
    var name = "Unknown"
    init(name: String) {
        self.name = name
    }
    
    func draw() {
        print("draw \(name)")
    }
}

class Circle: Figure {
    var radius = 0.0
}

let c = Circle(name: "Circle") // 상속받은 sub 클래스 이기 때문에 super 클래스의 name 사용가능
c.radius

c.name
c.draw()

let a = Figure(name: "Figure")
a.name // super 클래스의 값은 변함
c.name // super클래스에서 상속받은 super클래스의 값은 변하지 않음
c.draw()

 class Rectangle: Figure { // Rectangle 클래스는 Figure 클래스를 상속
    var width = 0.0
    var height = 0.0
}

final class Impossible: Figure { // Figure 클래스를 상속 받았지만 다른 클래스를 상속하는것은 불가능 (Final)
    
}

class Square: Rectangle { // Square 클래스는 Figure 클래스를 상속받은 Rectangle 클래스를 상속
}
  • 서브클래스(하위 클래스)는 슈퍼클래스(상위 클래스)의 속성과 메서드를 상속받아 사용가능
  • 클래스 앞에 final을 붙이면 더 이상 상속 하지 않는다라는 뜻(딩크족이 된다는 뜻)

Overriding - 재정의

  • 객체지향 특성의 다형성
  • 오버라이딩을 하면 상위 클래스의 메서드를 다시 구현해서 다른 동작을 하도록 만들 수 있음
import Foundation

class Figure {
    var name = "Unknown"
    init(name: String) {
        self.name = name
    }
    
    /*final*/ func draw() { // 멤버를 final로 선언하면 오버라이딩이 금지되는거고 상속대상이 제외되는것은 아님
        print("draw \(name)")
    }
}

class Circle: Figure {
    var radius = 0.0 // 읽기 쓰기가 가능한 속성을 읽기 전용으로 오버라이드는 못함
    
    var diameter: Double { // 읽기 전용 속성
        return radius * 2
    }
    
    override func draw() { // override 키워드를 추가해서 super클래스의 멤버를 재정의
        super.draw() // super 클래스의 메소드를 실행
        print("🔴")
        super.draw()
    }
}

let c = Circle(name: "Circle")
c.draw() // 오버라이딩한 메소드를 호출. 상위 구현을 완전히 무시

final class Oval: Circle { // 읽기 쓰기가 가능한 속성을 읽기 전용으로 오버라이드
// override var radius = 0.0 속성은 이런 방식으로 오버라이딩 하면안됨. 계산속성이나 프로퍼티 옵저버를 추가하는 방식으로 오버라이드 해야함.
    
    // 읽기, 쓰기 -> 읽기(x), 읽기, 쓰기 -> 읽기, 쓰기(o)
    // 읽기 -> 읽기(x), 읽기 -> 읽기, 쓰기(o)
    
    override var radius: Double { // 계산속성. 읽기 쓰기가 가능한 속성은 읽기와 쓰기가 모두 가능해야함.(읽기전용으로 오버라이드 불가능)
        get{
        return super.radius // 읽기
    }
        set {
            super.radius = newValue // 쓰기
        }
    }
    
    //읽기 전용으로 선언한 속성을 읽기와 쓰기와 가능한 속성으로 오버라이딩 (계산속성으로 오버라이드 할 때만 가능
    override var diameter: Double {
        get {
            return super.diameter // 읽기. super을 self 바꾸면 안됨. 동일한 속성이 반복적으로 호출됨.
        }
        set {
            super.radius = newValue // 쓰기.
        }
    }
}

// 프로퍼티 옵저버 오버라이드. 저장속성에만 사용가능(값이 바뀌면 실행)
class Oval2: Circle {
    override var radius: Double {
        willSet { // 속성의 값이 변경되기 직전 실행
            print(newValue)
        }
        didSet { // 속성의 값이 변경된 직후 실행
            print(oldValue)
        }
    }
    
//    override var diameter: Double {  읽기 전용 계산속성은 감시 할 수 없음. 값이 바뀌지 않는데 값이 바뀌는것을 감시할 수 없음.
//        willSet {
//            print(newValue)
//        }
//        didSet {
//            print(oldValue)
//        }
//    }
}

let o = Oval(name: "Oval")
o.radius
o.radius = 12
o.draw()

Protocols - 규약(특정 기능을 반드시 구현하도록 강제)

import Foundation

protocol Something {
    func doSomething()
}

struct Size: Something { // 프로토콜을 선언하면 프로토콜을 채용한다고 명시적으로 표시.
    func doSomething() { // 프로토콜에 있는 메소드를 구현해야함.
    }
}

protocol SomethingObject: AnyObject, Something { // Something 프로토콜을 채용하는 SomethiingObject 프로토콜
    
}

//struct Value: SomethingObject { // 클래스 전용 프로토콜이라 채용불가(Anyobject)
//
//}

class Object: SomethingObject { // Object 클래스가 SomethingObject 프로토콜을 채용하고, 요구사항을 충족
    // Something 프로토콜을 채용하는 SomethiingObject 프로토콜을 채용했으므로, Something 프로토콜의 메소드를 작성해야함.
    func doSomething() {
    }
}

프로토콜을 왜 써야하는지?

  • 여러 타입에 필수 기능을 정의 ex) 같은 이름의 메서드를 사용 하고 싶을 때, 프로토콜로 메서드만 정의해 놓으면 됨
  • 구조체,열거형에서 다형성을 구현할 수 있음
  • 확장과 유지보수의 용이성
  • 구조체는 상속이 안 되므로, 프로토콜로 공통 기능을 설계해야 함

POP(Protocol-Oriented Programming)

  • Apple은 2015년 9월, WWDC에서 Swift 2.0을 발표하면서 Swift는 프로토콜 지향 언어(Protocol-Oriented Language)라고 발표

상속보다는 프로토콜이 좋음(중요)

  • 프로토콜은 상속과 다르게 다중으로 채택해서 사용가능
  • 상속은 클래스만 사용 가능하지만, 프로토콜은 클래스, 구조체, 열거형 모두 사용 가능
  • 상속은 상위클래스에 강하게 의존하지만, 프로토콜은 결합 구조가 느슨함
  • 기능 변경시 상속은 전체 구조에 영향을 많이줌

MethodRequirements -  프로토콜에서 “이 메서드는 반드시 구현해야 한다”고 요구

import Foundation

    protocol Resettable {
 /*mutating 선언시*/  func reset() // 바디는 작성하지 않아도됨
    }
    
   /*struct*/ class Size: Resettable {
        var width = 0.0
        var height = 0.0
        //class면 mutating 안붙여도됨. 왜? class는 참조
       //struct면 mutating 붙여야함. 왜? 구조체는 값
        func reset() {
            width = 0.0
            height = 0.0
        }
    }
// static을 선언하면 메소드 앞에 static선언. 하지만 overriding 불가.
// 메소드앞에 static 대신 class로 선언하면 overriding 가능.

PropertyRequirements - 프로토콜에서 요구하는 프로퍼티 조건

import Foundation

protocol Figure {
   static var name: String {get set} //이 프로토콜을 따르는 타입은 name을 읽기/쓰기 가능하게 만들어야 함
}

struct Rectangle: Figure {
    nonisolated(unsafe) static  /*let //읽기전용만 있으면 let사용 가능*/ var name = "Rect" // var는 읽기 쓰기 모두 가능
}

struct Triangle: Figure {
    nonisolated(unsafe) static var name = "Triangle" // 읽기 쓰기 모두 있어서 변수로 선언해야함
}

class Circle: Figure {
 static   var name: String {
        get{
            return "Circle"
        }
     set { // 쓰기도 추가해야함
         
     }
    }
}
  • struct - 프로토콜이 읽기 쓰기를 요구하면 var를 사용해야함
  • class - 프로토콜이 읽기 쓰기를 요구하면 set도 꼭 구현해야 함

Extension - 확장

import Foundation

struct Size {
    var width = 0.0
    var height = 0.0
}

extension Size { // extention으로 구조체 속성에 area 추가.(기존 타입(struct)에 기능을 추가)
    var area: Double {
        return width * height
    }
}

extension Size: Equatable { // 프로토콜 구조를 추가 할 수 있음. Size연산자를 비교연산자로 비교할 수 있음.
    public static func == (lhs: Size, rhs: Size) -> Bool {
        return lhs.width == rhs.width && lhs.height == rhs.height
    }
}

let a = Size(width: 100, height: 100)
let b = Size(width: 100, height: 100)

if a == b {
    print("같음")
}

let s = Size()
s.width
s.height
s.area

extension String { // 이런식으로 기본적인 속성에 대해서도 기능을 추가할 수 있음
    var isNotEmpty: Bool {
        return !self.isEmpty
    }
}

let str = "hello"

if str.isNotEmpty {
    print("not empty")
}
  • 저장속성은 확장할 수 없음
  • 기존 메서드를 덮어쓸 수 없음

TypeCasting - 형변환

import Foundation

let num = 123
num is Int // 타입 체크는 런타임에 실행 (true)
num is Double //(false)
num is String // (false)

class Figure {
    let name: String
    init(name: String) {
        self.name = name
    }
    
    func draw() {
        print("draw \(name)")
    }
}
class Triangle: Figure {
    
}

class Rectangle: Figure {
   var width = 0.0
   var height = 0.0
    
    override func draw() {
        super.draw()
        print("⬛️ \(width) x \(height)")
    }
}
class Circle: Figure {
    var radius = 0.0
}
class Square: Rectangle {
    
}
let t = Triangle(name: "triangle")
let r = Rectangle(name: "Rectangle")
let s = Square(name: "Square")
let f = Figure(name: "Figure")
let c = Circle(name: "Circle")

r is Figure // Rectangle은 Figure을 상속받았기 때문에 true
s is Figure // Square은 Figure을 상속받은 Rectangle을 상속받았기 때문에 true
r is Square // false

if let square = r as? Figure {
    print("Figure")
} else {
    print("Not Figure")
}

var upcasted: Figure = s
upcasted = s as Figure // 이렇게 as를 사용해서 업캐스팅 할 수 있음
// 업캐스팅은 항상 안전하기 때문에 옵셔널바인딩을 하지 않아도 됨

upcasted as? Square
upcasted as! Square
upcasted as? Rectangle
upcasted as! Rectangle

let list = [r, s, t, c] // 리스트의 타입은 가장 슈퍼타입인 Figure

for item in list {
    item.draw() // 다형성. 업캐스팅한 인스턴스를 통해서 메소드를 호출하더라도 실제 타입에서 오버라이딩한 메소드가 호출
    if let c = item as? Circle { // Circle로 다운캐스팅 한 뒤에 성공한 경우에만 실행 및 접근
        c.radius // radius 속성이 선언된 인스턴스를 접근해야만 radius 속성에 접근가능함.
    }
}
  • 업 캐스팅 - 하위 → 상위 타입 변환(as)
  • 다운 캐스팅 - 상위 → 하위 타입 변환(as?,as!)

0개의 댓글