[Swift] 속성과 메서드

임승섭·2023년 5월 31일
0

Swift

목록 보기
6/35

속성

저장 속성 (Stored Properties)

  • 값이 저장되는 일반적인 속성(변수)

  • 여태까지 배운 대부분의 속성

  • 각 인스턴스가 가지는 고유의 데이터 저장 공간

struct Bird {
	var name: String	// 저장 속성
    var weight: Double
    // let weight: Double	// 값 변경 불가능
    
    init(name: String, weight: Double) {
    	self.name = name
        self.weigth = weight
    }

}

var aBird = Bird(name: "참새1", weight: 0.2)
aBird.name
aBird.weight = 0.3	
  • struct와 class에서 동일
  • 상수로 선언(let) 시 값 변경 불가능
  • 각 속성 자체가 고유의 메모리 공간 가짐
    • 아예 처음부터 메모리 공간에 값을 부여할 수도 있고,
      메모리 공간만 차지하게 할 수도 있지만,
      그렇게 할 거면 나중에 무조건 값을 지정해주어야 한다.
  • 객체 초기화 시 반드시 값을 가지고 있어야 함

지연 저장 속성 (Lazy Stored Properties)

  • 접근하는 순간, 메모리 공간을 만든다 (속성 초기화 시점)
struct Bird1 {
	var name: String	
    lazy var weight: Double = 0.2	// 지연 저장 속성 -> 메모리 공간이 생기지 않는다
    
    
    init(name: String) {
    	self.name = name
        // self.weight = ~~  // 불가능
    }

}

var aBird1 = Bird(name: "새1")	// weight에 대한 메모리 공간은 생기지 않는다

aBird1.weight		// 접근하는 순간 이제서야 메모리 공간을 만든다 (0.2 저장)
  • struct, class 동일
  • 지연 : 해당 저장 속성의 초기화를 지연시킨다
  • 해당 속성(변수)에 접근하는 순간, 개별적으로 초기화
  • 변수로만 선언 가능하다. (lazy let 불가능) (값이 변경되는 성격)
  • 선언 시 무조건 기본값을 저장해야 한다

lazy stored를 쓰는 이유

class AView {
	var a: Int
    
    // 1. 메모리 공간을 많이 차지할 때
    lazy var view = UIImageView()
    
    // 2. 다른 속성을 이용해야 할 때
    lazy var b: Int = {
    	return a * 10
    }()
    
    init(num: Int) {
    	self.a = num
    }
}

var view1 = Aview(num: 10)	// a에 대한 메모리만 저장
  • 메모리 공간을 많이 차지하는 속성을 저장할 때 (이미지, ...)
    메모리 낭비를 막기 위함

  • 다른 저장 속성에 의존해야만 할 때
    지연 저장 속성은 먼저 초기화된 속성에 접근할 수 있다.


계산 속성 (Computed Properties)

  • struct, class 동일
  • 관련있는 두 가지 메서드를 한번에 구현 (메서드를 속성으로 변환해 놓은 것)
  • 메서드를 구현한 것이기 때문에 메모리 공간을 가지지 않고,
    해당 속성에 접근하면 다른 속성에 접근해서 계산하고, 그 결과를 리턴 or 세팅
  • 메모리 주소는 강의자료 참고
  • var로 선언
  • 자료형 선언 필수
  • get 반드시 선언
  • example 1
class Person {
	var birth: Int = 0	// 저장 속성
    
    var age: Int {		// 계산 속성
    	get {		// get : 반드시 구현해야 함
        	return 2023 - birth
        }
        set(age) {	// set : 선택적으로 구현해도 되고 안해도 된다. (생략 가능)
        	self.birth = 2023 - age
        }
        
        /* set에서 parameter 생략이 가능하다. (newValue 사용)
        set {
        	self.birth = 2023 - newValue
        }
        */
    }
    
    /* set 생략하고 get만 구현할 때는 get 글씨 생략 가능 (get only property) (read-only)
    var age: Int {
    	return 2023 - birth
    }
    */
    
    
    /*
    // 두 개의 메서드 (birth라는 저장 속성을 통해 계산)
    func getAge() -> Int {		// 리턴값이 있다
    	return 2023 - birth
    }
    
    func setAge(_ age: Int) {	// 리턴값이 없다
    	self.birth = 2023 - age
    }
    */
}

var p1 = Person()
p1.birth = 2000
// p1.getAge()
// p1.setAge(20)

// age(계산속성)에 접근하면, get인지 set인지 판단
p1.age			// 21			   // get
p1.age = 20		// 나이를 새로 세팅	// set

p1.birth		// 2003
  • example 2
