티스토리 뷰

INDEX

1. 메서드(Methods) 

2. 서브스크립트(Subcripts)

3. 접근제어(Access Control)

4. 싱글톤 패턴(Singleton)

5. 마무리

 

이번 포스팅에서는 지난 속성(properties)에 이어서 클래스와 구조체에서 쓰이는 것들을 한데 묶어서 정리하고자 한다. 각각의 개념자체는 어렵지 않지만, 코드를 치는데 익숙하지 않고, 개념이 확실하게 정립되어 있지 않다면 흐릿하게 기억될 것이다. 

1. 메서드(Methods) 

1-1  인스턴스 메서드(Instance Methods)

: func()

클레스/구조체/(열거형)에 정의된 우리가 일반적으로 알고 있는 함수

메서드이기 때문에, 인스턴스에 메모리공간이 따로 할당되지는 않는다.

따라서 인스턴스를 생성해야만 메서드에 접근 가능 => 코드를 보면 당연하게 해왔던 것이란 걸 알 수 있다.

 

1) 클래스의 인스턴스 메서드

class Dog {
    var name: String //저장 속성
    var weight: Double //저장 속성
    static var species = "Dog" //타입 속성

    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
    
    func sit() {
        print("\(name)가 앉았습니다.")
    }
    
    func layDown() {
        print("\(name)가 누웠습니다.")
    }
    
    func play() {
        print("열심히 놉니다.")
    }
    
    func whatSpecies() {
      	print("제 종은 \(Dog.species)입니다.") 
    } //인스턴스 메서드에서 타입 속성 접근시에는 타입키워드(Dog.) 표기는 필수적이다.
      //그냥 species는 에러(물론 아래의 타입 메서드에서는 가능함)
    
    func changeName(newName name: String) {
        self.name = name
    }
    
}

let choco = Dog(name: "초코", weight: 0.8)

// 인스턴스(객체)의 메서드. 인스턴스의 이름을 통해 호출 가능하다

choco.sit()
choco.layDown()
choco.play()

//초코가 앉았습니다.
//초코가 누웠습니다.
//열심히 놉니다.

choco.changeName(newName: "귀요미초코")
choco.name
choco.sit()
choco.layDown()
choco.play()

//귀요미초코가 앉았습니다.
//귀요미초코가 누웠습니다.
//열심히 놉니다.

 

2)구조체의 인스턴스 메서드

mutating 키워드 

위는 클래스의 인스턴스 메서드에 해당되며, 메서드의 사용에 있어 별다른 제한사항이 없다.

하지만 값타입(Value Type)인 구조체, 열거형의 경우에는 인스턴스 메서드에서 속성값을 원칙적으로 변경불가하다.

따라서 수정허용을 위해서는 mutating키워드를 func앞에 꼭 붙여줘야한다.(클래스 - 구조체 차이점)

struct Dog2 {
    var name: String
    var weight: Double
    
    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
    
    func sit() {
        print("\(name)가 앉았습니다.")
    }
    //mutating 키워드가 없다면 에러
    mutating func changeName(newName name: String) {
        self.name = name
    }
    
}

var choco2 = Dog2(name: "초코22", weight: 20.0)
choco2.sit() //초코22가 앉았습니다.
choco2.changeName(newName: "다른 초코")
choco2.sit() //다른 초코가 앉았습니다.

주의점!! 

위아래 예제를 보면 클래스의 인스턴스 choco는 상수, 구조체의 인스턴스 choco2는 변수로 저장되어 있다.

일부러 저렇게 한 이유는 let,var선언을 주의하고 둘의 값 저장 방식을 한번 더 느꼈으면해서다.

클래스를 타입으로 선언할 경우, 상수(let)로 해도, 참조 타입(Reference Type)이기에 메서드 내부에서 변경을 해도 에러가 나지 않는다.

하지만 구조체는 값타입이기에  COW(Copy On Write)기법 통해 choco2내부의 인스턴스(name,weight)도 스택영역에 아예 새로운 메모리를 생성하여 값을 할당한다. 하지만 상수로 정의하였기에 기존 값(초코22, 20.0)이 있는 메모리를 벗어나는건 불가능하다. 따라서 mutating으로 선언했더라도 let으로 인스턴스를 선언했다면 값을 바꿀 수 없다.

(물론 var로 choco2를 선언했기에 위의 코드는 값 변경이 가능했던 것이다.) 추가적인 건 아래 공식문서 설명 참조

1-2 타입 메서드(Instance Methods)

: static func / class func 

다음은 타입 메서드다. 타입메서드는 인스턴스의 기능이 아닌 타입 자체에 필요한 기능을 구현시 사용하는 메서드이다.

사용시 주의점은 

1. 인스턴스 메서드가 인스턴스로 접근했다면 타입 메서드는 타입 자체로 접근한다.

 (인스턴스로 접근X) ex) choco.name

