티스토리 뷰

반응형

안녕하세요 :) Zedd입니다. 

오늘은 Property에 대해서 배워볼거에요!!!!

Swift의 프로퍼티에는 종류가 있답니다. 

글을 쓰면서 제가 프로퍼티 프로퍼티 그러는데, 딱 이건 무슨 프로퍼티다!!!라고 확실하게 적고싶어서...ㅎㅎ

그래서 정리해보려고 합니다.

역시나 Swift를 모를땐 Swift 문서 가이드를 봐야겠죠?

이 글도 <The Swift Programming Language (Swift 4) - Properties>를 번역하는 수준이 될 것 같네요. 

시작할게요! Property는 한글로 "프로퍼티"라고 언급할게요 :)




Properties




"프로퍼티"는 을 특정 클래스(class), 구조체(struct), 열거형(enum)과 연결합니다. 


먼저 Swift의 프로퍼티에는 크게 세가지가 있어요.


Stored Property(저장 프로퍼티)

Computed Property(연산 프로퍼티)

Type Property(타입 프로퍼티)




Stored Property(저장 프로퍼티)는 상수(constant)와 변수(variable)값을 인스턴스의 일부로 저장해요. 클래스와 구조체에서만 사용됩니다 :)

반면에 Computed Property(연산 프로퍼티)는 값을 연산합니다.(저장(store)하기 보다는.) 그때그때 특정 연산은 수행하여 값을 반환하죠. 클래스, 구조체 그리고 열거형에서 사용됩니다. 


이렇게 Stored Property(저장 프로퍼티)와  Computed Property(연산 프로퍼티)는 일반적으로 특정 타입의 인스턴스와 연결됩니다.

그러나 로퍼티를 타입 자체와 연결할 수도 있어요. 이러한 프로퍼티를 Type Property(타입 프로퍼티)라고 합니다. 


먼저, Stored Property(저장 프로퍼티)부터 볼게요. 이제부터 "저장 프로퍼티"라고만 언급할게요 :)



Stored Property(저장 프로퍼티)



저장프로퍼티는 간단하게 생각하면 돼요!

자, 위에서 저장프로퍼티는 클래스와 구조체에서만 사용될 수 있다고 말했죠?


그냥 클래스와 구조체의 인스턴스의 일부가 되는 상수, 변수에요.

var로 선언하면 "변수"를 저장하는 걸테고,

let으로 선언하면 "상수"를 저장하는 거겠죠?

이렇게 변수를 저장하는 저장 프로퍼티를 변수 저장 프로퍼티라고 합니다. 

상수를 저장하는 저장프로퍼티는 상수 저장 프로퍼티겠죠? 


또한, 저장프로퍼티를 선언할 때, 저장할 "기본값"을 줄 수 있어요. 또한, 이 값을 수정할 수도 있구요!

Apple 예제를 한번 볼까요?



struct FixedLengthRange {

    var firstValue: Int

    let length: Int

}


var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)


rangeOfThreeItems.firstValue = 6

rangeOfThreeItems.length = 10//error!

자! 저장프로퍼티는 클래스와 구조체에서 사용된다고 그랬는데, 예제는 구조체네요!

바로 저 FixedLengthRange라는 구조체안에 들어있는 fisrtValue와 length가 "저장 프로퍼티"에요!


var로 선언된 firstValue는 변수 저장 프로퍼티겠고, 

let으로 선언된 lengths는 상수 저장 프로퍼티네요.

그리고 초기값은 준것 같지 않죠? 


그리고 우리는 rangeOfThreeItems라는 FixedLengthRange의 인스턴스를 하나 만들고,

(var로 만들었다는 것을 기억해주세요 XD)

(구조체는 기본적으로 저장프로퍼티들을 파라미터로 가지는 이니셜라이저가 있죠?, 만약 초깃값을 줬다면 FixedLengthRange()로 선언해도 됐을거에요.)


 firstValue에는 0을, length에는 3을 줬어요. 

그리고 이제 rangeOfThreeItems는 firstValue와 length를 모두 갖게되니, firstValue를 6으로 만들어줘봤어요. FixedLengthRange구조체에서 firstValue는 변수 저장 프로퍼티로 선언되었으므로 값이 변할 수 있겠죠?


하지만, length는 let. "상수"로 선언되었기 때문에, rangeOfThreeItems통해 length를 바꿀려고 하면 에러가 나게 된답니다. 



자, 여기까진 이해가 가셨나요? 

우리 위에서

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)


var로 rangeOfThreeItems라는 인스턴스를 만들었어요.

하지만 이 rangeOfThreeItems을 let으로 만들면 조금 달라지는게 있답니다. 