class Person {
	var name: String = "사람"
    var height: Double = 160.0
    var weight: Double = 60.0
    
    func calculateBMI() -> Double {
    	let bmi = weight / (height * height) * 10000
        return bmi
    }
}

let p = Person()
p.height = 165
p.weight = 65

p.calculateBMI()	// 23.875

// 위 예시를 computed properties로 바꾸어서 구현
class Person1 {
	var name: String = "사람"
    var height: Double = 160.0
    var weight: Double = 60.0
    
    var bmi: Double {
    	get {
        	let result = weight / (height * height) * 10000
            return result
        }
        
        set {
        	// 키는 안변한다고 가정하고 몸무게 변화
            weight = bmi * height * height / 10000
        }
    }
}

let p1 = Person1()
p1.height = 165
p1.weight = 65

p1.bmi			// get
p1.bmi = 24		// set

타입 속성 (Type Properties)

  • static 추가한 저장 속성
  • 공통적으로 공유할 속성
    • 하나의 인스턴스에 속한다기보다는 타입 자체에 속해져있는 것(ㅡㅜㅡ)
class Dog {
	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.name		// 초코
dog.weight		// 15.0

* dog. 			// 인스턴스로 접근할 수 없다
Dob.species		// 타입 자체로 접근해야 한다.

저장 타입 속성

class Circle {
    
    // 저장 타입 속성
    static let pi: Double = 3.14	// 고정
    static var count: Int = 0   	// 인스턴스를 몇 개 찍어내는지 확인
    
    // 저장 속성
    var radius: Double     
    
    // 계산 속성
    var diameter: Double {     
        get {
            return radius * 2
        }
        set {
            radius = newValue / 2
        }
    }
    
    // 생성자
    init(radius: Double) {
        self.radius = radius
        Circle.count += 1			// 생성자가 일할 때마다 +1
    }
    
}

var circle1 = Circle(radius: 2) 	// 인스턴스 +1
circle1.radius
circle1.diameter = 6

circle1.diameter * Circle.pi		// 원의 둘레


var circle2 = Circle(radius: 3)		// 인스턴스 + 1

Circle.count		// 2



// 실제 사용
Int.max				// 9223...	(2^63)
Int.min				// -9223...	(-2^63)
Double.pi			// 3.141592...

계산 타입 속성

  • 파이(3.14)에 2배를 한 숫자가 많이 사용된다면, 아래와 같은 형태로 사용할 수 있다.
  • 다만, 메모리 공간을 하나 더 쓴다는 단점
class Circle1 {
	
    // 저장 타입 속성
    static let pi: Double = 3.14
    static var count: Int = 0
    
    // 계산 타입 속성
    static var multiPi: Double {	// read only 인 경우
    	return pi * 2	// 원칙적으로는 Circle1.pi라고 써야 하지만, 타입끼리는 서로 생략이 가능하다.
    }
    
    // 저장 속성
    var radius: Double
    
    // 생성자
    init(radius: Double) {
    	self.radius = radius
    }
}

let b = Circle1.multiPi

Summary

  • 저장 타입 속성
    • 인스턴스 생성 시 생성자에서 모든 속성 초기화 완료
    • 해당 저장 속성은 각 인스턴스의 고유한 값
    • 생성자가 따로 없기 때문에 타입 자체에 속한 속성
      따라서 항상 기본값이 필요함. 생략 불가능
    • 지연 속성의 성격을 가진다. (속성에 접근하는 순간 초기화)
  • 타입 속성
    • class, struct 둘 다 가능
    • let, var 가능
    • 특정 인스턴스에 속한 속성이 아니기 때문에 인스턴스 이름으로는 접근 불가능
  • 타입 속성을 선언하는 경우
    • 모든 인스턴스가 동일하게 가져야 하는 속성인 경우
    • 모든 인스턴스가 공유하는 성격에 가까운 속성인 경우
  • 상속 (class만 가능)
    • 저장 타입 : 하위클래스에서 재정의 불가능
    • 계산 타입 : 상위클래스에서 class키워드로 선언한 경우, 재정의 가능

속성 감시자 (Property Observer)

  • 정확하게는 저장 속성 감시자
class Profile {

	var name: String = "이름"
    