2. 타입 메서드 내부에서도 타입 멤버(타입 속성 + 타입 메서드)에 대한 접근만 허용한다. 

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()
    }

    func changeName(newName name: String) {
        self.name = name
    }
	//⭐️타입 메서드
    static func letmeKnow() {
        print("종은 항상 \(species)입니다.")      // Dog.species라고 써도됨	
        //name이나 weight은 타입속성이 아니기에 접근 불가하다❗️
    }

}

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


bori.sit() //보리가 앉았습니다.
bori.changeName(newName: "말썽쟁이보리")
bori.sit() //말썽쟁이보리가 앉았습니다.

bori.trainning()
//월월 저는 Dog$$$$입니다.
//말썽쟁이보리가 앉았습니다.
//말썽쟁이보리가 앉았습니다.
//말썽쟁이보리가 앉았습니다.

// ⭐️ 타입 메서드의 호출 ⭐️
Dog.letmeKnow() //종은 항상 Dog입니다.

3. 생성자 내부에서도 타입으로 접근한다.

 

 

class Circle {
    static let pi: Double = 3.14
    class var multiPi: Double {
        return pi * 2
    } // 상속시 재정의할려면 class 키워드 사용, 재정의가 필요 없을시 static키워드 사용가능
    
    var radius: Double
    
    init(radius: Double) {
        self.radius = radius
        
        Circle.pi // 내부에서도 타입으로 접근해야함
    }
}

Circle.multiPi // 타입으로 접근

4. static VS class. 

타입에 해당하는 보편적인 동작이라는 점에서 타입 메서드로 정의하려면 func 앞에 static이나, class키워드를 붙인다.

static VS class. 무슨 차이가 있을까? 

답은 오버라이딩의 여부다.

(오버라이딩은 상속을 통해서 가능하다. 상속에 관해서는 다음 포스팅에서 정리할 것이나 이 개념을 위해 오버라이딩(overriding)은 구조체와 클래스 1부에서 미리 한번 정리해 놓았다. )

타입메서드가 들어가 있는 클래스를 상속 받을때, 그 타입메서드에 대한 재정의를 허락할것인지, 말것인지에 따라 static/class로 나눠진다. 간단히 말해서, 그 함수(타입 메서드)의 오버라이딩을 허락한다면 class, 금지한다면 static키워드를 사용하면 된다. 아래 코드를 보자. 

// 상위클래스
class SomeClass {
    class func someTypeMethod() {     // 타입 메서드(static으로 했다면 에러발생)
        print("타입과 관련된 공통된 기능의 구현")
    }
}

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

}

SomeThingClass.someTypeMethod() //타입과 관련된 공통된 기능의 구현(업그레이드)

 

 

물론 상속은 static을 사용하여도 가능하다. 재정의가 불가능하다는 것이다.(아래코드 참조)

// 상위클래스
class SomeClass {
    class func someTypeMethod() {     // 타입 메서드
        print("타입과 관련된 공통된 기능의 구현")
    }
}

// 상속한 서브클래스
class SomeThingClass: SomeClass { }

SomeThingClass.someTypeMethod() //타입과 관련된 공통된 기능의 구현

아래는  실제 타입 메서드 사용 예시

2. 서브스크립트(Subcripts) 

대괄호안의 인덱스로 접근하는 문법(배열과 딕셔너리 형태)

배열: array[index]

딕셔너리:dictionary[key]

메서드이기에, 인스턴스에 메모리공간이 할당되어 있지 않다.

(코드를 보면 알겠지만 서브스크립트의 정의방법은 계산속성, input과 output이 있는 함수 정의 방식과 매우 유사하다.)

서브스크립트 정의 방법: subscript(parameter: Type) -> return Type { get{  } set{  }  }

메서드 접근 방법: instance[파라미터] 

주의점

1. 파라미터 생략 불가(값이 반드시 필요)
2. 리턴형!!도 생략 불가(저장할 값의 타입 명시 필요. 함수 떠올리기)
3. 읽기 전용(read-only)으로 선언 가능 (계산속성 떠올리기! set블록은 선택적 구현이 가능, 쓰기 전용X)

처음보면 추상적으로 느껴질 수 있는 개념이라고 느꼈다. 예시와 함께 알아놓는 게 좋다.

(인스턴스) 서브스크립트의 작동방식 -> 계산속성처럼 구현하면 된다(get블록과 set블록)

// ⭐️인스턴스 메서드로써의 서브스크립트 구현⭐️

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

    
    subscript(index: Int) -> String {     // 1) 함수와 동일한 형태이지만, 이름은 subscript
        get {                             // 2) get/set은 계산속성에서의 형태와 비슷
            return datas[index]
        }
        set(parameterName) {
            datas[index] = parameterName // 또는 파라미터 생략하고 newValue로 대체 가능(계산 속성의 setter)
        }
    }
    
}

var data = SomeData()
data[0] //Apple

 

서브스크립트 사용 예시

struct TimesTable {
    let minute: Int = 60
    
    subscript(index: Int) -> Int {
        return minute * index
    }
}

let threeTimesTable = TimesTable()
print("5시간은 분으로 환산하면 \(threeTimesTable[5])분입니다. ") //5시간은 분으로 환산하면 300분입니다.

