티스토리 뷰

실습시 기존에 작성했던 내용들을 MVC 모델로 변경하는 작업을 하게됩니다.

 

클래스 파일 생성


파일 이름은 파일내 가장 중요한 클래스 이름을 사용합니다.

메인 모델 클래스 이름을 Concentration으로 할 예정입니다.

File- New - File... 로 진입합니다.
Swift File을 선택 후 Next를 선택합니다.
파일명은 Concentration 한 뒤 Create 해줍시다.

 

Concentration 클래스 작성


Concentration.swift 파일을 만들었으니 클래스 만들어 봅시다.

필요한 내용으로는 카드 묶음이 필요할것이고, 동작으로는 카드를 뒤집는 동작이 필요합니다.

import Foundation

class Concentraion {
    
    var cards = [Card]()
    
    func chooseCard(at index: Int) {
        
    }
}

chooseCard의 파라미터가 Card가 아닌 Int값을 전달하는데 이는 다른 UI에 보다 유연하게 처리하기 위해서 입니다.

외부(컨트롤러)에서 Card 객체를 구하는것 보다 Index를 구하는게 훨씬 수월할것입니다.

 

Card 구조체 작성


Card.swift 파일을 생성한 뒤 Card 구조체를 작성해봅시다.

import Foundation

struct Card {
    var isFaceUp = false
    var isMatched = false
    var identifier: Int
}

identifier를 이모지를 사용할 수도 있겠만 그래서는 안됩니다. UI와 독립적이야 하기 때문입니다. 그래서 이모지나 JPEG 이미지같은 내용은 UI와 관련된 내용이기 때문에 여기 있어서는 안됩니다.

 

스위프트에서의 클래스와 구조체


공통점

- 변수와 메소드를 가질 수 있다.

 

차이점

- 구조체는 상속성이 없다

- 구조체는 값(Value) 타입이고, 클래스는 참조(Referece) 타입이다.

- 값 타입은 파라미터로 전달하거나 리스트에 넣거나, 다른 변수에 넣을때 복사가 이루어 진다.

- 스위프트에서 문자열, 숫자형, 배열, 딕셔너리은 값 타입이다.

- 실제로 대입때 마다 바로 복사하면 성능 문제가 있으므로 값이 변경되었을때 복사가 이루어진다.('쓰기 시 복사' 전달 체계)

- 참조 타입은 힙에 자료가 담겨져 있고, 포인터로 사용이 가능한 타입입니다.

- 참조 타입을 여러군데에서 쓴다면 실제 값을 복사하는게 아닌 그 값을 가리키는 포인터를 전달하여 사용하는것입니다.

- 한 오브젝트를 가리키는 여러 포인터가 있을 수 있습니다.

 

Controller와 Model 연결하기


컨트롤러에 Concentration 클래스 사용을 위해 객체를 생성한다.

import UIKit

class ViewController: UIViewController {

    var game = Concentraion()

    // 생략
}

위 코드를 보면 Concentration의 init 함수를 구현하지 않았는데 빈 파라미터로 객체를 생성이 가능합니다.

Class 내부의 멤버변수들이 모두 초기화가 되어 있다면 파라미터가 없는 init 함수를 자동으로 제공해주기 때문입니다.

Struct도 기본 생성자를 지원해주나 Class와 다릅니다.

아래와 같이 모든 멤버변수를 받는 생성자를 제공합니다.

순서는 선언 순서로 보여집니다.

 

init 함수 추가하기


먼저 Card의 init 함수도 추가해봅시다. isFaceUp과 isMatched는 기본값이 false이기 때문에 identifier만 설정해주면 됩니다.

init(identifier i: Int) {
    identifier = i
}

위와 같이 i로 축약해서 쓰는것 보다는 아래와 같이 self를 쓰는 형식을 선호합니다.

init(identifier: Int) {
    self.identifier = identifier
}

self가 없다면 init에서 전달받은 identifier 변수로 인식하게 됩니다.

사용자가 직적 init를 구현하면 기본제공하는 init 함수는 사용이 불가능해집니다.

파라미터가 3개인 init 함수가 사라졌다.

 

카드 개수가 몇개로 늘어날지 모르기 때문에 Conecentration의 카드 개수를 지정해주는 init 함수를 추가해봅시다.

init(numberOfPairsOfCards: Int) {
    for identifier in 1...numberOfPairsOfCards {
        let card = Card(identifier: identifier)
        cards += [card, card]
    }
}

card를 하나 생성하여 array에 넣고 있습니다. 이때 Card는 struct 타입이기 때문에 복사가 이루어집니다. 동일한 카드가 생성시 1번, 복사에 2번 생성되므로 총 3개의 카드가 생성되는것을 알 수 있습니다.

 

static 함수 및 변수


카드의 Identifier 지정을 Concentration이 아닌 Card 구조체 내에서 지정해주도록 구조를 변경해봅시다.

import Foundation

struct Card {
    var isFaceUp = false
    var isMatched = false
    var identifier: Int
    
    static var identifierFactory = 0
    
    static func getUniqueIdentifier() -> Int {
        identifierFactory += 1
        return identifierFactory
    }
    
    init() {
        self.identifier = Card.getUniqueIdentifier()
    }
}

static 키워드가 있는 var와 func이 하나씩 추가되고 init에서 static 함수를 호출하고 있습니다.

