🐦 Swift

[Swift] 속성(property)와 메소드(method)

dev_zoe 2023. 6. 8. 17:18
반응형

본 포스팅은 '스위프트 프로그래밍 (3판)' 도서 앨런 Swift 문법 마스터스쿨 강의를 참고하여

Swift 프로그래밍에 대해 정리하는 글입니다.

혹시 틀린 부분이 있거나 질문이 있으시다면 언제든지 댓글 달아주시면 정말 감사하겠습니다 :)


*속성(프로퍼티): 구조체/클래스의 변수

*함수(메소드): 구조체/클래스의 함수

 

속성(property, 프로퍼티)

프로퍼티 관련해서 구조체와 클래스 간의 차이는 없음

저장 속성

- 위에서 말한 클래스/구조체의 속성과 저장속성은 같은 말이다. 즉, 인스턴스의 변수/상수를 의미함

- 인스턴스 생성 시, 저장 속성은 반드시 값을 가지고 있어야만함 (저장 속성을 온전히 다 초기화하는 것이 인스턴스 생성의 조건임)

struct Bird {
    var name: String?   // name, weight: 저장속성!
    var weight: Double
    
    init(name: String, weight: Double) {    // 기본값이 없으면, 생성자를 통해 값을 반드시 초기화해야함
//        self.name = name     // 옵셔널이면 초기화하지 않아도 무방
        self.weight = weight
    }
    
    func fly() {
        print("날아갑니다.")
    }
}

var aBird = Bird(name: "참새1", weight: 0.2)

 

지연 저장 속성

lazy 키워드를 var 앞에 붙인 속성으로, 값에 접근이 있어야만 초기화(=지연)하는 속성

class AClass {
    var a: Int
    
    // 1) 메모리를 많이 차지할때 -> 필요할 떄 이미지를 로드해서 초기화할 수 있음
    lazy var view = UIImageView()     // 객체를 생성하는 형태
    
    // 2) 다른 속성을 이용해야할때(다른 저장 속성에 의존해야만 할때)
    lazy var b: Int = { // 클로저
        return a * 10
    }()
    
    init(num: Int) {
        self.a = num
    }
}

var view = AClass(num: 10)

 

1) "나중에 초기화한다" 라는 속성때문에, lazy var 만 가능하다.

2) 생성 시에 초기화하지 않기때문에, 기본값이 반드시 있어야한다.

 

 

💡 언제 사용하는가?

1) 처음부터 초기화할 필요가 없을 때 -> 메모리 공간의 낭비를 막을 수 있음

ex. 이미지의 경우 초기화에 많은 메모리를 차지하는데, 처음부터 이미지를 초기화하는게 아니라 이미지뷰를 사용하고자 할 때 이미지 초기화

2) 지연 저장 속성이 다른 저장 속성을 이용하고자 할 때 (위의 변수 b의 예시)

-> 일반 저장 속성의 경우, 클래스가 초기화될 때 바로 메모리 영역에 공간이 생기면서 초기화되므로 변수 b가 a의 값을 이용 못할 수 있으므로, 일반 속성이 초기화가 된 다음에 나중에 접근할 때 사용하기 위해서 지연 저장 속성을 쓰는것임

 

계산(연산) 속성

속성의 형태를 가진 실질적 메서드로, getter/setter를 간결하게 구현할 수 있는 속성

class Person {
    var name: String = "사람"
    var height: Double = 160.0
    var weight: Double = 60.0
    
    var bmi: Double { // 타입추론이 불가하므로 반드시 지정해야함
        get {        //getter ===> 값을 얻는다는 의미 -> 필수 구현
            let bmi = weight / (height * height) * 10000
            return bmi
        }
        set(bmi) {   //setter ===> 값을 세팅한다(넣는다)는 의미 -> 선택 구현
            weight = bmi * height * height / 10000
        }
        // bmi라는 파라미터를 생략하고, newValue를 사용해 구현 가능
        /*
        set {
        	weight = newValue * height * height / 10000
        }
        */
    }
}

1) setter -> 후에 값을 세팅함 이라는 속성 때문에 var 로만 선언이 가능하며, 타입도 명시해주어야한다.

2) get은 필수 구현이며, set은 선택 구현이다.

3) set에서 파라미터를 생략 가능하며, 이 때 newValue라는 이름으로 대신하여 사용할 수 있다.

4) get만 있다면 get을 생략하고 return하는 것이 가능하다.

class Person {
    var name: String = "사람"
    var height: Double = 160.0
    var weight: Double = 60.0
    