2차원 배열 구현하기 -> 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 nil
        }
        return data[row][column]
    }
}

var mat = Matrix()

if let x = mat[0, 1]     // 대괄호 안에 파라미터 2개 필요
{ print(x) } //2

 

아래와 같은 예제들을 1,2개 만들어보면 금방 익힐 수 있다. 

// read-only이기에, get표시 생략 O
class Europe{
    var countries = ["France", "England", "Poland", "Italy"]
    subscript(index: Int) -> String {
        return countries[index]
    }
}

var planeTo = Europe()
planeTo[3] //Italy

//set블록의 parameter를 newValue로 대체 O
class Asia{
    var countries = ["Korea", "China", "Japan","Singapore"]
    subscript(index: Int) -> String{
        get { return countries[index] }
        set { return countries[index] = newValue}
    }
}

//반복문
var shipTo = Asia()
shipTo[2]
shipTo[2] = "jjjjapan"

for i in 0...3 {
    print(shipTo[i])
}
//Korea
//China
//jjjjapan
//Singapore

 

 

타입 서브스크립트 (Type Subscripts)

: subscript앞에 static키워드를 붙인다.

위에 있는건 인스터스 서브스크립트이며, 타입 서브스크립트는 타입 자체에서 호출되는 서브스크립트.

class TypeSubScript{
    static var numbers: [Int] = [1,2,3,4,5,6]
    static subscript(index: Int) -> Int {
        return numbers[index]
    }
}

TypeSubScript[3] //4

 

아래는 타입 서브스크립트 공식문서 예제

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

 

3. 접근제어(Access Control)

특정 코드의 접근을 다룬 소스파일이나 모듈에서 제한하는 것으로 쉽게 말하면 private 키워드를 사용하여 외부에서 private으로 선언된 속성과 메서드에는 접근하지 못하게 하는 것이다.

 

접근 제어란 개념은 왜 필요 할까?

컴파일 시간이 줄어든다. (해당 변수가 어느 범위까지 쓰이는지 알고 있다.)

코드의 영역을 분리시켜 효율적으로 관리 가능하다.

class SomeClass {
    private var name = "이름"
    
    func nameChange(name: String) {
        self.name = name
    }
}



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

 

4. 싱글톤 패턴(Singleton)

앱이 실행하는 동안 유일하게 1개만 존재하는 객체가 필요할때 사용하는, 공용으로 사용하고 싶을때 생성하는 객체이다.

데이터 영역에 유일한 객체의 주소가 담겨 있다.

자기자신을 초기화하여 static let 변수에 할당하는 방식으로 할당된 변수는 lazy(지연속성)처럼 동작한다.

class Singleton {
    // 타입 프로퍼티(전역변수)로 선언
    static let shared = Singleton()      // 자신의 객체를 생성해서 전역변수에 할당
    var userInfoId = 12345
    private init() {} // 새로운 객체 추가적 생성이 불가하게 막는 것 가능
}

싱글톤 패턴의 장점은?

1번만 생성가능하기에, 메모리 낭비를 줄인다.

전역 인스턴스이기에 공유가 쉽다(View Controller 1, View Controller 2, 3,...등에서 공통적으로 접근이 가능해 객체내 데이터 공유가 가능하다)

class Info {
    static let me = Info()
    var name: String?
    var age: Int?
    var kg: Double?
    
    private init() {} //인스턴스의 추가생성을 막기위해 private
}

let myInfo = Info.me
myInfo.name = "이도형" // View Controller 1에서 수정
myInfo.age = 23 // View Controller 2에서 수정
myInfo.kg = 70 // View Controller 3에서 수정

아래는 실제 싱글톤 패턴의 사용 예시.

 

5. 마무리

지금까지 크게 인스턴스 메서드, 타입 메서드, 서브스크립트 문법, 접근제어, 싱글톤 패턴에 대해 정리했다. 각 인덱스별로 기억에 남아야 될건,

먼저 인스턴스 메서드는 크게 클래스와 구조체에서 정의해보며 클래스와 달리 구조체는 메서드 내 속성값을 변경할 수 없다는 점에서 mutating키워드가 필수적이었다. 구조체(값타입)와 클래스(참조타입)의 메모리 저장방식을 잘 알고 있다면, 헷갈리지 않을 것이다.

다음 타입메서드에서는 타입멤버에만 접근이 가능한 메서드로 static과 class키워드를 사용하여 정의하였고 이 둘의 차이점은 오버라이딩의 유무였다.

다음은 대괄호와 인덱스를 이용한 문법 (배열과 딕셔너리 형태로 표현가능한) 서브스크립트였다. subscript키워드로 구현하며 계산속성, 함수형태를 가지고 있었으며, 타입 서브스크립트도 있었다.

마지막 부분에는 메모리가 1번만 생성되는 유일객체싱글톤 패턴을 설명하며 보조적으로 접근제어가 가능한 private키워드에 대해 간단히 설명하며 마무리 하였다. 이 내용들이 다 정리가 된다면 PASS

 

출처

Inflearn Allen님의 강의 노트

Swift 공식문서

 

 

 

 

 

 

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함