[Swift 문법] - 프로퍼티, 공유 프로퍼티, 접근 제어, 다형성, 클로져

sun02·2021년 8월 31일
0

100 days of Swift 

목록 보기
16/40

1. 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()

프로퍼티를 메서드 안에서 사용하면 자동으로 같은 object에 속한 값을 사용한다

Property observers

프로퍼티가 변경되려하거나 변경되었을 때 실행할 코드를 추가할 수 있다. 이것은 예를 들어 사용자의 인터페이스를 업데이트하는 좋은 방법이다

프로퍼티 옵저버에는 willSet 과 didSet이 있고 이 들은 각각 변경 전과 후에 호출된다. willSet에서 프로퍼티가 변경될 값은 newValue 라 불리고, didSet에서 이전 값은 oldValue라 불린다.

Person 구조체에서 clothes 프로퍼티에 두 가지 프로터피 옵저버를 넣어 보자:


struct Person {
    var clothes: String {
	willSet {
	    updateUI(msg: “I’m changing from \(clothes) to \(newValue)“)
	didSet {
	    updateUI(msg: “I just changed from \(oldValue) to \(clothes)”)
	)
    }
}

from updateUI(msg: String) {
    print(msg)
}
var taylor = Person(clothes: “T-shirts”)
taylor.clothes = “short skirts”

이 코드는 “I’m changing from “T-shirts to short skirts” 와 “I just changed from T-shirts to short skirts” 를 출력한다.

Computed properties

뒤에서 코드 되는 프로퍼티를 만드는 것은 가능하다. 예를 들어 우리는 스트링의 uppercased() 메서드를 이미 사용했지만 필요할 때 계산되는 capitalized 라는 프로퍼티도 있었다.

연산 프로퍼티를 만들기 위해서 프로퍼티 뒤에 여를 중괄호를 적고 get 이나 set을 사용하여 적절할 시간에 작동하도록 한다. 예를 들어, 사람 나이의 7배를 자동으로 반환하는 addInDogYears 프로퍼티를 추가하고 싶다면 다음과 같이 한다:


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
}

2. Static properties and methods

스위프트에서는 인스턴스의 타입이 아닌 타입의 프로퍼티와 메서드를 생성할 수 있다. 이것은 공유 데이터를 저장하여 데이터를 의미있게 구성하는 데 유용하다.

이러한 공유 프로퍼티는 “static properties”라 불리고 static 키워드를 사용하여 만들 수 있다. 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.favorite)

따라서 테일러 스위프트의 팬은 자신의 이름과 나이를 가지지만, 좋아하는 노래는 모두 같다

정적 메서드는 해당 구조체의 인스턴스가 아니라 구조체 자체에 속하기 때문에 구조체의 비-정적 프로퍼티에 접근하는데 사용할 수 없다.

3. Access control

접근 제어는 구조체와 클래스 안의 어떤 데이터가 바깥에 노출될 것인지 정할 수 있게 하고 다음의 네 가지 수정자를 선택할 수 있다.

+Public : 모든 사람이 이 프로퍼티를 읽고 적을 수 있다.
+Internal : 너의 스위프트 코드에서만 이 프로퍼티를 읽고 적을 수 있다. 프레임워크로 코드를 제공하면 다른 사람들은 해당 프로퍼티를 읽을 수 없다.
+File Private : 해당 유형과 같은 파일에 있는 스위프트 코드만 이 프로퍼티를 읽고 쓸 수 있다.
+Private : 가장 제한적인 옵션으로, 타입이나 확장 내의 메서드 내에서만 이 프로퍼티를 사용가능하다.

대부분은 접근 제어를 지정할 필요가 없지만 가끔 남들이 접근하는 것을 막기 위해 프로퍼티를 명시적으로 private 로 설정하려는 경우가 있다. 이것은 너의 메서드는 해당 프로퍼티와 작업할 수 있지만 다른 사람들은 그렇게 할 수 없기 때문에 유용하다.

프로퍼티를 private 으로 선언하기 위해, 다음과 같이 실행해라:


class TaylorFan {
    private var name: String?
}

4. Polymorphism and typecasting

클래스는 서로 상속할 수 있기 때문에 이는 한 클래스가 다른 클래스의 사실상 상위 집합임을 의미한다 : B 클래스는 A가 가진 모든 것을 갖고 있고 몇 가지 추가 기능이 있다. 따라서 이것은 네가 필요에 따라 B를 B유형 또는 A유형으로 다룰 수 있다는 것을 의미한다


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)
    }
}

StudioAlbum과 LiveAlbum은 Album을 상속받는다. LiveAlbum의 모든 인스턴스는 Album에서 상속되기 때문에 Album 또는 LiveAlbum으로 취급될 수 이따 – 동시에. 이것은 “Polymorphism(다형성)” 이라 하고 다음은 코드를 작성할 수 있다:


var taylorSwift = StudioAlbum(name: “Taylor Swift”, studio: “The Castle 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]