    var bmi: Double {  // Get 블록만 있다면 get 블럭으로 감싸지 않고 바로 return 문 사용 가능
        let bmi = weight / (height * height) * 10000
        return bmi
    }
}

 

💡 언제 사용하는가?

1) 해당 변수의 값을 가져오거나 세팅하는 메소드가 필요할 때, 해당 속성을 사용하면 더 간결하고 가독성있게 코드 작성이 가능함

 

타입 속성

각각의 인스턴스가 아닌 타입 자체에 속한 속성이기 때문에,
인스턴스.속성이 아닌 Type.속성 형식으로 접근한다. (인스턴스 내부에서도 마찬가지)

 

1) 앞에 static을 붙이고, class 키워드를 통해 재정의가 가능하도록 할 수 있음 (기능도 C의 static 과 거의 유사함)

2) 인스턴스에 속한 것이 아니기 때문에, 인스턴스를 생성하지 않고도 호출할 수 있다.

 

1) 저장 타입 속성

1) 모든 인스턴스가 공통적으로 가져야하는 보편적인 속성이거나, 한 번만 초기화하면 인스턴스가 동일한 속성을 지속적으로 사용할 수 있을 때 유용함

2) let과 var 모두 선언 가능

3) 별도의 생성자를 통한 초기화 과정이 없으므로, 반드시 기본값이 필요하다.

4) 자체적으로 지연(lazy)의 속성을 가지며, 지연 속성과는 달리 멀티 스레드 환경에서 Thread-Safe 하다.

*Thread-Safe?: 멀티스레드 환경에서 여러 스레드로부터 동시에 접근이 가능함에도 불구하고 프로그램 실행에 문제가 없는 상태

5) class 키워드 사용 불가 -> 속성 상속이 불가능하기 때문

class Circle {
    
    // 저장 타입 속성 -> let과 var 모두 선언 가능
    static let pi: Double = 3.14
    static var count: Int = 0   // 인스턴스를 (전체적으로)몇개를 찍어내는지 확인
    
    // 저장 속성
    var radius: Double     // 반지름
    
    // 생성자
    init(radius: Double) {
        self.radius = radius
        Circle.count += 1
    }
}

var circle1 = Circle(radius: 2)   // 인스턴스를 +1 개 찍어냈다.
circle1.radius
circle1.diameter
Circle.count    // 인스턴스에 속한 프로퍼티가 아니므로 Type.속성 이런식으로 접근함 -> 1

var circle2 = Circle(radius: 3)   // 인스턴스를 +1 개 찍어냈다.
Circle.count    // 2

 

2) 계산(연산) 타입 속성

1) 저장 타입 속성과는 달리 상속이 가능하며(실질적 메서드이기 때문), static이면 재정의 불가능/class 키워드면 재정의가 가능하다.

2) var로만 선언이 가능하다. (계산 타입 속성)

class Circle {
    
    // 저장 타입 속성
    static let pi: Double = 3.14
    static var count: Int = 0
    
    
    // (계산) 타입 속성(read-only)
    static var multiPi: Double {
        return pi * 2
    }
    
    // 저장 속성
    var radius: Double     // 반지름
    
    
    // 생성자
    init(radius: Double) {
        self.radius = radius
    }
}

 

💡 언제 사용하는가?

1) 모든 인스턴스가 공통적인 속성을 가지도록 할 때

 

속성 감시자 

속성의 변화 시점을 관찰하는 실질적 메서드이며, 변화하기 직전(willSet)과 변화 직후(didSet)에 필요한 내용을 구현할 수 있다.

 

1) var 로만 선언 가능

2) 변수가 업데이트 될 때 필요한 동작을 정의하고자 할 때 사용

3) 저장 속성, 상속한 계산 속성을 재정의할 때 사용 가능

4) willSet(변경 직전), didSet(변경 직후) 메소드로 구현 가능하며, 둘중 하나로만 구현해도 무관 (일반적으로 didSet 사용함)

class Profile {
    
    // 일반 저장 속성
    var name: String = "이름"
    
    var statusMessage: String {
        willSet(message) {  // 바뀔 값이 파라미터로 전달
            print("메세지가 \(statusMessage)에서 \(message)로 변경될 예정입니다.")
            print("상태메세지 업데이트 준비")
        }
        // 프로퍼티 생략 가능. 이 때는 newValue가 전달
        didSet(message) {   // 바뀌기 전의 과거값이 파라미터로 전달
            print("메세지가 \(message)에서 \(statusMessage)로 이미 변경되었습니다.")
            print("상태메세지 업데이트 완료")
        }
        // 프로퍼티 생략 가능. 이 때는 oldValue가 전달
    }
    
