스위프트 심화2

이은호·2023년 6월 28일
0

swift

목록 보기
8/8
post-thumbnail

구조체와 클래스는 우리가 배운 개념들을 총동원하여 만들어지는 코드블록이다. 스위프트는 기본적으로 객체지향 언어인데, 객체지향의 핵심은 필요한 기능을 객체로 구현해야한다는 것이다. 그리고 객체를 만들어내는 대상이 바로 구조체와 클래스다.
구조체와 클래스는 비슷하지만 클래스만의 차이점이 있다.
상속, 타입캐스팅, 소멸화 구문, 참조에 의한 전달

개념

정의

클래스를 선언할때는 카멜케이스를 사용한다.

// 구조체 예시
struct Resolution {
    // 구조체 정의 내용이 들어갈 부분
}

// 클래스 예시
class VideoMode {
    // 클래스 정의 내용이 들어갈 부분
}

클래스를 초기화할시, 초기값을 주거나 옵셔널타입으로 선언해야한다.

구조체:복사에 의한 전달 && 클래스:참조에 의한 전달

struct Resolution {
    var width = 0
    var height = 0
    
    func desc() -> String {
        return "Resolution 구조체"
    }
}

class VideoMode {
    var interlaced = false
    var frameRate = 0.0
    var name: String?
    
    func desc() -> String {
        return "VideoMode 클래스"
    }
}

let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

cinema.width = 2048
print("cinema 인스턴스의 width 값은 \(cinema.width)입니다")
print("hd 인스턴스의 width 값은 \(hd.width)입니다")

let hd2 = VideoMode()
var cinema2 = hd2

cinema2.interlaced = false
print("cinema 인스턴스의 width 값은 \(cinema2.interlaced)입니다")
print("hd 인스턴스의 width 값은 \(hd2.interlaced)입니다")

코드를 통해 보자
만약 동일한 인스턴스인지 확인하고자 하면, ===와 !==연산자를 이용하면 되겠다.

언제 무엇을 사용해야할까

단순화해, 구조체를 사용할때가 아니면 클래스를 이용하면 된다. 구조체를 써야할때는 언제일까
1. 서로 연관된 몇개의 기본 데이터타입들을 캡슐화하여 묶는 것이 목적일 때
2. 캡슐화된 데이터에 상속이 필요하지 않을 때
3. 복사방식이 합리적일때
4. 캡슐화된 원본 데이터를 보존해야할 때
위와 같은 상황이거나 보편적인 상황으로는 구조체를 작성하는 것이 합당하겠다.

프로퍼티

속성은 두가지 종류가 있다.
* 저장 프로퍼티

  • 입력된 값을 저장하거나 저장된 값을 제공하는 역할
  • 상수 및 변수를 사용해서 정의 가능
  • 클래스와 구조체에서는 사용이 가능하나, 열거형에서는 사용할 수 없다.
    * 연산 프로퍼티
  • 특정 연산을 통해 값을 만들어 제공하는 역할
  • 변수만 사용해서 정의 가능
  • 클래스, 구조체, 열거형 모두에서 사용가능

저장프로퍼티

클래스 내에서 선언된 변수나 상수를 부르는 이름입니다.

초기값을 할당해주지 않을수도 있지만, 이 경우 옵셔널타입으로 선언해야한다.

class User {
    var name: String
    
    init() {
        self.name = ""
    }
}

// 두 번째 해결책 - 프로퍼티를 옵셔널 타입으로 바꿔줍니다
class User {
    var name: String?
}
(또는)
class User {
    var name: String!
}

// 세 번째 해결책 - 프로퍼티에 초기값을 할당해 줍니다
class User {
    var name: String = ""
}

연산프로퍼티

연산 프로퍼티는 다른 프로퍼티에 의존적이거나, 혹은 특정연산을 통해 얻을 수 있는 값을 정의할 때 사용한다. 대표적으로 나이다. 나이는 출생연도에 의존적이기 때문이다.

import Foundation

struct UserInfo {
    // 저장 프로퍼티 : 태어난 연도
    var birth: Int!
    
    // 연산 프로퍼티 : 올해가 몇년도인지 계산
    var thisYear: Int! {
        get {
            let df = DateFormatter()
            df.dateFormat = "yyyy"
            return Int(df.string(from: Date()))
        }
    }
    