다형성이 어떻게 작동하는지 보여주기 위해 한 단계 더 가보자, 세 클래스 모두에 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”
    }
}

getPerformance() 메서드는 Album 클래스에 존재하지만, 자식 클래스들이 override 하였다. 앨범들을 담은 배열을 만들었을 때, 우리는 그 배열을 앨범의 하위클래스(LiveAlbum, StudioAlbum)들로 채웠었다. 하위클래스들이 Album 클래스를 상속받았기 때문에 배열에 넣을 수 있었지만 그들의 본래 클래스를 잃지는 않는다. 따라서 다음과 같이 코드를 작성할 수 있다:


var taylorSwift = StudioAlbum(name: “Taylor Swift”, studio: “The Castle 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]

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

해당 하위 클래스에 따라 gerPerformace()의 재정의 버전이 자동으로 사용된다. 이것이 다형성이다 : object는 클래스와 부모 클래스로 동시에 작동할 수 있다.

Converting types with typecasting

타입캐스팅은 한 가지 타입의 object를 다른 유형으로 바꾸는 것이다.

이것이 왜 필요한지 생각하기 위해 고민할 수 있지만 간단한 예시를 들어 보자:


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

allAlbums는 Album 타입을 가지지만 이것은 실제로는 그 하위클래스를 갖고 있다. 스위프트는 그것을 모르기 때문에 네가 print(album.studio) 와 같이 작성한다면 StudioAlbum만 studio 프로퍼티를 가지기 때문에 이것은 빌드되지 않는다.

타입캐스팅은 세 가지 형태로 제공되지만 대부분의 경우 너는 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)
    }
}

이것은 모든 album을 살펴보고 album의 세부 Performance를 출력한다 왜냐하면 performance는 Album 클래스와 모든 album클래스의 하위 클래스에 공통적이기 대문이다. 그런 다음 album 값을 studioAlbum 으로 변환할 수 있는지 확인하고 변환할 수 있다면 studio name을 출력한다. 배열의 liveAlbum에 대해서도 동일한 작업이 수행된다.

강제 다운캐스팅은 한 타입의 오브젝트가 다른 타입처럼 다뤄질 수 있다고 네가 확신할 때 사용한다. 하지만 만약 네가 틀렸다면 프로그램은 충돌할 것이다. 강제 다운캐스팅은 옵셔널 값을 반환할 필요는 없다 왜냐하면 변환이 확실히 작동할 것이라고 네가 말하고 있기 때문이다. – 만약 틀렸다면 네가 코드를 잘못 작성한 것이다.

이를 증명하기 위해 live album을 제거하여 배열에 studio album만 있도록 해보자.


var taylorSwift = StudioAlbume(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)
}

이거는 분명히 인위적인 예시이다, 왜냐하면 이것이 정말 너의 코드라면 너는 그냥 allAlbumes가 아닌 [StudioAlbum]을 데이터 타입을 가지도록 수정할 것이기 떄문이다. 그래도 이것은 강제 다운캐스팅이 어떻게 작동하는지 보여준다 그리고 이 예시에서는 올바른 가정을 하기 때문에 충돌이 발생하지 않는다.

스위프트를 사용하면 배열 루프의 일부로 다운캐스트 할 수 있고 이 경우 더 효율적이다. 만약 배열에서 강제 다운캐스트를 작성하고 싶다면 다음과 같이 작성한다:

for album in allAlbums as! [StudioAlbum] {
	print(album.studio)
}

다운캐스트가 루프가 시작될 때 발생하기 때문에 루프 안에서 모든 항목들을 다운캐스트 해 줄 필요가 없다. 다시 말하지만, 배열 안의 모든 항목이 studioAlbum이도록 수정하는게 좋다, 그렇지않으면 코드는 충돌할 것이다.

스위프트는 배열에서 옵셔널 다운캐스팅도 허용한다, 비록 루프에 대한 값이 항상 존재하는지 보장하기 위해 nil 병합 연산자를 사용해야하는게 조금 까다롭긴하지만. 여기 예시가 있다:

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

이 것은 “allAlbums를 LiveAlbum 배열로 변환해라, 만약 실패한다면 live album으로 된 빈 배열을 만들어 대신 사용해라” 라는 의미이다.

Converting common types with initializers

타입캐스팅은 스위프트가 모르는 것을 네가 알고 있을 때 유용하다. 예를 들어 스위프트가 실제로 type B라고 생각하는 type A의 오브젝트가 있는 경우이다. 그러나, 타입캐스팅은 해당 유형이 실제로 사용자가 말하는 것과 같을 때만 유용하다 – 실제로 관련이 없는 경우 type A를 type Z로 강제할 수 없다.

예를 들어, number라 불리는 정수를 갖고 있을 때 너는 이것을 다음과 같이 문자열로 바꿀 수 없다


let number = 5
let text = number as! String

