Swift문법 - (12)속성과 메서드

Youth·2022년 9월 28일
0

swift문법공부

목록 보기
12/27
post-thumbnail

속성과 메서드

구조체나 클래스의 변수 → 속성(Property)

  • 저장속성(stored), 지연(lazy) 저장 속성
  • 계산속성(computed)
  • 타입속성(type)
  • 속성감시자(property observer)

구조체나 클래스의 함수 → 메서드(Method)

  • 인스턴스 메서드
  • 타입 메서드
  • 서브스크립트
  • 생성자(지정생성자, 편의생성자, 필수생성자, 실패가능생성자)
  • 소멸자

속성(property)

  • 구조체나 클래스나 속성과 관련된 것들은 차이가 없다

저장속성(Stored Properties)

  • 열거형(Enum)의 경우엔 저장속성을 정의할 수 없다
struct Bird {
    var name: String       // name이라는 저장속성
    var weight: Double     // weight라는 저장속성
    
    init(name: String, weight: Double) {
    // 기본값이 없으면, 생성자를 통해 **값을 반드시 초기화해야함**
        self.name = name
        self.weight = weight
    }
    
    func fly() {
        print("날아갑니다.")
    }
}

지연저장속성(Lazy Stored Properties)

struct Bird1 {
    var name: String
    lazy var weight: Double = 0.2
    // 선언시에는 메모리 공간을 가지고 있지 않음
    // 따라서 초기화 할때 아규먼트를 저장공간에 넣어주는 행위가 불가능
    // **lazy로 선언할때는 "반드시" 기본값을 선언 해줘야함**
    
    init(name: String) {
        self.name = name
    }
    // weight의 경우 lazy로 선언되었기 때문에 초기화가 불가능
    
    func fly() {
        print("날아갑니다.")
    }
}

var aBird1 = Bird1(name: "새")   
// weight 속성 초기화 안됨

aBird1.weight  
// 해당 변수에 접근하는 이 시점에 초기화됨 (메모리 공간이 생기고 숫자가 저장됨)

언제 지연저장속성을 이용하나요

class AView {
    var a: Int
    
    // 1) 메모리를 많이 차지할때
    lazy var view = UIImageView()     // 객체를 생성하는 형태
    
    // 2) 다른 속성을 이용해야할때(다른 저장 속성에 의존해야만 할때)
    // 간단히 말해서 아래 예시처럼 **a가 있어야 b를 만들어낼 수 있는 상황 
    // b라는 값은 a의 값에 의존한다**
    lazy var b: Int = {
        return a * 10
    }()
    
    init(num: Int) {
        self.a = num
    }
}

메서드의 메모리 동작

클래스의 경우 힙영역 → 데이터 영역 → 코드영역

구조체의 경우 코드영역에서 → 바로 메서드 코드영역(클래스보다 빠름)

계산속성(Computed Properties)

class Person {
    var birth: Int = 0

    var age: Int {
    // **계산속성은 무조건 var로 선언을 해줘야한다**
        get {
            return 2021 - birth
        }
        // 중괄호의 열고 닫음 = 함수
			
    // get블럭만 선언할때는 get과 {}생략 가능
    var age: Int {
        return 2021 - birth
    }

        set(age) {
        // set의 input인자의 경우 타입말고 파라미터만 선언해주면된다
        // newValue라는 기본 파라미터를 사용하면 생략이 가능하다
            self.birth = 2021 - age
        }

        // newValue를 사용하면 코드가 간결해진다
        set {
            self.birth = 2021 - newValue
        }
    }
}

var p1 = Person()

// get블럭 -> 파라미터가 없으면 get블럭이라고 판단 -> **읽기**
// 계산속성에서 get블럭은 무조건 구현을 해야함
p1.age   -> 개발자가 값을 "얻기(get)"위한 블럭

// set블럭 -> 파라미터가 있으면 set블럭이라고 판단 -> **쓰기**
// 계산속성에서 set블럭은 선택사항임(구현안해도 됨)
p1.age() -> 개발자가 값을 "세팅(set)"하기 위한 블럭

⭐️다른 저장 속성으로 계산해 나오는 메서드인 경우 계산속성으로 만들 수 있다⭐️

타입속성(Type Properties)

  • 모든 인스턴스가 동일하게 가져야 하는 속성
  • 모든 인스턴스가 공유해야하는 성격에 가까운 이어야 함

1)저장 타입 속성