정적 메소드를 호출시 타입명을 앞에 붙여줘야하기 때문에 Card.getUniqueIdentifier() 로 호출하는 걸 알 수 있습니다.

static 함수인 getUniqueIdentifier 내부에서 identifierFactory 앞에 Card.이 없는데 , 이는 같은 static 으로 선언되어 있기 때문입니다.

 

Lazy 키워드


변경된 초기화 코드를 사용하여 Concentration을 생성해봅시다.

var game = Concentraion(numberOfPairsOfCards: (cardButtons.count+1)/2 )

count에 +1을 해주는 이유는 홀수인 경우 카드 생성이 안되는 문제가 있기 때문입니다.

카드가 3개라면 2개만 생성하기 때문에 +1을 해주어 4개를 생성하게 합니다.

 

위와 같이 코드를 수정하면 아래와 같은 오류가 뜹니다.

cardButtons 변수를 초기화 함수의 파라미터로 사용할 수 없다고 합니다.

self가 완전히 생성이 된 후 사용을 해야하나 시점이 너무 빨라서 발생한 문제입니다.

 

프로퍼티 생성 시점을 늦추는 키워드가 바로 lazy입니다.

lazy 키워드를 붙이면 일단은 초기화가 되었다고 쳐주고 실제로 사용하게 될 때 생성하게 됩니다.

중단점을 걸어 디버깅 해보면 사용하는 곳이 없어 생성자를 호출하지 않습니다.

단, lazy를 변수에 사용시 DidSet(속성관찰자) 키워드를 사용할 수 없게 됩니다.

 

Nil  확인 및 리턴


아래와 같은 코드가 있습니다.

var emoji = [Int:String]()
    
func emoji(for card: Card) -> String {
    if emoji[card.identifier] != nil {
        return emoji[card.identifier]!
    } else {
        return "?"
    }
}

if 문에서 nil을 체크하고 nil이 아니라면 값을 return하고 nil이라면 "?"을 return 합니다.

?? 연산자를 사용하여 위의 코드를 아래와 같은 코드로 수정이 가능합니다.

var emoji = [Int:String]()
    
func emoji(for card: Card) -> String {
    return emoji[card.identifier] ?? "?"
}

nil 인 경우에는 뒤에 나오는 값"?"을 사용하도록 할 수 있다.

 

Random 함수


특정 범위 안의 숫자값을 랜덤으로 구해보자

arc4random_uniform을 사용하면 된다.

파라미터와 리턴값 타입이 UInt32이다.

파라미터는 UInt32이며 0에서 해당값 미만의 숫자를 랜덤으로 구해줍니다.

아래 코드에서 0 이상 emojiChoices.count 미만의 값을 임의로 전달해줍니다.

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

 

카드 뒤집기 처리


카드를 선택했을때 상황

1. 모든 카드가 뒷면인 경우 -> 그냥 선택한 카드를 뒤집는다

2. 두장의 카드가 앞면인 경우 -> 두장을 뒷면으로 바꾸고 선택한 카드를 뒤집는다.

3. 한장의 카드가 앞면인 경우 -> 선택한 카드와 동일한 종류인지 확인 한다.

 

프로퍼티를 하나 추가하여 앞면인 카드를 추적할 수 있게 합니다.

앞면인 카드가 없으면 nil로 관리하기 위해 옵셔널 변수로 선언합니다.

var indexOfOneAndOnlyFaceUpCard: Int?

 

카드를 뒤집느 함수를 구현해봅시다.

func chooseCard(at index: Int) {
    if !cards[index].isMatched {
        if let matchIndex = indexOfOneAndOnlyFaceUpCard {
            // check if cards match
            if cards[matchIndex].identifier == cards[index].identifier {
                cards[matchIndex].isMatched = true
                cards[index].isMatched = true
            }
            
            cards[index].isFaceUp = true
            indexOfOneAndOnlyFaceUpCard = nil // 두 장의 카드가 앞면이기 때문에 nil로 처리
        } else {
            //  either no card or 2 cars are face up
            for flipDownIndex in cards.indices {
                cards[flipDownIndex].isFaceUp = false
            }
            
            cards[index].isFaceUp = true
            indexOfOneAndOnlyFaceUpCard = index // 한장만 앞면이기 때문에 index 기록
        }
    }
}

 

TODO 주석


나중에 해야할 일을 기록하기 위해 주석을 입력할 수 있습니다.

그중 // TODO: 를 입력하게 될 경우 주석이 아래와 볼드체로 표시됩니다. (띄워 쓰기를 맞춰 입력해야합니다. )

일반 주석과는 다르게 볼드체로 나타납니다.

그리고 함수 리스트에서 TODO로 입력한 내용을 확인 할 수 있습니다.

주석 내용을 누르면 입력된 위치로 이동합니다.

콜론 다음에 - 빼기 기호를 넣을 수 있습니다. 

// TODO: - Suffle

위와 같이 입력한 경우에는 함수 리스트에 구분선이 생기게 됩니다.

init과 Suffle사이에 구분선이 생긴것을 확인 할 수 있습니다.

MARK 키워드를 입력할 수 도 있으니 참고바랍니다.

// MARK: - Suffle

Mark로 입력한 경우

TODO는 해야할 일을 기록하는 용도로 쓰며(구현이 더 필요한 부분들)

MARK는 다음에 쉽게 찾기 위해 기록하는 용도로 씁니다.(특정 코드 부분을 찾기 쉽게 기록)

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