    init(message: String) {
        self.statusMessage = message
    }
    
}

5) 계산 속성과 속성 감시자는 논리가 겹치므로 같이 사용할 수 없다. (어차피 set을 통해서 데이터의 변화 관찰이 가능한데 willSet, didSet을 추가할 이유가 없음)

 

💡 언제 사용하는가?

1) 변수가 변하면 무엇인가를 업데이트 하는 패턴 구현이 필요할 때

 

메서드(method)

메서드 관련해서 구조체와 클래스 간의 차이는 없음 (생성자와 소멸자 관련해서만 차이점이 존재)

인스턴스 메서드

특정 타입의 인스턴스에 속한 함수

class Dog {
    static var species = "Dog" // 타입 속성
    
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
    
    func sit() { // 인스턴스 메서드
        print("\(name)가 앉았습니다.")
    }
    
    func trainning() { // 인스턴스 메서드
        print("월월 저는 \(Dog.species)입니다.")
        sit()    // 내부에서도 사용 가능
        sit()
        self.sit()     // self키워드는 명확하게 지칭하는 역할일 뿐
    }
}

 

❗️struct나 enum과 같은 값 타입은 원칙적으로는 자기 자신의 속성을 메소드를 통해 변경할 수 없으므로, (값 타입은 값이 변경되면 클래스의 데이터를 변경하는것이 아니라 값이 변경된 인스턴스로 복사하는 것이기 때문) mutating 키워드를 붙여 반드시 컴파일러에게 "자기 자신의 속성을 변경하는 메소드" 임을 알려주어야한다.

 

struct Dog {
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
    
    func sit() { // 인스턴스 메서드
        print("\(name)가 앉았습니다.")
    }
    
    mutating func changeName(_ name: String) { // 구조체는 mutating을 반드시 붙여야함
    	self.name = name
    }
}

 

타입 메서드

- 각각의 인스턴스가 아닌 타입 자체에 속한 메소드이기 때문에,
인스턴스.메소드()가 아닌 Type.메소드() 형식으로 접근한다. (인스턴스 내부에서도 마찬가지)

 

1) 앞에 static을 붙임 (기능도 C의 static 과 거의 유사함) / class 사용시에만 상속 시 재정의가 가능하다.

2) 인스턴스에 속한 것이 아니기 때문에, 인스턴스를 생성하지 않고도 호출할 수 있다.

class Dog {
    static var species = "Dog" // 타입 속성
    
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
    
    class func introduce(_ name: String) {     // 타입 메서드 -> 상속가능
        print("우리 개의 이름은 \(name) 입니다.")
    }
    
    static func letmeKnow() {     // 타입 메서드에서, 타입속성에 접근시에는 타입으로 접근하지 않아도 됨
        print("종은 항상 \(species)입니다.")      // Dog.species라고 써도됨
    }   
}

Dog.letmeKnow() // 실제 호출 -> 종은 항상 Dog입니다.
Dog.introduce("초코") // 우리 개의 이름은 초코입니다.

class Yuri: Dog {
	override class func introduce(_ name: String) {
    	print("우리 개는 \(name) 이라고 해요")
    }
}

Yuri.introduce("yuri") // 우리 개는 yuri 라고 해요

 

서브스크립트

대괄호를 통해서 인스턴스 내부의 특정 값에 접근하거나(get), 설정할 수 있는(set) 문법

array[idx], dictionary[key] 이렇게 인덱스나 키로 값을 가져오는 게 모두 서브스크립트 문법임

 

1) subscript 키워드 사용하여 명명

2) 인스턴스[파라미터] 형식으로 접근 (이 때 파라미터 생략 불가)

3) get은 필수적으로 구현하며, set은 선택사항이다. set에서 프로퍼티를 생략하면 newValue 라는 프로퍼티가 기본적으로 제공된다.

4) 서브스크립트 메서드 앞에 static/class를 붙임으로써 타입 서브스크립트를 만들 수 있다.

5) 각 타입의 구현부 혹은 extension의 구현부에 위치해야한다.

 

class Data {
    var datas = ["Apple", "Swift", "iOS", "Hello"]

    // 파라미터 생략 불가
    subscript(index: Int) -> String {
        get {         // 필수 구현
            return datas[index]
        }
        set {		 // 선택 구현
            datas[index] = newValue         // 파라미터 생략하고 newValue로 대체 가능
        }
    }
}

var data = Data()
Data[0]
Data[0] = "AAA"

 

반응형