class Dog {
    // 저장속성 앞에 static을 붙이면 저장 타입속성이 됨
    static var species: String = "Dog"
    
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
}
let dog = Dog(name: "초코", weight: 15.0)
dog.~~species~~ -> 인스턴스에 .을 찍어도 속성으로 보이지 않음
Dog.species   //"Dog"

2)계산 타입 속성

  • 상속을 했을때 static키워드면 override불가능 class키워드면 가능
class Circle1 {
    
    // 저장 타입 속성
    **// 저장 타입 속성은 반드시 기본값을 설정해놔야함**
    static let pi: Double = 3.14
    static var count: Int = 0
    // 저장 속성
    var radius: Double     // 반지름
    
    // (계산) 타입 속성(read-only)
    **// 타입속성끼리는 편하게 변수 공유를 할 수 있음
    // Circle1.pi라고 안하고 그냥 pi라고 해도 됨**
    static var multiPi: Double {
        return pi * 2
    }
    
    // 계산 속성(read-only)
    **// 타입속성이 아니기 때문에 Circle1.multiPi로 접근해야함**
    var getArea: Double {
        return Circle1.multiPi * radius
    }
    
    // 생성자
    init(radius: Double) {
        self.radius = radius
    }
    
}

let myCircle = Circle1(radius: 10.0)
myCircle.getArea

타입속성의 특징

  • 지연 속성의 성격을 가짐 ⭐️
  • 저장 타입속성은 기본적으로 지연 속성 (속성에 처음 접근하는 순간에 초기화됨)이지만, lazy라고 선언할 필요는 없다.

상속에서의 재정의

1) 저장 타입속성 → 재정의 불가능

2) 계산 타입속성 → 상위클래스에서 static이 아닌 class로 정의한 경우엔 재정의 가능

속성감시자

  • 실제로는 “저장”속성감시자라고 생각하면 편함 - 저장속성을 관찰
class Profile {
    var name: String = "이름"

		// 기본적으로 저장속성의 모양을 가지고 있음
    // 추가로 중괄호를 열고닫아주고 그안에 willSet{} or didSet{}을 구현
    // 일반적으로는 didSet{}만 구현한다
    var statusMessage: String = "기본 상태메세지" {
				// didSet은 변화된 직후에 실행됨
        didSet(message) {
            print("메세지가 \(statusMessage)에서 \(message)로 변경될 예정입니다.")
						// print("메세지가 \(oldValue)에서 \(statusMessage)로 이미 변경되었습니다.")
            // 예전값에서 (내가세팅할)새로운값으로 바뀐다 즉, 예전값은 중요도가 낮다
            // 예전값을 oldValue라는 정해진 파라미터로 바꿔도 됨
        }
    }

		// 속성감시자는 저장속성자체를 감시하는 역할이기때문에
    **// 초기값없이 init으로 초기값을 생성해줘도 됨(초기값 세팅에는 감시자실행 x)**    
}
  • willSet은 값이 저장되기 직전에 호출됨
  • didSet은 새 값이 저장된 직후에 호출됨(⭐️자주쓰임)

구조체나 클래스나 “속성”에 있어서는 차이가 거의 없다

메서드(Methods) - 클래스와 구조체 동일

클래스의 인스턴스 메서드

  • 기본적으로 클래스안에 들어있는 기본적인 모양의 메서드(함수)
class Dog1 {
    static var species = "Dog"
    
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    } 
    func trainning() {
        print("월월 저는 \(Dog1.species)입니다.")
        sit()          // self가 생략된 형태라고 보면됨
        self.sit()     // self키워드는 명확하게 지칭하는 역할일 뿐
    }

		**// 구조체로 비슷한 기능을 구현할때는 조금 달라짐
    // 해당 메서드는 클래스의 저장속성의 값을 바꾸는 기능을 가지고 있음**
    func changeName(newName name: String) {
        self.name = name
    }    
}

let bori1 = Dog1(name: "보리", weight: 20.0)
bori1.trainning()
bori1.changeName(newName: "말썽쟁이보리")
bori1.sit()

구조체의 메서드

struct Dog2 {
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    
    
		// 클래스에서는 메서드 내에서 속성을 수정해도 상관 없었지만
    // 구조체에서는 mutating키워드를 넣어야만 속성을 수정할 수 있음
    mutating func changeName(newName name: String) {
				// 저장속성을 수정하는 경우
        **self.name = name**
    }
    
}

