momodudu.zip

#3 Swift - Properties 본문

ios/Swift

#3 Swift - Properties

ally10 2019. 11. 26. 14:01

Property란, 특정 class, enumeration, structure에 속하는 특정 value를 의미한다.

 

Swift에서는 Stored Property, Computed Property, Type Property가 있다.

자세하게 설명하기전에 간단하게 말하자면, stored property는 constant, variable 등 말 그대로 "저장"을 위해서 사용하는 value이다.

Computed property는 value를 저장하지 않고, 연산을 하는 getter 혹은 optional setter가 있다.

Type property는 "Type"그 자체와 연관된 Property인데, 말은 어렵지만 쉽게 설명하자면 c++ class의 static 변수라고 보면 된다.

즉 Instnace마다 정의되는게 아니라, class의 타입 통틀어서 하나만 존재하는 Property를 의미한다.

 

 

 

1. Stored Property

특정 class나 structure의 instance 내부에 저장되는 value를 의미한다. c++ class의 멤버변수 정도로 보면 되겠다.

struct RectStruct{
    var width: Int = 10
    var height: Int = 10
}

class RectClass{
    var width: Int = 10
    var height: Int = 10
}

 

위 예제에서 width, height 들을 stored property 라고 부른다.

 

 

Lazy Stored Property

class RectClass{
    // Default value
    var width: Int = 10
    var height: Int = 10
    
    // initializer
    init(width: Int, height: Int){
        self.width = width
        self.height = height
    }
}

 

class의 경우 , 기본적으로 위와 같이 initialize가 필요하다.

default value로 초기화를 하거나, initializer로 초기화를 해주지 않으면 컴파일 에러가 난다.

 

그런데 클래스의 몸집이 커지다보면 initialize 자체에 과부하가 걸릴 수 있다.

그래서 system이 처음 시작할때 initlaiize 단계에서 과부하가 걸리게 되면 시스템 전체적으로 느려질 수 있다.

그래서 init이 당장 필요하지 않은 변수들에 대해서는 초기화를 늦출 수 있는데, 그게 lazy stored property 이다.

 

class DataImporter{
    
    var fileName = "data.txt"

    // ....
}

class DataManager{
    lazy var importer = DataImporter()
    var data = [String]()
}

DataImport를 포함하고 있는 DataManager class 구조이다.

예를들어 Importer에서 file read같은 동작을 하게 된다면 initialize 단계에서 file read 와 같은 system call 이 발생할 수 있다.

 

그래서 importer의 경우, 실제로 사용하는 단계에서 초기화를 하도록 위와 같이 var 앞에 lazy keyword를 붙여주면

실제로 importer에 접근할 때 초기화를 시켜준다.

 

이처럼 lazy property로 선언하려면 반드시 var 로 선언해야 한다.

 

 

 

2. Computed Property

stored property에서는 실제로 어떤 값을 저장했다면, computed property는 값을 저장하는게 아니라

어떤 값을 가지고 연산하는 property를 의미한다. 흔히 얘기하는 getter/setter이다.

 

struct Point{
    var x = 0.0
    var y = 0.0
}

struct Size{
    var width = 0.0
    var height = 0.0
}

struct Rect{
    var origin = Point()
    var size = Size()
    
    var center: Point{
        get{
            let centerX = origin.x + (size.width/2)
            let centerY = origin.y + (size.height/2)
            
            return Point(x:centerX, y:centerY)
        }
        set{
            origin.x = newValue.x - (size.width/2)
            origin.y = newValue.y - (size.height/2)
        }
    }
}

 

위와 같이 정의된 클래스가 있다고 할 때, center에 엮이는 get/set이 computed property로 볼 수 있다.

center는 변수값이지만 center에 변수를 set 하거나 get 할때마다 이와 같은 computed property가 수행된다.

get만 쓸 수도 있지만 set은 optional이라 set만 쓸 수 없다. get과 같이 써야 한다.

 

get만 선언된 경우에는 Read-only computed property라고 부른다.

 

Property Observer

Swift에서는 특정 변수가 바뀔때마다 바뀌기 직전/직후로 특정 동작을 정의할 수 있는데, 그게 property observer다.

바뀌기 직전인 willSet과 바뀌고 난 직후인 didSet으로 정의할 수 있다.

 

struct Rect{
    var origin = Point()
    var size = Size()
    