    // 저장속성 + 저장 속성이 변하는 시점을 '관찰'하는 메서드
    var statusMessage: String = "기본 상태메세지" {	// 뒤에 값까지 써준다. -> 기본 저장 공간을 갖는다.
    	willSet(message) {	// 추가적인 걸 더 써주었다
        	print("메세지가 \(statusMessage)에서 \(message)로 변경될 예정입니다.")
            //print("상태메세지 업데이트 준비")
        }
    }
}

let p = Profile()

p.statusMessage				  // "기본 상태메세지"
p.statusMessage = "행복해"		// 출력 : 메세지가 기본 상태메세지에서 행복해로 변경될 예정입니다.
p.statusMessage = "우울해"		// 출력 : 메세지가 기본 상태메세지에서 행복해로 변경될 예정입니다.
  • 기존에 들어있던 값이 바뀌면 메서드가 호출된다
  • 실질적인 성격은 메서드
  • willset은 값이 저장되기 직전에 호출, didset은 값이 저장된 후 호출
class Profile1 {

	var name: String = "이름"
    
    var statusMessage: String = "기본 상태메세지" {	
    	willSet(message) {		// 값이 저장되기 직전에 호출
        	print("메세지가 \(statusMessage)에서 \(message)로 변경될 예정입니다.")
            //print("상태메세지 업데이트 준비")
        }
        diddSet(message) {		// 값이 저장되고 난 직후에 호출 (파라미터는 바뀌기 전의 값)
        	print("메세지가 \(message)에서 \(statusMessage)로 이미 변경되었습니다.")
            // print("상태메세지 업데이트 완료")
        }
        
        /*	파라미터 생략하고 newValue, oldValue로 사용 가능
        willSet {
        	print("메세지가 \(statusMessage)에서 \(newValue)로 변경될 예정입니다.")
        }
        didSet {
        	print("메세지가 \(oldValue)에서 \(statusMessage)로 이미 변경되었습니다.")
        }
        */
    }
}

  • class, struct 동일하게 적용
  • 어떤 속성이 변하는 시점을 알아차리도록 시점에 재약을 만드는 코드를 짜긴 어렵기 때문에
    아주 좋은 수단이다
  • 일반적으로는 willSet과 didSet 중 하나만 구현 (보통 didSet)
  • 속성 감시자의 추가
    • 저장 속성 : 원래도 가능, 상속한 경우도 가능
    • 계산 속성 : 상속해서 재정의하는 경우만 가능. (단순 메서드 추가)
      • 속성 감시자를 만드는 대신,
        계산 속성의 set에서 값 변경을 관찰할 수 있기 때문에
        재정의가 아닌 본래 계산 속성에는 추가 불가능
  • 당연히 let(상수)에는 추가 불가능
  • swift 5.3 이후 지연저장 속성에도 속성 감시자 구현 가능

메서드

인스턴스 메서드 (Instance Method)

class

  • 가장 기본적인 메서드
  • 인스턴스에 메모리 공간이 따로 할당되어 있지 않다. (붕어빵 틀에만 존재한다)
  • 인스턴스 이름 뒤에 . 찍고 사용
class Dog {
	var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
    	self.name = name
        self.weight = weight
    }
    
    func sit() {
    	print("\(name)가 앉다")
    }
    
    func layDown() {
    	print("\(name)가 눕다")
    }
    
    func play() {
    	print("논다")
    }
    
    func changeName(newName name: String) {		// mutating 키워드가 필요 없다
    	self.name = name
    }
    
    func trainning {  // 메서드 안에서 다른 메서드 사용 가능
    	sit()
        sit()
        self.sit()	// self 쓸 수도 있고 안써도 된다
    }
}

let bori = Dog(name: "보리", weight: 20.0)

bori.sit()
bori.layDwon()

bori.changeName(newName: "말썽쟁이보리")
bori.name

struct

  • 값 타입(value type)(struct, enum)에서는 기본적으로
    인스턴스 메서드 내에서 속성 수정이 불가능하다
  • 수정하려면 mutating 키워드를 붙여서 속성을 수정한다. (class와 struct의 차이)
struct Dog2 {
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
    
    func sit() {
        print("\(name)가 앉았습니다.")
    }
    
    mutating func changeName(newName name: String) {	// 이름을 바꾸는 메서드
        self.name = name
    }
    
}
  • 값 타입의 인스턴스 메서드 내에서 자신의 속성값 수정은 원칙적으로 불가능하다
  • mutating 키워드 필요

오버로딩 지원

  • 동일한 이름을 갖는 메서드를 여러 개 선언할 수 있다.
func sit() {
        print("\(name)가 앉았습니다.")
}
    
func sit(a: String) {
        print("\(a)")
}

// 둘 다 사용 가능
bori.sit()
bori.sit(a: "hi")

타입 메서드 (Type Method)

  • 메서드지만, 인스턴스의 성격이라기보단 타입 자체의 성격에 가까운 메서드
  • 타입에 해당하는 보편적인 동작
  • static 또는 class 키워드 사용
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)
Double.random(in: 1.2...3.7)

