[Swift] Day15 - Swift review, day three

한철희·2023년 3월 6일
0

100 DaysOfSwift

목록 보기
11/11

휴 드뎌 복습 컨텐츠 마지막 날이네요
이걸로 기초복습을 마무리하고
유데미 안젤라쌤 강의로 넘어가려고 합니다.
오늘은 분량이 좀 적으려나...
개강하고 학교에 시간이 많이 들어가니 걱정되네요 허허
오늘도 화이팅 해봅시다!

Properties

구조체와 클래스는 그들만의 상수와 변수를 가지죠 이것을 프로퍼티라고 합니다.

struct Person {
    var clothes: String
    var shoes: String
    
    func describe() {
        print("I like wearing \(clothes) with \(shoes)")
    }
}

let taylor = Person(clothes: "T-shirts", shoes: "sneakers")
let other = Person(clothes: "short skirts", shoes: "high heels")
taylor.describe()
other.describe()

그리고 메서드에서 프로퍼티를 사용할 수 있는데요
같은 객체에 있는 값을 자동적으로 가져와 사용합니다.

Property observers

스위프트에서는 프로퍼티의 변화 전과 후에 코드를 실행 시키는 방법이 있다고 했죠
이 방법은 유저인터페이스에서 값의 변화를 업데이트하기에 좋은 방법입니다.

두 가지 방법이 있는데 willSetdidSet입니다.
willSetnewValue을 제공합니다 새로운 프로퍼티가 될 값을 저장합니다
didSetoldValue를 제공하는데요 이전의 값을 나타냅니다.
위의 함수에 이것을 적용해서 다시 작성해보죠

struct Person {
    var clothes: String {
        willSet {
            updateUI(msg: "I'm changing from \(clothes) to \(newValue)")
        }
    }
}

func updateUI(msg: String) {
    print(msg)
}

var taylor = Person(clothes: "T-shirts")
taylor.clothes = "short skirts"

Computed properties

uppercased()라는 메소드로 문자열을 다루었죠?
프로퍼티에는 capitalized라는 존재한답니다
이런걸 연산프로퍼티라고 하는데요 get이나 set키워드를 통해 작성할 수 있습니다
아래 예시를 볼까요?

struct Person {
    var age: Int

    var ageInDogYears: Int {
        get {
            return age * 7
        }
    }
}

var fan = Person(age: 25)
print(fan.ageInDogYears)

만약 데이터를 읽는데만 사용하고 싶다면 get부분을 없애면 됩니다.

var ageInDogYears: Int {
    return age * 7
}

Static properties and methods

static propertiesstatic키워드를 붙여서 작성할 수 있습니다

struct TaylorFan {
    static var favoriteSong = "Look What You Made Me Do"

    var name: String
    var age: Int
}

let fan = TaylorFan(name: "James", age: 25)
print(TaylorFan.favoriteSong)


사용할 때는 풀네임을 적어서 사용합니다

생성된 인스턴스들은 각자의 나이와 이름을 가질겁니다. 하지만 좋아하는 노래의 이름은 똑같겠죠


Access control

Access control은 클래스나 구조체의 데이터 중에서 어떤 것이 외부에 노출되도록 할것인지 정하는 것이다

  • Public: 누구나 프로퍼티를 읽고 쓸 수 있다.
  • Internal: 사용자의 코드만 프로퍼티에 접근할 수 있다. 프레임워크로 다른 사람들이 사용한다면 프로퍼티를 읽을 수 없다.
  • File Private: 같은 파일 내의 코드만 프로퍼티에 접근할 수 있다.
  • Private: 프로퍼티가 속한 메서드에서만 사용할 수 있다.

대부분의 경우에는 이것들을 지정하지 않아도 되지만 가끔 다른 사람들이 프로퍼티에 직접 접근하는 것을 막기위해 사용한다.
이것이 유용한 이유는 사용자의 메서드는 작동하지만 다른 사람들의 것은 안되기 때문

private프로퍼티는 아래와 같이 작성한다

class TaylorFan {
	private var name: String?
}


접근이 안되는것을 확인할 수 있다.


Polymorphism and typecasting

클래스는 상속이 가능하기 때문에 한 클래스가 다른 클래스의 부모가 됩니다.
때문에 상속받는 클래스는 부모클래스를 그대로 사용할 수도 혹은 몇가지를 추가해서 사용할 수도 있죠

class Album {
    var name: String

    init(name: String) {
        self.name = name
    }
}

class StudioAlbum: Album {
    var studio: String

    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }
}

class LiveAlbum: Album {
    var location: String

    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }
}

3가지 클래스를 만들었습니다. album, StudioAlbum, LiveAlbum 이네요
2개는 album클래스를 상속받구요
여기서 StudioAlbum은 album으로 다뤄질 수 있는데 이를 "polymorphism"이라고 합니다.

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")
var iTunesLive = LiveAlbum(name: "iTunes Live from SoHo", location: "New York")

var allAlbums: [Album] = [taylorSwift, fearless, iTunesLive]

album만 받는 배열을 만들고 2개의 studioalbum과
1개의 livealbum을 넣었습니다
album만 받는 배열인데 어떻게 다른게 들어갔는지 설명하자면
studioalbum과 livealbum이 album을 상속받기 때문에 똑같이 다뤄진다고 보면 됩니다.

밑에 getPerformance()메서드를 추가해서 실제로 어떻게 동작하는지 보죠

class Album {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func getPerformance() -> String {
        return "The album \(name) sold lots"
    }
}