    var center: Point{
        willSet(newCenter){
            print("center will be changed \(center.x), \(center.y) -> \(newCenter.x), \(newCenter.y)")
        }
        didSet(oldCenter){
            print("center was changed \(oldCenter.x), \(oldCenter.y) -> \(center.y),\(center.y)")
        }
    }
}

 

위처럼 willSet은 바뀌기 직전, 즉 아직 instance 내부의 center는 변하지 않은 상태고 곧 바뀔 값인 newCenter를 들고 있다.

didSet은 바뀐 직후, 즉 현재 instance에 assign이 이미 되어있는 상태고, 그 전의 값을 들고 있다.

 

그래서 이처럼 property를 계속해서 observe를 할 수 있다. 

 

 

 

Property Wrapper

특정 property의 get/set을 마치 interface처럼 관리할 수 있다.

만약 어떤 숫자를 get/set할 건데, 그 숫자를 항상 특정 숫자 아래로만 관리를 하고 싶은 상황이라고 하면,

모든 property마다 get/set에다가 12를 넘지 않은 상태로 관리하도록 정의할 수 있다.

그렇지만 이런 Property수가 많아진다면 코드 중복이 많아질 것이다.

 

그래서 사용하는게 바로 이 Property wrapper다. property에 대한 interface정도로 정의하면 될 것 같다.

Swift예제에 따라 어떤 특정 숫자가 12를 넘지 못하도록 Property를 감싸는 wrapper를 작성할 수 있다.

 

@propertyWrapper
struct TwelveOrLess{
    private var number:Int = 0
    var wrappedValue: Int{
        get
        {
            return number
        }
        set
        {
            number = min(number,12)
        }
    }
}

 

struct 선언과 유사하지만 앞에 @propertyWrapper keyword를 붙여주고

실제 property 동작이 어떻게 정의될지 구현을 위해 number라는 변수를 선언한다.

그리고 이 number의 wrapped value의 동작을 getter/setter로 정의해준다.

 

get은 그대로 리턴해주고, set의 경우 12를 넘지 않도록 정의한다.

 

 

그리고 이제 이 껍데기를 실제로 property에 씌워야 한다.

struct SmallRect{
    @TwelveOrLess var height:Int
    @TwelveOrLess var width:Int
}

 

height, width에 대해 위 wrapper를 씌우기 위해 @WrapperName을 같이 적어주면 된다.

 

이렇게 되면 실제로 SmallRect의 height, width에 어떤값을 넣더라도 12를 넘지 않는다.

이 wrapper에도 initializer를 정의할 수 있다.

 

3. Type Property

위에서 설명한 stored, computed property는 모두 class, structrue 혹은 enum의 instance자체에 종속되는 값들이었다.

즉, instance 하나마다 각 property가 할당되는 값이다.

 

type property의 경우, instance가 아닌 type 그 자체에 종속되는 값이다.

c++ class의 static variable과 유사하다고 보면 된다.

 

예를들어 Point class를 하나 정의했다고 치면,

class Point{
    static var maxSize = 10
    
    var size: Int
    init(size: Int){
        self.size = size
    }
}

var pointList = [Point]()
var point1 = Point(size:1)
var point2 = Point(size:2)

 

Point Class에서 stored property는 위에서 설명한대로 size가 될 것이다.

그리고 이 Point class의 size property는 point1, point2 마다 할당된다.

그러나 point의 max size를 정의한 static var maxSize의 경우에는 point1, point2에 종속되지 않고

Point class자체에 종속된다. 그래서 이 값을 접근할때도 type 자체로 접근해야 한다.

 

Point.maxSize = 15

 

주의할점은 class의 initializer 는 instance 단위로 호출되므로, 이 type property의 초기화는 default value 값 설정으로만 할 수 있다.

 

그리고 이 type property도 위에서 언급한 stored, computed type 두가지가 모두 존재한다.

즉, stored instance property / computed instance property와, 

stored type property / computed type property 이렇게 존재한다고 볼 수 있다.

'ios > Swift' 카테고리의 다른 글

#6 Swift - Extension  (0) 2019.11.28
#5 Swift - Optional chaining  (0) 2019.11.27
#4 Swift - Initialization  (0) 2019.11.26
#2 Swift - Functions and Closures  (0) 2019.11.25
#1 Swift 기본 문법 - Basics : constant, variables, Optionals  (0) 2019.11.22