티스토리 뷰

Mutating


Concentraion 클래스를 struct 타입으로 변경시 처리해야 되는 것들을 확인해봅시다.

class Concentraion {
	...
}

struct Concentraion {
	...
}

struct로 변경시 chooseCard 함수에서 애러가 발생합니다.

self의 프로퍼티에 값을 대입하는 부분에 Cannot assign to property:'self' is immutable 에러가 발생한다.

에러가 발생한 곳을 보면 모두 멤버 변수에 값을 대입하는 곳이다.

아래와 같이 구조체에서 값을 변경하는 함수는 mutating 키워드를 붙여줘야한다. 

mutating 키워드를 추가하여 객체를 바꾸는 함수라는것을 알려주자.

 

indexOfOneAndOnlyFaceUpCard의 set 함수에서 값을 변경하고 있습니다.

private var indexOfOneAndOnlyFaceUpCard: Int? {
    get {
        ...
    }
    
    set {
        for index in cards.indices {
            cards[index].isFaceUp = (index == newValue)
        }
    }
}

하지만 mutating 키워드가 없지만 에러가 발생하지 않았습니다.

계산된 속성에서는 mutating이 없어도 swift가 알아서 인식을 해줍니다. 그래서 함수만 고려하면 됩니다.

 

 

Protocol(프로토콜)


별도의 구현이 없는 메소드를 가지고 있으며, 데이터(값, 레퍼런스)에 대한 저장 공간은 없습니다. 

(프로퍼티가 get, get/set 함수를 가지면 선언이 가능합니다.)

protocol SomeProtocol {
    var someNumber: Int { get }
    var someString: String { get set }
}

 

API 구현을 보다 간결하게 해줍니다.

함수를 호출하는 쪽에서는 파라미터를 전달하고 결과값을 받으며

수신쪽에서는 전달 받은 파라미터로 함수가 어떤 동작을 할지 구현합니다.

 

 다중 상속을 제공합니다. 

프로퍼티는 변수와 함수를 선언만 하기 때문에 저장공간이 따로 없습니다.

데이터의 상속은 없기 때문에  순수하게 기능만 상속하게 됩니다.

 

프로토콜은 클래스, 구조체, 열거형과 같이 그냥 하나의 타입입니다.

 

프로토콜 사용

1. 프로토콜을 선언합니다.

protocol Moveable {
     func move(to point: CGPoint)
 }

 

2. 클래스나 구조체, 열거형에 프로토콜을 상속합니다.

(클래스 이름 뒤에 : 를 적고 프로토콜 이름을 적습니다.)

 class Car : Moveable {
     func changeOil()
 }

 

3. 프로토콜 내용을 구현합니다.

 class Car : Moveable {
     func move(to point: CGPoint) { ... }
     func changeOil()
 }

 

프로토콜 구현시 모든 함수와 프로퍼티를 의무적으로 구현해야합니다.

하지만 Object-c에서의 프로토콜에서는 구현이 선택적입니다.

스위프트에서 @objc를 프로토콜 선언 앞에 붙이면 objective-c 타입으로 선언됩니다.(그렇게 되면 선택적으로 구현이 가능합니다.)

 

프로토콜 예시)

protocol SomeProtocol : InheritedProtocol1, InheritedProtocol2 {
      var someProperty: Int { get set }
      func aMethod(arg1: Double, anotherArgument: String) -> SomeType
      mutating func changeIt()
      init(arg: Type)
}

프로퍼티인 경우에 get과 get/set 이 가능합니다.

self의 변수 값을 변경시는 함수는 mutating 키워드를 고려해야합니다.(구조체에서 상속시 필요)

초기화 함수인 init 함수를 추가할 수 도 있습니다.

클래스만 받는 프로토콜로 정의시 상속부분에 class 키워드를 추가해줘야합니다.

protocol SomeProtocol : class, InheritedProtocol1, InheritedProtocol2 {
	...
}

 

프로토콜 상속 예시)

class SomeClass : SuperclassOfSomeClass, SomeProtocol, AnotherProtocol {
	// implementation of SomeClass here
	// which must include all the properties and methods in SomeProtocol & AnotherProtocol
}

클래스인 슈퍼 클래스 뒤에 쉼표를 적고 구현할 프로토콜을 추가해주면 됩니다.(구조체는 슈퍼클래스가 X)

프로토콜에 정의된 프로퍼티나 함수를 구현하지 않는다면 에러가 발생합니다.

프로토콜에 init이 있다면 require키워드를 앞에 붙여줘야합니다.

class SomeClass : SuperclassOfSomeClass, SomeProtocol, AnotherProtocol {
      // implementation of SomeClass here, including ...
      required init(...)
}

 

파라미터로 특정 프로토콜을 요구하는 함수 선언

func slipAndSlide(x: Slippery & Moveable)

slipAndSlide 함수 호출시 전달하는 파라미터 타입은 Slippery, Moveable 둘다 구현한 타입이어야합니다.

 

델리게이션


프로토콜의 아주 중요한 용도중 하나

