휴 드뎌 복습 컨텐츠 마지막 날이네요
이걸로 기초복습을 마무리하고
유데미 안젤라쌤 강의로 넘어가려고 합니다.
오늘은 분량이 좀 적으려나...
개강하고 학교에 시간이 많이 들어가니 걱정되네요 허허
오늘도 화이팅 해봅시다!
구조체와 클래스는 그들만의 상수와 변수를 가지죠 이것을 프로퍼티라고 합니다.
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()
그리고 메서드에서 프로퍼티를 사용할 수 있는데요
같은 객체에 있는 값을 자동적으로 가져와 사용합니다.
스위프트에서는 프로퍼티의 변화 전과 후에 코드를 실행 시키는 방법이 있다고 했죠
이 방법은 유저인터페이스에서 값의 변화를 업데이트하기에 좋은 방법입니다.
두 가지 방법이 있는데 willSet
과 didSet
입니다.
willSet
은 newValue
을 제공합니다 새로운 프로퍼티가 될 값을 저장합니다
didSet
은 oldValue
를 제공하는데요 이전의 값을 나타냅니다.
위의 함수에 이것을 적용해서 다시 작성해보죠
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"
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는 static
키워드를 붙여서 작성할 수 있습니다
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은 클래스나 구조체의 데이터 중에서 어떤 것이 외부에 노출되도록 할것인지 정하는 것이다
대부분의 경우에는 이것들을 지정하지 않아도 되지만 가끔 다른 사람들이 프로퍼티에 직접 접근하는 것을 막기위해 사용한다.
이것이 유용한 이유는 사용자의 메서드는 작동하지만 다른 사람들의 것은 안되기 때문
private
프로퍼티는 아래와 같이 작성한다
class TaylorFan {
private var name: String?
}
접근이 안되는것을 확인할 수 있다.
클래스는 상속이 가능하기 때문에 한 클래스가 다른 클래스의 부모가 됩니다.
때문에 상속받는 클래스는 부모클래스를 그대로 사용할 수도 혹은 몇가지를 추가해서 사용할 수도 있죠
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
하여 사용합니다.
한가지 타입의 객체를 다른 것으로 바꾸어 주는것을 타입캐스팅이라고 합니다.
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)
}
타입캐스팅은 스위프트가 모르는 것을 사용자가 알 때 더 유용합니다.
내가 사용하는 A 객체를 스위프트가 B라고 생각하는 경우 말이죠
한가지 주의할 점은 관계없는 타입을 전환할수는 없다는 겁니다.
예시를 보면 Int
를 가지고 있을 때 그것을 아래처럼 그냥 String
으로 바꿀 수 없습니다.
let number = 5
let text = number as! String
String
과 Int
는 완전히 다른 타입이기 때문에 변환을 강제할 수 없습니다.
원래 값을 바꾸는게 아닌 새로운 값을 만들어서 변환해줄수는 있습니다.
let number = 5
let text = String(number)
print(text)
이건 스위프트에 내재된 데이터타입에서만 사용할 수 있습니다.
클로저는 코드를 가지고 있는 변수라 생각할 수 있어요. Int
변수가 5나 40같은 값을 가지듯이 클로저는 스위프트 코드를 가집니다
SwiftUI에서 가져온 예시를 볼까요
let message = "Button pressed"
Button("Press Me", action: {
print(message)
})
이 코드에서는 2가지 인자가 필요하죠. 버튼의 이름과 버튼이 눌렸을 때 출력되는 메시지죠. 첫번째 인자는 "Press Me"로 해주고 두번째 인자는 message
라는 문자열을 선언해주었습니다.
여기서 특징으로는 message
상수는 클로저 밖에서 선언되었지만 클로저 내부에서 사용되고 있습니다.
클로저는 매우 자주 사용되기 때문에 코드를 좀 더 쉽게 읽을 수 있는 문법이 제공됩니다. 메서드의 마지막 인자가 클로저라면 아래와 같이 작성할 수 있습니다.
let message = "Button pressed"
Button("Press Me") {
print(message)
}
인자를 없애고 바로 {
괄호를 써서 코드를 작성했죠.
이런 방식은 코드를 더 짧고 읽기 쉽게 해줍니다. 그래서 더 선호되는 방식이죠.