    // 연산 프로퍼티 : 올해 - 태어난 연도 + 1
    var age: Int {
        get {
            return (self.thisYear - self.birth) + 1
        }
    }
}

let info = UserInfo(birth: 2000)
print(info.age)

// 실행 결과

실무에서는 읽기전용 프로퍼티를 많이 쓴다.

프로퍼티 옵저버

특정프로퍼티를 계속 관찰하다가 값이 변경되면 이를 알아차리고 반응한다.
종류로는 willSet과 didSet이 있다.

struct Job {
    var income: Int = 0 {
        willSet(newIncome) {
            print("이번 달 월급은 \(newIncome)원입니다")
        }
        
        didSet {
            if income > oldValue {
                print("월급이 \(income - oldValue)원 증가하셨네요.\n소득세가 상향조정될 예정입니다")
            } else {
                print("저런, 월급이 삭감되셨군요. 그래도 소득세는 깎아드리지 않아요. 알죠?")
            }
        }
    }
}
var job = Job(income: 1000000)
job.income = 2000000

재밌는 코드다 ㅋㅋ

타입 프로퍼티

클래스나 구조체에 값을 저장하게 되는 경우가 있다. 바로 스태틱이다.

struct Foo {
    // 타입 저장 프로퍼티
    static var sFoo = "구조체 타입 프로퍼티값"
    
    // 타입 연산 프로퍼티
    static var cFoo: Int {
        return 1
    }
}

class Boo {
    // 타입 저장 프로퍼티
    static var sFoo = "클래스 타입 프로퍼티값"
    
    // 타입 연산 프로퍼티
    static var cFoo: Int {
        return 10
    }
    
    // 재정의가 가능한 타입 연산 프로퍼티
    class var oFoo: Int {
        return 100
    }
}
var foo = Foo()
var boo = Boo()
//print(foo.sFoo)
print(Foo.sFoo)

타입프로퍼티는 인스턴스에 속하지 않으므로 위 주석과 같이 출력이 불가하다. 만약 사용하고자 하면 클래스,구조체,열거형 그 자체와 함께 사용해야한다.

메소드

인스턴스 메소드

struct Resolution {
    var width = 0
    var height = 0
    
    // 구조체의 요약된 설명을 리턴해주는 인스턴스 메서드
    func desc() -> String {
        let desc = "이 해상도는 가로 \(self.width) X \(self.height) 로 구성됩니다"
        return desc
    }
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
    
    // 클래스의 요약된 설명을 리턴해주는 인스턴스 메서드
    func desc() -> String {
        if self.name != nil {
            let desc = "이 \(self.name!) 비디오 모드는 \(self.frameRate)의 프레임 비율로 표시됩니다"
            return desc
        } else {
            let desc = "이 비디오 모드는 \(self.frameRate)의 프레임 비율로 표시됩니다"
            return desc
        }
    }
}

var s = VideoMode()
print(s.desc())
s.name = "이은호"
print(s.desc())

타입메서드

스태틱메소드다. 위의 타입프로퍼티와 동일한 속성이다. 다만 스태틱이라고 작성하지는 않는다.

class Foo {
    // 타입 메서드 선언
    class func fooTyopeMethod() {
        // 타입 메서드 구현 내용 들어갑니다
        print("Hello Swift")
    }
}

let f = Foo()

Foo.fooTyopeMethod()

상속

여기에서 클래스의 이름을 정리해보도록 하자.
상속관련해서 종류는 두가지다
부모클래스(상위클래스,슈퍼클래스,기본클래스)
자식클래스(하위클래스,서브클래스,파생클래스)

class A {
    var name = "Class A"
    
    var description: String {
        return "This class name is \(self.name)"
    }
    
    func foo() {
        print("\(self.name)'s method foo is called")
    }
}

let a = A()

a.name // "Class A"
a.description // "This class name is Class A"
a.foo()

// 실행 결과
class B: A {
    var prop = "Class B"
    
    func boo() -> String {
        return "Class B prop = \(self.prop)"
    }
}

let b = B()
b.prop // "Class B"
b.boo() // Class B prop = Class B
b.foo()
b.name // "Class A"
b.foo() // "Class A's method foo is called"

b.name = "Class C"
b.foo() // "Class C's method foo is called"

오버라이딩