위 예제에서 rangeOfThreeItems의 var를 let으로만 바꿔볼까요? 


struct FixedLengthRange {

    var firstValue: Int

    let length: Int

}


let rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)


rangeOfThreeItems.firstValue = 6//error!

rangeOfThreeItems.length = 10//error!

그러면!!! 아까는 잘되면 firstValue를 바꿔주는 것 까지 안되게 됩니다. 

length는 상수저장프로퍼티라서 안됐지만, 지금은 FixedLengthRange의 length를 var로 변경해도 둘다 오류가 날거에요.


왜냐!!구조체는 Value type이기 때문이에요. <Swift 기초문법(Class/struct/enum)> 글에서 배웠었죠? 아직 Swift 구조체 특징을 잘 모르신다면, 읽고오시는 것을 추천해드릴게요 :)


구조체의 인스턴스가 var로 선언되면 해당 구조체의 프로퍼티들을 변경할 수 있지만(변수 저장 프로퍼티만이겠죠?) let으로 선언되면 모든 프로퍼티가 let으로 선언된 것 같이 됩니다. 


이제 왜 firstValue가 안바뀌는지 아시겠나요? 



자, 우리는 지금까지 구조체만 봤는데, 구조체는 위에서 말했다시피 Value type이었죠? 그럼 Reference type인 클래스에서는 어떻게 될까요? 

위 FixedLengthRange를 구조체말고 이번에는 클래스로 바꿔봅시다. 

저장 프로퍼티들에게 초기값이 없다면, 클래스는 init이 반드시 필요하죠? init도 빼먹지 말고 만들어줍시다 :)




class FixedLengthRange {

    var firstValue: Int

    let length: Int


    init(firstValue : Int, length:Int) {

        self.firstValue = firstValue

        self.length = length

    }

}


var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)


rangeOfThreeItems.firstValue = 3

rangeOfThreeItems.length = 10//error!

이번에도 처음에는 rangeOfThreeItems를 var로 선언했어요. 

Reference type인 클래스도 let으로 선언된 length의 값을 바꾸진 못하네요.

이건 당연하겠죠? 아예 length라는 저장프로퍼티가 정의될 때 변하지 않는 값. "상수"라고 정의되었으니까요.


그럼 이번에도 rangeOfThreeItems를 let으로 선언해봅시다. 

class FixedLengthRange {

    var firstValue: Int

    let length: Int


    init(firstValue : Int, length:Int) {

        self.firstValue = firstValue

        self.length = length

    }

}


let rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)


rangeOfThreeItems.firstValue = 3

rangeOfThreeItems.length = 10//error!

위에 있는 예제와 딱 하나 달라졌어요. var가 let으로 바뀌었다는 것...

위 구조체에서는 rangeOfThreeItems를 let으로 바꾸자마자 rangeOfThreeItems의 firstValue를 바꾸는 코드가 에러가 났었는데, 클래스는 에러가 나질 않네요!


왜냐하면 클래스는 Reference type이기 때문에, 원본에 바로 접근하기 때문이죠. 

원본의 firstValue는 var였기 때문에 여전히 값의 변경이 가능하지만, 

원본의 length는 let으로 선언되어있기때문에 값을 바꾸지 못하는거에요. 


이제 구조체와 클래스간의 저장프로퍼티들의 특징?을 조금 아시겠나요? ㅎㅎ


지금까지 "저장프로퍼티"에 대해서 알아봤는데, 연산 프로퍼티로 넘어가기 전에 저장프로퍼티를 조금 더 볼게요. 저장프로퍼티긴 한데 "게으른 저장 프로퍼티"라는 게 있습니다. 



 Lazy Stored Properties(게으른 저장 프로퍼티)


게으르다..?저장프로퍼티긴한데..게으르다?...

도대체 어떻게하면 게으른 저장 프로퍼티가 될 수 있는지 알아봅시다. 


게으른 저장 프로퍼티는 한마디로 말해서, 값이 사용되기 전까지는 값이 계산되지 않는 프로퍼티에요!

lazy라는 키워드를 사용하여 선언한답니다. 


앟ㅎㅎ모르겠어ㅎ

이럴땐 예제를 하나 같이 봅시다. 

class DataImporter {

    //DataImporter클래스는 외부파일에서 데이터를 가져오는 클래스!

    var filename = "data.txt"

    //데이터를 가져올 외부파일이름은 "data.txt"인가봐요 :)

}


class DataManager {

    lazy var importer = DataImporter()

    var data = [String]()

    //DataManager클래스는 데이터 관리하는 클래스에요. importer 위에서 선언한 DataImporter 인스턴스이며, lazy 선언되었네요.

}