var dog = Dog2(name: "흰둥이", weight: 20.0)
// 저장속성 자체에 접근을해서 값을 변경하는건 언제라도 가능
dog.name = "흰순둥이"

**// ⭐️메서드를 통해 속성을 수정하려면 mutating키워드가 반드시 필요**
dog.changeName(newName: "흰순둥이")

타입 메서드(Type Methods)

  • 타입속성과 마찬가지로 static을 class로 선언하면 재정의(overloading)이 가능
class Dog {
    static var species = "Dog"
    
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    
		// 타입 메서드에서, 타입속성에 접근시에는 타입으로 접근하지 않아도 됨
    static func letmeKnow() {     
        print("종은 항상 \(species)입니다.")      
				// Dog.species라고 써도됨
    }

// ⭐️타입 메서드의 호출
Dog.letmeKnow()
// 타입 메서드의 예시
Int.random(in: 1...100) // 정수라는 클래스안에서 바로 사용할수 있는 메서드

서브스크립트

  • 대괄호([])는 특별한형태의 함수를 불러오는 역할이라고 생각

아래와 같은 문법을 서브크립트로 구현해본다면?

var array = ["Apple", "Swift", "iOS", "Hello"]
array[0] // "Apple"
class SomeData {
    var datas = ["Apple", "Swift", "iOS", "Hello"]

		// 1) 함수와 동일한 형태이지만, 이름은 subscript
		// 2) get/set은 계산속성에서의 형태와 비슷
    // 대괄호안에 Int를 넣고 그러면 String이 반환된다는 뜻
    subscript(index: Int) -> String { 
        get {                        
						// 대괄호 안에 index를 넣으면 return으로 datas[index]
            return datas[index]
        }
				// 파라미터를 넣고싶다면 set(parameter)하고 newValue대신 parameter
        set {
            datas[index] = newValue
        }
    }
}

var data = SomeData()
// data.data[0] 원래는 이렇게 구현해야하지만 subscript를 구현하면 아래형식으로도 가능
data[0] // "Apple"
data[0] = "AAA"

서브스크립트 실제 사용 예시 1

struct TimesTable {
    let multiplier: Int = 3
    // 인스턴스의 대괄호에 Int를 넣으면 3*index를 반환한다
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}

let threeTimesTable = TimesTable()
print("6에 3배를 하면 숫자 \(threeTimesTable[6]) 이(가) 나옵니다.")

서브스크립트 실제 사용 예시 2

struct Matrix {
    // 2차원 배열
    var data = [["1", "2", "3"],
							  ["4", "5", "6"],
							  ["7", "8", "9"]]
    
    // 2개의 파라미터를 받는 읽기전용 서브스크립트의 구현
    subscript(row: Int, column: Int) -> String? {
        if row >= 3 || column >= 3 {
						// return형이 있는 함수의 경우 return을 만나면 함수 종료
            return nil
        }
        return data[row][column]
    }
}

// 2개의 파라미터를 받는 서브스크립트 구현도 가능
var mat = Matrix()
mat[0, 1]!     // 대괄호 안에 파라미터 2개 필요, "2"반환

접근제어(따로 단원있음) - 블로그x

class SomeClass {
		// 인스턴스의 .name에 접근할수 없어짐
    private var name = "이름"   
    func nameChange(name: String) {
        self.name = name
    }
}
var s = SomeClass()
// 은닉화(private)해도 변경은 가능함
s.nameChange(name: "홍길동")

싱글톤 패턴

class Singleton {
	// 올바른 싱글톤 패턴의 구현
	static let shared = Singleton()
	var userInfoID = 12345
	// 생성자를 숨겨버리면 새로운 인스턴스를 절대 생성할 수 없음
	private init() {}
}

// 싱글톤을 선언하는 순간 유일한 인스턴스가 생성됨(절대 새로운 인스턴스 안생김)
Singleton.shared // userInfoID = 12345

// 원래 존재하던 인스턴스를 넣는거임
let object = Singleton.shared
object.userInfoID = 12346
Singleton.shared.userInfoID // 12346

// 그렇지만 새로운 객체를 생성할 수는 있음
// private init() {}가 없다면 가능하지만 있다면 불가능(error)
let object3 = Sington()
object3.userInfoID

배열은 실제로 구조체로 구현이 되어있기 때문에 스택영역에 무조건존재하는줄알았는데 클래스내의 저장속성에 배열이저장되어있는 경우엔 힙영역에 존재

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

0개의 댓글