class - 타입 메서드의 상속

  • static - 상속 시 재정의 불가능
  • class - 상속 시 재정의(override) 가능 키워드
class SomeClass {
	class func someTypeMethod() {
    	print("타입과 관련된 공통된 기능의 구현")
    }
}

SomeClass.somTypeMethod()

// 상속
class SomeThingClass: SomeClass {
	override class func someTypeMethod() {
    	print("타입과 관련된 공통된 기능의 구현 (업그레이드)")
    }
}

SomeThingClass.someTypeMethod()

서브스크립트 (Subscripts)

  • 대괄호를 이용해서 접근가능하도록 만든 문법
    array[0]
    dictionary["A"]
  • 특별한 형태의 메서드
  • 역시 메서드이기 때문에 메모리 공간 할당x

인스턴스 서브스크립트의 직접 구현

class SomeDate {
	var data = ["apple", "swift", "ios", "hello"]
    
    subscript(index: Int) -> String {		// func 키워드도 생략. 아예 이름을 subscript
    	get {
        	return datas[index]
        }
        set (parameterName) {
        	datas[index] = parameterName	// 여기서는 paramter 생략하고 newValue 사용 가능. 계산 속성의 set과 동일
        }
    }
}

var data = SomeDate()
data[0]
data[0] = "AAA"
  • 파라미터 생략 불가능 (index)
  • 리턴형도 생략 불가능. 저장할 값의 타입 명시 필요.
  • read-only로도 선언 가능. (set 생략 가능)
  • set block에서 기본 파라미터 newValue 제공

Example

struct TimesTable {
	let multiplier: Int = 3
    
    subscript(index: Int) -> Int {
    	return multiplier * index		// get만 구현
    }
}

let threeTimesTable = TimesTable()
threeTimesTable[6]		// 18
threeTimesTable[24]		// 72

struct Matrix {
	var data = [ ["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"] ]
    
    subscript(row: Int, column: Int) -> String? {
    	if row >= 3 || column >= 3 {
        	return nil
        }
        return data[row][column]
    }
}
var mat = Matrix()
mat[0, 1]! 			// 파라미터 두 개 필요

타입 서브스크립트

enum Planet: Int {	// 열거형의 원시값
	case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    
    static subscript(n: Int) -> Planet {
    	return Planet(rawValue: n)!		// Planet을 생성
    }
}

let mars = Planet[4]
print(mars)		// "mars"
  • static 또는 class 키워드로 타입 자체의 서브스크립트 구현 가능
  • class는 상속 시 재정의 가능하다

접근 제어 (Access Control)

  • 객체의 은닉화
  • 코드 내부의 세부 구현 내용을 숨기는 것이 가능
class SomeClass {
	private var name = "이름"		// 외부에서 name에 접근 불가
    
    func nameChange(name: String) {
    	self.name = name
    }
}

// 기존
var s = SomeClass()
s.name = "홍길동"
s.nameChange(name: "홍홍홍")

// private 후
s.	// name이 안 뜬다
s.nameChange(name: "하하하")	// nameChange 함수로만 이름 변경 가능

Singleton Pattern

  • 메모리상에 유일하게 1개만 존재하는 객체를 설계한다

  • 앱 구현 시, 유일하게 1개만 존재하는 객체가 필요한 경우

  • 한번 생성된 이후에는 앱이 종료될 때까지 유일한 객체로 메모리에 상주

  • 메모리 구조 강의자료 보기

  • 변수로 접근하는 순간 lazy하게 동작해서 메모리에 올라감

class Singleton {
	static let shared = Singleton()		// 자신의 객체 생성해서 변수에 할당
    
    var userInfoId = 12345
    private init() {}					// 생성자 앞에 private -> 접근 불가
}


Singleton.shared		// 접근하는 순간 메모리에 올라감

let object = Singleton.shared
object.userInfoId =1234567

Single.shared.userInfoId		// 1234567. 이렇게 접근해도 값은 동일 (유일한 객체)


let object2 = Singleton.shared	// 유일한 객체 가리키는 것
object2.userInfoId				// 1234567 (유일한 객체)

// 원래. -> init이 private이 되었기 때문에 새로운 객체 생성이 불가능하다.
* let object3 = Singleton()		// 그냥 새로운 객체 생성
* object3.userInfoId				// 12345

실제 사용 예시


let screen = UIScreen.main    // 화면 관라히는 유일한 객체
let userDefaults = UserDefaults.standard    // 유저 관리
let application = UIApplication.shared   // 앱 관리
let fileManager = FileManager.default    // 파일 관리
let notification = NotificationCenter.default   // 노티피케이션(특정 상황, 시점을 알려줌)

0개의 댓글