즉, 문자열과 정수형은 완전히 다른 유형이기 때문에 너는 문자열을 정수형으로 강제할 수 없다. 대신, 정수를 입력하여 새로운 문자열을 만들면 스위프트는 이 둘을 변환할 수 있다. 차이점은 미묘하다 : 이것은 동일한 값을 재해석하는 것이 아니라 새로운 값이다.

따라서, 이 코드는 다음과 같이 재작성된다:

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

이것은 스위프트의 내장 데이터 유형 중 일부에서만 작동한다: 예를 들어 너는 정수형과 실수형을 문자열로 바꾸거나 되돌릴 수 있지만 만약 사용자 정의 구조체를 만든 경우 스위프트가 자동으로 변환할 수 없다 – 스스로 코드를 작성해야한다.

5. Closures

지금까지 정수, 문자열, 실수, 배열, 구조체, 클래스 등등을 만났지만 스위프트에서 광범위하게 사용되는 또다른 유형의 데이터가 있다. 이는 클로져이다. 이것들은 복잡하지만 매우 강력하고 표현력이 뛰어나서 CoCoa Touch에서 널리 사용되므로 이것을 이해하지 않고는 멀리 갈 수 없다.

클로져는 코드를 가지는 변수로 생각할 수 있다. 따라서, 정수가 0 또는 500을 보여하는 것처럼 클로져는 스위프트 코드 라인을 보유한다. 클로져는 생성된 화면을 캡쳐한다. 즉 클로져는 내부에서 사용되는 값의 복사본을 가진다.

너만의 클로져를 설계할 필요는 없기 때문에 다음이 매우 복잡하다고 생각되더라도 두려워하지 마라. 그러나 Cocoa와 Cocoa Touch 둘 다 종종 필요에 따라 클로져를 작성하도록 요구하므로 최소한 그들이 어떻게 작동되는지는 알아야한다. Cocoa Touch 예시를 먼저 보자:


let vw = UIView()

UIView.animate(withDuration: 0.5, animations: {
	Vw.alpha = 0
})

UIView는 가장 기본적인 종류의 사용자 인터페이스 컨테이너를 나타내는 UIKit의 iOS 데이터 유형이다. 지금은 이것이 무엇을 하는건지 걱정하지 마라, 중요한 것은 기본 사용자 인터페이스 구성 요소라는 것이다. UIView에는 animate()라는 메서드가 있고 이것은 에니메이션을 사용하여 인터페이스가 표시되는 방식을 바꾼다 – 무엇이 몇 초동안 변경되는지 네가 설명하면 나머지는 Cocoa Touch가 처리한다.

Animate() 메서드는 위 코드에서 두 개의 매개변수를 사용한다. : 애니메이션을 적용할 시간과 애니메이션의 일부로 실행할 코드가 포함된 클로져이다. 첫 번째 매개변수로 0.5를 지정했고 두 번째 매개변수로 UIkit에 뷰의 알파(불투명도)를 “완전히 투명함”을 의미하는 0으로 조정하도록 요청했다.

이 메서드는 클로져를 사용해야한다 왜냐하면 UIKit이 애니메이션 시작을 준비하기 위해 모든 종류의 작업을 수행해야하기 때문이다. 따라서 UIKit이 괄호(클로져)안의 코드를 복사하여 저장하고, 모든 준비 작업을 수행한 다음, 준비가 되면 코드를 실행한다. 코드를 직접 실행하는 경우 이것은 불가능하다.

위의 코드는 클로저가 어떻게 환경을 캡쳐하는지 보여준다: 나는 클로저 밖에서 vw 상수를 선언하였고 클로저 내부에서 이것을 사용하였다. 스위프트는 이를 감지하여 해당 데이터를 클로저 내부에서도 사용할 수 있게 한다.

클로저의 환경을 자동으로 캡처하는 스위프트의 시스템은 매우 유용하지만 대때로 문제를 일으킨다. : 만약 오브젝트 A가 클로저를 프로퍼티로 저장하고 그 프로퍼티가 오브젝트 A를 참조하는 경우, 너는 강력한 참조 주기하 불리는 것을 갖게 되고 이것을 unhappy한 사용자가 될 것이다. 이것은 지금 알아야하는 것보다 훨씬 고급 주제이므로 아직은 걱정하지 않아도 된다.

Trailing closures

클로저들이 너무 자주 사용되기 때문에 스위프트는 코드를 읽기 쉽게 만들기 위해 약간의 syntactic sugar를 적용할 수 있다. 규칙은 다음과 같다: 만약 메서드의 마지막 매개변수가 클로저를 사용하는 경우 해당 매개변수를 제거하고 중괄호 안에 코드 블럭으로 제공한다. 예를 들어, 우리는 앞의 코드를 다음과 같이 바꿀 수 있다.


let vw = UIView()

UIView.animate(withDuration: 0.5) {
	vw.alpha = 0
}

이것은 네 코드를 더 짧고 읽기 쉽게 만들기 떄문에 이러한 구문 유형 – trailing closure syntax – 가 선호된다.

출처

0개의 댓글