class StudioAlbum: Album {
    var studio: String
    
    init(name: String, studio: String) {
        self.studio = studio
        super.init(name: name)
    }
    
    override func getPerformance() -> String {
        return "The studio album \(name) sold lots"
    }
}

class LiveAlbum: Album {
    var location: String
    
    init(name: String, location: String) {
        self.location = location
        super.init(name: name)
    }
    
    override func getPerformance() -> String {
        return "The live album \(name) sold lots"
    }
}

Album클래스에 이미 존재하는 getPerformance() 메서드를 다른 2개의 자식 클래스에서 override하여 사용합니다.

Converting types with typecasting

한가지 타입의 객체를 다른 것으로 바꾸어 주는것을 타입캐스팅이라고 합니다.

for album in allAlbums {
    print(album.getPerformance())
}

위에서 작성했던 코드인데요 2번째 줄을 print(album.studio)로 변경하면 에러가 뜨게됩니다
왜 그럴까요? allAlbums라는 배열은 Album 타입의 요소를 가지고 있지만 사실 서브 클래스인 studioAlbum이나 liveAlbum으로 배열이 이루어져 있죠
우리가 바꾸려는 코드는 studioAlbum 객체만 가지고 있는 프로퍼티이기 때문에 에러가 뜹니다.

타입 캐스팅을 하는데 3가지 방식이 있습니다만
대부분의 경우는 as?as!를 쓰게 될겁니다. 두개는 각각 옵셔널 다운캐스팅과 강제 다운캐스팅으로 불립니다.
전자는 전환이 될 수도 실패할수도 있다는 뜻이고 후자는 전환이 될것이고 실패할 경우 앱이 크래시가 난다는 뜻입니다.
원래의 옵셔널과 강제언래핑과 의미가 비슷합니다.

참고로 전환, 변환한다는 얘기는 스위프트가 특정 객체를 다루는 방식을 바꾼다는 뜻입니다.

예시를 보면 이해가 더 쉬울 겁니다. 옵셔널과 비슷하거든요

for album in allAlbums {
    let studioAlbum = album as? StudioAlbum
}

스위프트는 studioAlbum을 StudioAlbum?타입을 가지도록 만들겁니다.
옵셔널 값을 가지게 된다는 뜻은 nil을 반환할 수도 있다는 뜻이죠

여기서 if let이 사용됩니다.

for album in allAlbums {
    print(album.getPerformance())

    if let studioAlbum = album as? StudioAlbum {
        print(studioAlbum.studio)
    } else if let liveAlbum = album as? LiveAlbum {
        print(liveAlbum.location)
    }
}

강제 다운캐스팅은 특정 타입이 다른 타입으로 전환될 수 있다느 것을 사용자가 확신할 때 사용할 수 있습니다.

아래 예시를 보죠

var taylorSwift = StudioAlbum(name: "Taylor Swift", studio: "The Castles Studios")
var fearless = StudioAlbum(name: "Speak Now", studio: "Aimeeland Studio")

var allAlbums: [Album] = [taylorSwift, fearless]

for album in allAlbums {
    let studioAlbum = album as! StudioAlbum
    print(studioAlbum.studio)
}

반복문안에도 직접 작성할 수 있으므로 아래와 같이 작성할 수도 있습니다

for album in allAlbums as? [LiveAlbum] ?? [LiveAlbum]() {
    print(album.location)
}

Converting common types with initializers

타입캐스팅은 스위프트가 모르는 것을 사용자가 알 때 더 유용합니다.
내가 사용하는 A 객체를 스위프트가 B라고 생각하는 경우 말이죠
한가지 주의할 점은 관계없는 타입을 전환할수는 없다는 겁니다.

예시를 보면 Int를 가지고 있을 때 그것을 아래처럼 그냥 String으로 바꿀 수 없습니다.

let number = 5
let text = number as! String

StringInt는 완전히 다른 타입이기 때문에 변환을 강제할 수 없습니다.
원래 값을 바꾸는게 아닌 새로운 값을 만들어서 변환해줄수는 있습니다.

let number = 5
let text = String(number)
print(text)

이건 스위프트에 내재된 데이터타입에서만 사용할 수 있습니다.


Closures

클로저는 코드를 가지고 있는 변수라 생각할 수 있어요. Int변수가 5나 40같은 값을 가지듯이 클로저는 스위프트 코드를 가집니다

SwiftUI에서 가져온 예시를 볼까요

let message = "Button pressed"

Button("Press Me", action: {
    print(message)
})

이 코드에서는 2가지 인자가 필요하죠. 버튼의 이름과 버튼이 눌렸을 때 출력되는 메시지죠. 첫번째 인자는 "Press Me"로 해주고 두번째 인자는 message라는 문자열을 선언해주었습니다.

여기서 특징으로는 message 상수는 클로저 밖에서 선언되었지만 클로저 내부에서 사용되고 있습니다.

Trailing closures

클로저는 매우 자주 사용되기 때문에 코드를 좀 더 쉽게 읽을 수 있는 문법이 제공됩니다. 메서드의 마지막 인자가 클로저라면 아래와 같이 작성할 수 있습니다.

let message = "Button pressed"

Button("Press Me") {
    print(message)
}

인자를 없애고 바로 {괄호를 써서 코드를 작성했죠.
이런 방식은 코드를 더 짧고 읽기 쉽게 해줍니다. 그래서 더 선호되는 방식이죠.

profile
초보 개발자 살아남기

0개의 댓글