뷰와 컨트롤러 간의 블라인트 커뮤니케이션을 가능케 합니다.

 

사용방법

1. 뷰를 위한 프로토콜을 선언합니다.( UIkit의 뷰프로토콜에는 will, did should와 같은 함수가 선언되어 있습니다.)

2. 뷰 안에 프로토콜 타입의 weak delegate 프로퍼티를 추가합니다.( 테이블뷰나 스크롤뷰에는 deleage 변수가 있습니다.)

3. view가 필요한 시점에 delegate의 함수를 호출합니다.(will, did, should 시점에 호출합니다.)

4. 컨트롤러에 protocol를 추가 후 구현합니다.(UIkit의 뷰 프로토콜의 함수는 objecti-c로 구현되어 옵셔널입니다.)

5. 컨트롤로가 가지는 view(2.)의 deleage 변수값을 self로 세팅합니다.

 

사용 예시) UIScrollView

UIScollView은 아래와 같은 프로퍼티가 존재합니다.(2번 내용)

weak var delegate: UIScrollViewDelegate?

weak 키워드를 붙인 이유 

주로 delegate는 viewController로 설정됩니다. viewController는 해당 view를 멤버 변수로 등록하여 사용하며 View도 컨트롤러를 등록하여 사용시 메모리 사이클 문제가 발생하게 됩니다. 그래서 weak를 사용하여 사이클을 예방합니다.

 

UIScollView의 프로토콜은 아래와 같이 정의되어 있습니다.(1번 내용)

@objc protocol UIScrollViewDelegate {
    optional func scrollViewDidScroll(scrollView: UIScrollView)
    optional func viewForZooming(in scrollView: UIScrollView) -> UIView
    ... and many more ...
}

 

스크롤 뷰가 있는 뷰 컨트롤러에 UIScorllViewDelegate를 선언합니다.(4번 내용)

class MyViewController : UIViewController, UIScrollViewDelegate { 
	... 
}

 

스크롤 뷰의 delegate 변수를 self(MyViewConroller)로 설정합니다.(5번내용)

scrollView.delegate = self

이제 관심있는 UIScorllViewDelegate 함수를 구현합니다.(4번 내용)

 

프로토콜의 다른 사용 예시) Dictionary

딕셔너리 사용시 키값은 고유값이어야만 합니다.

이를 위해 딕셔너리의 키 값 타입은 특정 프로토콜을 구현해야하는데 Hashable입니다.

protocol Hashable: Equatable { 
      var hashValue: Int { get }
}

 

protocol Equatable {
      static func ==(lhs: Self, rhs: Self) -> Bool
 }

Hashable을 구현해야한다는건 Equatable 프로토콜을 구현해야 한다는 뜻입니다.

파라미터에서 Self는 자신의 타입을 의미합니다.

즉 자신의 타입 두개가 동일한지 확인하는 함수 구현이 필요합니다.

 

Dictionary 타입 정의는 아래와 같습니다.

Dictionary<Key: Hashable, Value>

키는 Hashable 프로토콜 타입이어야하며, 값은 Value 타입으로 제한이 없습니다.

 

코드에 적용해봅시다.

아래는 카드에 해당하는 emoji를 가져오는 함수입니다.

private var emoji = [Int:String]()

private func emoji(for card: Card) -> String {
    if emoji[card.identifier] == nil, emojiChoices.count > 0 {
        emoji[card.identifier] = emojiChoices.remove(at: emojiChoices.count.arg4random)
    }
    
    return emoji[card.identifier] ?? "?"
}

함수 내부에서 카드의 identifier를 접근하여 사용하고 있습니다. 이를 개선해봅시다.

먼저 딕셔너리의 키를 Int에서 Card로 변경합니다.

private var emoji = [Card:String]()

emoji  변수를 사용하는 부분도 함께 수정해줍니다.

private func emoji(for card: Card) -> String {
    if emoji[card] == nil, emojiChoices.count > 0 {
        emoji[card] = emojiChoices.remove(at: emojiChoices.count.arg4random)
    }
    
    return emoji[card] ?? "?"
}

물론 위와 같이 수정시 에러가 발생합니다.

에러를 수정해보도록 합시다.

먼서 Card에 Hashable를 구현해야합니다.

struct Card : Hashable {
	...
}

위와 같이 수정시 에러는 발생하지 않으나 동작이 정상적으로 되지 않습니다.

 

Equatable 프로토콜의 == static 함수를 구현해시 정상적으로 동작하지 않습니다.

Hashable 프로토콜의 hash(into:)함수도 함께 구현해줘야합니다.

Hashable 프로토콜의 hashValue 구현시에는 deprecated 경고가 발생한다.(hash(into:)사용 권장)

struct Card : Hashable {

    ...
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(identifier)
    }
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.identifier == rhs.identifier
    }
}

 

프로토콜을 사용하는 부분들은 아래와 같다.

Countable: 1...3이나 0..<6와 같은 구조

Sequence: 이터레이터, for in 구조에서 사용 가능하게 해준다.

Collection : subcripting([]), index(offsetBy:), index(of:), etc

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
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 31
글 보관함