let manager = DataManager()//DataManager 인스턴스를 만듭니다. 참고로, DataManager 저장프로퍼티들은 초기값이 있으므로 init 없어도 됩니다.

manager.data.append("Some data")//DataManager 저장프로퍼티 data라는 배열에 어떤 데이터를 집어넣고,

manager.data.append("Some more data")// 집어넣어요.

//하지만 아직까지 DataImporter 인스턴스인 importer프로퍼티는 생성되지 않았습니다. lazy 저장프로퍼티이기 때문이죠.

자, 주석으로 좀  설명을 적어놨는데..좀 이해가 가시나요?

게으른 저장 프로퍼티는 초기값이 인스턴스의 초기화가 될때까지 값을 모르는 외부요소에 의존하는 경우에 유용해요. 또한, 초기값이 복잡하거나 계산비용이 많이 드는 설정을 필요로 할때도 유용하다고 하네요 :)



그러니까!!!importer라는 DataImporter의 인스턴스는 lazy로 선언이 되었으니 "게으른 저장 프로퍼티"에요.

하지만 밖에서 manager라는 DataManager의 인스턴스를 이용해 importer에 한번도 접근을 안했죠? 

원래같으면(lazy로 선언을 안했다면) importer가 생성이 되었겠지만, 

lazy로 선언되었으니 사용되기 전까지 생성하지 않아요. 

importer프로퍼티에 처음 액세스 할 때 만들어지게 됩니다. 

print(manager.importer.filename)

이렇게 해야 비로소!! importer라는 DataImporter인스턴스가 생성됩니다. 

그런데 왜 importer라는 프로퍼티를 게으른(lazy) 저장 프로퍼티로 만들었을까요?


DataManager인스턴스인 manager는 파일에서 데이터를 가져오지 않고도 데이터를 관리할 수 있죠?

그러니까 DataManager 인스턴스를 만들 때 (manager를 만들 때) DataImporter인스턴스를 만들 필요가 없어요.

대신 DataImporter인스턴스를 처음 사용하게 될 때 생성하는 것이 더 합리적이다는 것이죠. 



뭔가 좀 감이 오시나요?

이 게으른 저장 프로퍼티를 잘만 사용하면 성능도 올라가고, 공간낭비도 줄일 수 있다고 해요 :)

자! 게으른 저장 프로퍼티에 대한 감을 잡으셨으면, 게으른 저장 프로퍼티의 특징을 조금 알려드릴게요.




첫번째로, 반드시 lazy 프로퍼티는 항상 변수로서 선언해야 합니다. 즉, var로 선언해야한다는것!!

위에서도 lazy var로 선언했죠?

왜냐하면, 초기값은 인스턴스 초기값이 검색되지 않을 수 있기 때문이에요. 


우리 저~기 위에서 let으로 선언한 프로퍼티는 인스턴스를 만들 때 빼고, 값을 변경 할 수 없었죠?

이렇게 let으로 선언한 프로퍼티는 초기화를 함과 동시에 값을 가져야하기 때문에, 게으른 저장 프로퍼티로 선언할 수 없어요.

게으른 저장 프로퍼티는 "값이 필요할 때" 초기화를 하니까요.


두번째는, lazy로 선언했다고 해도 lazy 프로퍼티가 초기화 되지 않은 상태에서!! 여러 쓰레드가 동시에 이 lazy프로퍼티에 액세스 한다면, 이 프로퍼티가 단 한번만 초기화 된다는 것을 보장할 수 없습니다. 




원래 Computed Property도 같이 쓰려고 했는데!! 아무리 생각해봐도 따로 쓰는게 나을 것 같아서요 :)

할 말도 많고....ㅎㅎ그래서 나눠쓰려고 해요!!

아무튼! 이 글이 저장프로퍼티를 이해하는데 도움이 되었으면 좋겠네요 :)

벌써 금요일이에요! 연휴가 끝나가고 있습니다 ㅠㅠ 다들 맛있는거 많이 드시고 남은 연휴 즐겁게 보내시길 바랄게요 XD

저장프로퍼티에 관한 궁금한점이나 지적할 점 등은 댓글이나 PC화면 오른쪽 하단에 있는 채널서비스를 이용해주세요 :)

그럼 안녕~



 





반응형

'Swift' 카테고리의 다른 글

Swift ) Properties - Property Observers(프로퍼티 옵저버)  (1) 2017.10.09
Swift ) Properties - Computed Property(연산 프로퍼티)  (7) 2017.10.07
Swift ) tuple  (3) 2017.10.02
Swift ) Types  (0) 2017.10.01
Swift ) split에 대한 고찰  (1) 2017.09.27