상속받은 멤버들을 수정해야할 경우 오버라이딩을 한다.
오버라이딩의 경우 슈퍼클래스의 멤버들을 추상멤버라고 가정하고 작성해야한다(형식을 유지해야한다.)
그 형식이란 다음과 같다.
저장 프로퍼티를 get, set 구문이 모두 있는 연산 프로퍼티로 오버라이딩하는 것
get, set 구문이 모두 제공되는 연산 프로퍼티를 get, set 구문이 모두 제공되는 연산 프로퍼티로 오버라이딩하는 것
get 구문만 제공되는 연산 프로퍼티를 get, set 구문이 모두 제공되는 연산 프로퍼티로 오버라이딩하는 것
get 구문만 제공되는 연산 프로퍼티를 get 구문만 제공되는 연산 프로퍼티로 오버라이딩하는 것
-> 결론적으로 오버라이딩을 하기 위해서는 get과 set을 이용하게 된다.

class Vehicle {
    var currentSpeed = 0.0
    
    var description: String {
        return "시간당 \(self.currentSpeed)의 속도로 이동하고 있습니다"
    }
    
    func makeNoise() {
        // 임의의 교통수단 자체는 경적을 울리는 기능이 필요없습니다
    }
}

class Car: Vehicle {
    var gear = 0
    var engineLevel = 0
    
    override var currentSpeed: Double {
        get {
            return Double(self.engineLevel * 50)
        }
        set {
            print("하이")
        }
    }
    
    override var description: String {
        get {
            return "Car: engineLevel=\(self.engineLevel),\nso currentSpeed=\(self.currentSpeed)"
        }
        set {
            print("New Value is \(newValue)")
        }
    }
}

let c = Car()

c.engineLevel = 5
c.currentSpeed // 250
c.description = "New Class Car"

print(c.description)

타입 캐스팅

int() String()과 같이 클래스 간에도 캐스팅이 가능하다.

초기화 구문

드디어 생성자 초기화 구문이다. 외부값으로 초기화하는 방법에 대해 알아보자.

struct Resolution {
    var width = 0
    var height = 0
    
    init(width: Int) {
        self.width = width
    }
}

class VideoMode {
    var resolution = Resolution(width: 4096)
    var interlaced = false
    var frameRate = 0.0
    var name: String?
    
    // 초기화될 때 name 인자값만 받는 init 구문
    init(name: String) {
        self.name = name
    }
    // 초기화될 때 interlaced 인자값만 받는 init 구문
    init(interlaced: Bool) {
        self.interlaced = interlaced
    }
    // 초기화될 때 interlace, frameRate 두 개의 인자값을 받는 init 구문
    init(interlaced: Bool, frameRate: Double) {
        self.interlaced = interlaced
        self.frameRate = frameRate
    }
    // 초기화될 때 interlace, frameRate, name 세 개의 인자값을 받는 init 구문
    init(interlaced: Bool, frameRate: Double, name: String) {
        self.interlaced = interlaced
        self.frameRate = frameRate
        self.name = name
    }
}
let nameVideoMode = VideoMode(name: "홍길동")
let simpleVideoMode = VideoMode(interlaced: true)
let doubleVideoMode = VideoMode(interlaced: true, frameRate: 40.0)
let tripVideoMode = VideoMode(interlaced: true, frameRate: 40.0, name: "홍길동")

속이 뻥~ 만약 기본초기화를 하고 싶다면, 파라미터가 없는 init()을 하나 더 만들어주면된다.

오버라이딩생성자

오버라이딩을 통해서도 슈퍼멤버에 접근할 수 있다.

class Base {
    var baseValue: Double
    init(inputValue: Double) {
        self.baseValue = inputValue
    }
}
class ExBase: Base {
    override init(inputValue: Double) {
        super.init(inputValue: 10.5)
    }
}

옵셔널 체인

만약 클래스도 타입이므로 nil이 들어갈 수 있다. 그런데 이런 징그러운 코드가 나올 수도 있다.

struct Human {
    var name: String?
    var man: Bool = true
}

var boy: Human? = Human(name: "홍길동", man: true)

if boy != nil {
    if boy!.name != nil {
        print("이름은 \(boy!.name!)입니다")
    }
}

// 혹은

if let b = boy {
    if let name = b.name {
        print("이름은 \(name)입니다")
    }
}

print(boy?.name)
print(boy?.man)

이를 해결하는 방법이 바로 위의 옵셔널 체인이다. 그냥 이런게 있다 정도만 알아두자.

0개의 댓글