아스키코드와 유니코드(ASCII, Unicode)

|

개인적인 연습 내용을 정리한 글입니다.
잘못된 내용이 있다면 편하게 댓글 남겨주세요!


들어가기에 앞서…

  • 컴퓨터의 기본 저장 단위는 바이트(byte)이다.
  • 1바이트는 8비트(bit)이다.
  • 1바이트에는 2의 8승에 해당하는 256개의 고유한 값을 저장할 수 있다.
  • 문자나 기호들의 집합을 컴퓨터에 저장하거나, 통신 목적으로 사용할 경우에는 부호로 바꿔야한다.
    • 이를 ‘문자인코딩(encoding)’ 또는 ‘부호화’ 라고 하며 부호화된 문자를 복원하는 것을 ‘복호화’ 라고 한다.
  • 모스부호도 일종의 문자 인코딩이다.

아스키(ASCII) American Standard Code for Information Interchange

아스키코드는 7비트, 증 128개의 고유한 값만 사용한다.

7비트만 활용하는 이유는 1비트를 통신 에러 검출을 위해 사용하기 때문이라고 한다. » Parity Bit

위 이미지를 통해 0~127까지 각각 고유한 값이 할당되어 있는 것을 알 수 있다.

  • 0~32: 인쇄와 전송 제어용으로 사용됨
  • 33~126: 숫자, 알파벳 대/소문자, 특수기호 등이 할당

이렇게 0~127까지 총 128개의 고유값을 저장하고 있다.

이는 영문 키보드로 입력할 수 있는 모든 가능성을 다 담고 있지만, 이 외의 다른 언어를 표현하기에는 7비트로는 부족하다는 것을 알 수 있다.

그래서 이를 보완하기 위해 용량을 더 크게 확장한 유니코드가 등장하게 되었다.

유니코드(Unicode)

한글은 자음과 모음의 조합 가능 개수만 따져도 128개는 가뿐히 넘는다.

즉, 아스키 코드의 한계를 극복하기 위해 유니코드가 등장하게 되었다.

swift 기본문법 - 상수와 변수

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


상수와 변수

swift는 함수형 프로그래밍 패러다임을 지향하기에 불변 객체를 굉장히 중요시함으로 상수 표현이 많이 등장하게 된다.

  • 상수 선언 키워드: let
    • 상수의 선언/ let 이름: 타입 = 값
  • 변수 선언 키워드: var
    • 변수의 선언/ var 이름: 타입 = 값

swift는 띄어쓰기에 굉장히 민감한 언어로 이를 잘못 쓰게되면 오류/컴파일에 문제가 생길 수 있다.
뿐만 아니라 값의 타입이 명확하다면 타입은 생략이 가능하다.

  • let 이름 = 값
  • var 이름 = 값

상수(let)으로 값을 지정해주고 나면 차후에 다른 값으로 변경이 불가능하다. 반면 변수(var)는 차후에 다른 값을 할당해줄 수 있다.

var variable : String = "차후에 변경이 가능한 var"
let constant : String = "차후에 변경이 불가능한 let"

variable = "변수는 이렇게 차후에 다른값을 할당할 수 있습니다."
constant = "상수는 차후에 값을 변경할 수 없습니다."
constant에는 값을 새로 할당할 수 없으니 let을 var로 변경하라.

상수와 변수 선언 후 나중에 값 할당하기

나중에 할당하려고 하는 상수나 변수는 우선적으로 타입은 미리(반드시) 명시해주어야 한다.

let sum: Int  // 타입만 먼저 선언해놓은 상수
let inputA = 10
let inputB = 20

sum = inputA + inputB  // 상수 선언 후 첫 할당

상수는 선언 후 처음으로 한번만 할당해줄 수 있으며 할당 이후에는 그 값을 절대 바꿀 수 없다.
변수또한 선언을 미리 할 수 있으며 상수와 다르게 할당 이후에도 그 값을 바꾸는 것이 가능하다.

var nickname : String

nickname = "zehye"
nickname = "jihye"

그리고 이러한 상수와 변수 모두 할당되기 전에 사용하고자 한다면 컴파일러가 오류를 띄워준다.

초기화 되기 이전에 사용이 되었다.

swift 기본문법 - 이름짓기, 콘솔로그, 문자열 보간법

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


이름짓기 규칙

  • Lower Camel Case: function, method, variable, constant > someVariableName..
  • Upper Camel Case: type(class, struct, enum, extension) > Person, Point, Week..

콘솔로그

  • print: 단순 문자열 출력
  • dump: 인스턴스의 자세한 설명(description 프로퍼티)까지 설명

문자열 보간법(String Interpolation)

프로그램 실행 중 문자열 내에 변수 또는 상수의 실질적인 값을 표현하기 위해 사용 > \()

import Swift
let age: Int = 10

"안녕하세요! 저는 \(age)살 입니다."  // "안녕하세요! 저는 10살 입니다."
"안녕하세요! 저는 \(age+5)살 입니다."  // "안녕하세요! 저는 15살 입니다."

print("안녕하세요! 저는 \(age)살 입니다.")
>>> 안녕하세요! 저는 10살입니다
class Person {
  var name: String = "zehye"
  var age: Int = 26
}

let zehye: Person = Person()
print(zehye)
dump(zehye)
>>> Person 클래스 주소  // print의 결과값
>>> Person 클래스 주소  // dump의 결과값
>>> zehye
>>> 26

Stanford Lecture2 - MVC(Model-View-Control)패턴

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
Stanford Developing iOS11 Apps with Swift를 듣고 정리하였습니다.


MVC 디자인 패러다임

일단 기본적으로 우리 시스템 안의 모든 객체는 세가지로 나뉜다. > Model, View, Controller

1. Model

앱에서 ‘무엇’에 해당하는 UI와 독립적인 객체들이다. 집중력게임에서는 집중력게임을 할 줄 아는 부분에 해당하는 앱이다. 카드가 매치되는지 확인하고, 가져가며 언제 카드를 뒤집어야 하는지 그런 것들을 아는 부분이다. 즉, 집중력 게임에 대한 지식들을 의미한다.

하지만 그게 어떻게 화면에 나오지는 지에 대한 부분은 없다.

2. Controller

‘어떻게’에 해당한다. 어떻게 집중력 게임이 화면에 나오는지에 관심을 갖는다.
Model의 정보를 해석하고 구성해서 View에게 보내주는 역할 혹은 사용자와의 상호작용을 모델에게 해석해주는 역할

3. View

컨트롤러의 하인들로 보통 아주 일반적인 UI 요소들인데(UIButton, UILabel, UIViewController) 컨트롤러가 모델과 통신해 앱에서 어떤 것을 UI에 가져오도록 할 때 필요하다.

이러한 MVC는 세가지 캠프 사이의 커뮤니케이션을 관리하는 데에 의미가 있다. 캠프에 객체를 넣으면 서로 얘기할 때 특정한 규칙을 따라야 한다.

MVC 서로의 관계

Model과 Controller

Controller > Model: Controller가 원하는 대로 Model과 이야기 할 수 있다.

  • 무엇이 어떤건지에 대해 사용자에게 보여줘야 하기 때문에 모델에 대한 접근이 가능해야 한다.
  • 모델의 공개된 모든 기능과는 거의 무제한적인 대화가 가능하다.

Model > Controller: 직접적인 소통은 불가능하다.

  • Model은 UI와 독립적이고 Controller는 근본적으로 UI에 종속된다.

그러나 소통은 가능하다. Model의 데이터가 변경되었을때 해당 변경사항과 연결된 UI에도 업데이트를 하는 경우!

Notification & KVO(key value observing): Model의 변경사항을 방송하는 것

Model과 View

둘 사이의 소통은 불가능하다.

이유1: 모델은 UI와 독립적이지만, View는 UI에 의존한다.(View는 UI와 관련된 것들만을 포함한다)
이유2: View는 일반 객체일 뿐이다. (버튼 그 자체가 무슨일을 하는지는 알수가 없다.)

View와 Controller

Controller > View: View는 일반적으로 Controller의 하인들이다.

  • Controller는 outlet을 통해 View 객체들을 모두 관리할 수 있다.

View > Controller: 구조적으로 미리 정해진 방식을 통해 Controller에게 행위에 대한 요청(delegate)과 데이터에 대한 요청(data source)을 할 수 있다.

구조적: 이 통신을 하기로 했을 때 일반적인 객체는 어떻게 컨트롤러와 대화를 할지 조금 먼저 생각해놓고 있어야 한다는 것

더 나아가 targer-action의 구조를 통해 사용자의 행위에 따라 필요한 함수를 호출할 수도 있다.

  • target: 컨트롤러가 해야하는 건 자신에게 타겟을 만드는 것
  • action: UIButton이나 다른 것들은 액션을 가지고 버튼을 누를때마다 타겟을 호출
scrollView?

ScrollView가 이미지 같은 걸 스크롤하면 이를 컨트롤러에게 말해줘야 한다.(아래로 더 스크롤할수 있는지, 옆으로 스크롤해도 되는지?와 같은..) 즉, 일을 수행하는 동안 컨트롤러와 대화하길 원한다.
그렇게 하려면 ScrollView가 미리 정의한 메소드를 delegate의 일부로 사용해야 한다.
이 delegate는 ScrollView에 있는 var이고 이 var는 객체를 가지고 있다.
우리가 이 객체에 대해 아는건 이것이 특정 메시지들에 반응을 한다는 것이다.
이는 특정 메시지들에 반응을 한다는 것을 의미한다. 이런 메시지의 대부분에는 will, should, did같은 단어가 들어간다.

view는 일반적(객체)이기 때문에 자기가 화면에 표시하고 있는 데이터를 가질 수는 없다. 즉, 화면에 표시하고 있는 데이터를 인스턴스 변수로 가지고 있지 않다는 뜻이다.

더 나아가 아래와 같은 상황을 가정해보자

View가 아이팟 음악 라이브러리 전체를 보여주고 있다고 생각해보자
노래는 50,000곡이 있다고 가정한 상황에서 ListView(리스트를 만드는 일반적인 View)가 그 50,000개의 곡을 모두 가져오는 것은 불가능할 것이다. 그 대신 프로토콜을 사용해 다른 방식의 특별한 메시지를 주고받는데 (어느곳에 있는 데이터를 줘라, 항목이 몇개 있나 하는 메시지 등) 그러면 Controller는 이를 구현해 Model에게 이야기하여 데이터를 View에 가져다 준다.

즉, TableView는 iOS에 있는 일반적인 뷰 중 하나인데 아이패드에 음악을 스크롤할 때 이게 아이팟 음악 앱이라고 생각을 하고 노래 리스트를 나열해주는 것이 아닌 단지 데이터를 준다고만 인지한 상태에서 화면에 리스트를 띄우게 된다.

이러한 종류의 delegate를 data source라고 한다.

이러한 delegate와 data source는 서로 비슷한데 둘의 차이점은 다른 메소드들을 가지고 있는 것 이다.
이때 메소드들은 UI요소들에 의해 좌우된다.

즉, 미리 정해진 리스트가 아니라 UI요소의 상황에 따라 바뀌는 것을 의미한다.

복잡한 MVC

MVC가 다른 MVC와 소통할 때 다른 MVC를 항상 자신의 뷰로 취급한다. 그래서 세부사항을 모른채로 구조적인 방법으로만 이야기한다. 이는 일반적이고 재사용 가능한 컴포넌트처럼 행동한다.

즉, MVC를 잘 그룹화하는 것이 중요하다!

Model 만들기

Concentration.swift

File > New > File > Swift File > 최상위 폴더아래(ViewController.swift와 동일한 위치)에 생성

Model에 class를 생성할 때에는 공개 API가 무엇인지를 항상 생각해야한다. API란 Application Programming Interface의 약자로 클래스안의 모든 메소드와 인스턴스 변수의 리스트를 의미한다. 공개 API는 다른 클래스들의 사용을 허락한 메소드와 인스턴스 변수들을 의미한다. 결국 이 클래스를 어떻게 사용하는지를 결정하는 것을 뜻한다.

이때 사용자 입장에서 집중력 게임에서 할 수 있는 일은 카드를 뒤집는 일 밖에 할 수 없다. 다른 카드 매칭 같은 것들은 내부에서 구현해야하는 것이다. 사용자의 관점에서는 오직 카드를 touch하는 행위만 할 수 있다. 그래서 카드를 고르게 해주는 func이 필요하다.

import Foundation

class Concentration {
    var cards = [Card]()  // 빈 배열로 초기화
    func chooseCard(at index: Int) {

    }
}

카드를 고를때 인덱스를 이용하게 함으로써 다른 종류의 UI에 대해 좀 더 유연하게 대응해준다.

Card.swift

이제 Card 모델을 만들어보도록 한다. 이 모델또한 UI와는 아무런 관계가 없고 여기서 가장 흥미로운 점은 Card를 구조체로 만드는 것이다.(클래스가 아니다)

구조체와 클래스의 차이

  1. 구조체는 상속성이 없다. 이를 통해 구조체를 좀 더 간단하게 만들 수 있다.
  2. 구조체는 값 타입이고 클래스는 참조 타입이다.

값 타입은 인자로 보내거나 배열에 넣거나 다른 변수에 할당해도 복사가 가능하다. iOS에서는 배열, 문자열, 정수형, 딕셔너리 모두 구조체이다. 코드안에서 주고받을 때 계속 복사된다. 무언가를 전달할 때 모든 내용을 하나하나 복사하기보다 누군가 내용을 변경했을 때만 실제 복사하도록 하는 전달방식을 취한다.

참조 타입은 힙에 자료형이 담겨있고 그 자료형에 포린터를 쓸 수 있다. 이를 여러군데 사용한다면 실제로 그 자료형을 보내는게 아닌 자료형을 가리키는 포인터를 보내는 것이다. 따라서 코드안에 한 오브젝트를 가리키는 포인터가 잔뜩 있을 수 있다.

다시 Card 모델 생성으로 돌아와서 이 Model안에서는 카드가 무엇을 해야하는지, 어떻게 게임이 진행되어야하는 지에 관한 내용을 작성해준다. Card가 어떻게 보이는지에 대해서는 절대 정의하지 않는다.

import Foundation

struct Card {
    var isFaceUp = false  // 뒤집어져 있는 것이 기본
    var isMatched = false  // 서로 일치하지 않는 것이 기본
    var identifier : Int  // 카드의 식별자
}

Controller에서 Model을 써보기

ViewController.swift

Controller에 Concentration 모델클래스를 연결해보자

var game : Concentration

이제 이 game에겐 어떤 메시지도 전달이 가능해진다. 카드를 가져올수도 있고 카드를 선택할 수도 있다.

그러나 익숙한 에러가 하나 뜰 것이다.

초기화가 되지 않았다는 것으로 아래와 같이 코드를 진행해준다.

var game : Concentration = Concentration()

신기하게도 해당 방법을 통해 에러를 해결할 수 있게 될 것이다. Concentration는 클래스이기 때문에 모든 변수들이 초기화되면 인수가 없는 init을 자동으로 가지게 된다. Concentration에서는 변수 cards가 딱 하나있고 cards 변수는 이미 초기화가 되어있었다. 따라서 Concentration은 자동으로 init을 가지게 된다.

그리고 iOS는 강한 타입추론이기에 game이 당연히 Concentration 타입이라는 것을 알 수 있기 때문에 아래와 같이 작성이 가능하다.

var game = Concentration()

자체적인 init을 만들어주기

그러나 우리가 가질 카드가 앞으로 몇개인지는 알수가 없다. 따라서 자체적인 init을 만들어줘야 한다.

Concentration.swift


구조체가 받는 공짜 이니셜라이저는 모든 변수를 초기화한다. 이미 Card에서 isFaceUp, isMatched과 같이 이미 초기화한 것이 있다고 하더라도 이 변수 모두를 초기화해버린다. 따라서 구조체에서 다시 초기화해줌으로써 Card를 초기화한다.

card가 스스로 자기의 식별자를 지정해주게 한다. 카드가 어떤 숫자를 골라도 상관없이 유일하기만 하면 된다. 이것만 충족시키면 된다. 즉 init이 식별자를 받지 않도록! 유일한 식별자를 주기 위해서 Card.swift에서 static 메서드를 사용한다.

import Foundation

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

    static func getUniqueIdentifier() -> Int { // 유일한 Int를 리턴한다
      return 0 // 함수가 호출될때마다 유일한 식별자(0)를 리턴할 것이다
    }

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

정적함수는 함수이며 Card클래스 안에 있지만 Card에게 보낼 수 없다. Card는 이 메시지를 전혀 이해하지 못하고 메시지를 이해할 수 있는 것은 Card의 타입뿐이다. 이 함수를 전역함수나 유틸리티 함수 도는 그냥 타입에 붙어있는 함수로 생각하면 된다. Card에게 유일한 식별자를 달라고 요청할 수도 없다. Card 타입 자체에게 요청한다.

그래서 함수를 부르고 싶다면 타입에게 부른다. > Card.getUniqueIdentifier()

import Foundation

class Concentration { var cards = Card func chooseCard(at index: Int) {

}

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

스위프트에서의 _ 는 무시하라는 의미이거나 다시 쓰지 않을 거라 어떤 것이어도 상관없다는 의미

카드를 섞기

인스턴스 멤버인 cardButtons를 사용할 수 없다. 이 cardButtons은 속성 이니셜라이저 안에서 사용이 불가능하다. game변수는 속성이고 초기화하고 있으며 속성 이니셜라이저는 self가 존재하기 전에 실행되어야 한다.

스위프트에서는 어떤 거라도 사용하기 전에는 완전히 초기화를 해야한다. 어떤 변수에 접근하거나 어떤 함수를 부르거나 여튼 무엇을 하든 초기화가 되어있어야 한다. 그러나 아직 우리는 game을 초기화하는 중이었기 때문에 완벽한 초기화가 되어있지 않다. 이렇게 지금 상황은 하나가 다른 하나에 의존하고 있는 상황이다.

이를 해결하는 방법이 있다.

  1. lazy
lazy var game = Concentration(numberOfPairsCards: (cardButtons.count + 1) / 2)

어떤 변수를 lazy로 만들면 누가 사용하기 전까지는 초기화하지 않는다. 누군가 game을 사용하려 할 때 초기화를 한다. 즉 cardButtons 변수가 초기화될때까지 아무도 game변수를 사용할 수 없다. 이렇게 lazy를 사용하면 초기화가 되었다고 쳐주게 된다. 그러나 이런 lazy를 사용할때 한가지 제약사항이 있다. lazy가 되면 didSet을 가질 수 없다.

lazy var game = Concentration(numberOfPairsCards: (cardButtons.count + 1) / 2) {
  didSet {}
}

lazy변수는 속성관찰자를 사용할 수 없다는 의미이다. 속성 관찰자를 사용하려면 game이 변하는 모든 곳을 찾아서 다른 방법으로 해야한다.

func updateVireFromModel() {
    for index in cardButtons.indices {

    }
}

indices는 배열의 메소드로 모든 인덱스의 계수 가능 범위를 배열로 리턴해준다.

arc4:유사(pseudo) 임의 번호 생성기이다. 0부터 상한 사이의 숫자를 임의로 생성해준다.

Stanford Lecture1 - Introduction to iOS11, Xcode9 and Swift4

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
Stanford Developing iOS11 Apps with Swift를 듣고 정리하였습니다.


What’s in iOS?

iOS는 네개의 층으로 구성되어 있다. 각 층은 Cocoa Touch, Media, Core Services, Core OS이다.
맨 아래층은 하드웨어에 가깝고, 맨 위층은 사용자에 가깝다.

  • Cocoa Touch: 화면이나 버튼 등 유저 인터페이스(UI)와 관련된 layer
  • Media: 음악, 영상, 애니메이션, 사진 등과 관련된 layer
  • Core Services: Foundation이 포함된 layer로 네트워킹, 데이터베이스, 파일관리와 관련된 layer
  • Core OS: C언어로 짜여진 유닉스 OS layer로 앱 개발자로서 거의 직접 접근하지 않는 layer

Xcode 초기 설정

Main.storyboard

UI. xcode를 통해 UI를 그래픽으로 구현 -> 코드를 작성할 필요없이 그래픽으로 구현
버튼, 텍스트필드, 슬라이드 등을 드래그하여 사용하고 버튼과 슬라이더가 스크린에 실시간으로 나타나 원하는대로 편집하고 실행하면 됨

ViewContrller.swift

Main.storyboard에서 만든 UI와 코드를 연결 하는 곳
UIKit는 버튼과 슬라이더 등이 있는 iOS 프레임워크 -> 코코아터치 층 같은 것과 비슷

import UIKit

class ViewController: UIViewController {
  // UIViewController는 UIKit에 들어있고 SuperClass로 ViewContrller는 UIViewController로부터 상속을 받음으로써 제어하는 모든기능을 상속받음
}

카드만들기 - UIButton 써보기

카드를 구성하기 위해 Button이라는 객체를 사용하게 된다. 이 Button은 View를 상속받고 있기 때문에 배경화면을 설정할 수 있다.

UI에 있는 항목 조작하기

UI에 있는 항목을 조작하기 위해서는 실행에 대해 코드를 연결해야 한다.

action: 버튼을 눌렀을 때 메서드를 호출하라는 의미

이 메서드는 인수(arguments)를 가질수도 있고 가지지 않을 수도 있다.
None 은 인수가 없어도 괜찮은 것이고 이외에는 sender 로 설정. 이 경우에는 UIKit를 보내주는 인수를 의미함으로 인수는 꼭 필요하다.

즉, touchCard가 넘어오면 뒤집을 수 있어야 하는데 이러한 소통을 위해 인수가 존재한다.

Type은 인수의 타입으로 Any가 되어있는데 Button으로 변경해줘야 한다.
버튼이 메소드를 보내주는 것이기 때문으로 만약 UIButton으로 변경해주지 않으면 나머지 코드가 작동하지 않는다.

event는 Touch Up Inside로 경계안에서 터치하고 손을 뗄 때 이 메시지를 보내라는 뜻을 가진다.

IBAction와 _ 의미

13번째 줄이 원으로 바뀌었는데, 원에 마우스를 올려보면 어떤 버튼이 메소드를 부르는지 보여준다.

swift의 모든 인수에는 이름이 있으며 메소드를 부를때 이 이름을 포함해야 한다. 즉, 메소드를 호출하거나 코드를 읽을 때 첫번째 인수가 무엇인지 기억할 필요가 없다. 그리고 이 인수의 이름은 두개로 하나는 호출할 때 사용하는 외부이름 이고 하나는 우리가 코드 구현에 사용할 내부 이름 이다.

이름을 하나만 가지는 것도 가능하고 유효하다. withemoji가 emoji가 된다면 외부, 내부 이름 모두 emoji가 된다.
그리고 만약 인수 앞에 _ 가 있다면 그것은 인수가 없다는 것을 의미한다. (거의 사용하지는 않는다.)

touchCard에는 _ 가 있는데 iOS에서 메시지를 보내는 것으로 이건 Objective-C에서 왔고 여기서는 내부 외부 이름이 없었기 때문에 있다.

@IBAction func touchCard(_ sender: UIButton)

카드뒤집기

카드를 클릭했을 때 카드가 뒤집히는 효과를 주기 위해 함수를 하나 선언한다. 두개의 인자를 받는 함수이며, 개별 인자에 대해 ‘외부이름’과 ‘내부이름’을 설정한다. 실제 Button을 클릭했을때, flipCard라는 함수가 호출되고 해당 함수에 두가지 인자를 전달하게 된다. 첫번째 인자는 고스트 이모지이며, 두번째 인자는 눌러진 버튼이다. 이렇게 flipCard로 전달된 두개의 인자를 이용해 우리가 원하는 결과(카드 뒤집기)를 만들어낸다.

func flipCard(withEmoji emoji:String, on button: UIButton) {}

근본적으로 flipCard는 토글이다. flipCard 메소드에게 버튼을 확인해서 이미 유령이면 텍스트 없이 주황색으로 뒤집게 한다.
아니라면 흰 바탕에 유령이 있게 하면 된다. 최소한의 값은 normal state 로 설정해야한다.

func flipCard(withEmoji emoji:String, on button: UIButton) {
    // print("flipCard(withEmoji: \(emoji))")

    // 버튼의 현재 타이틀이 유령으로 되어있는지를 확인
    if button.currentTitle == emoji {
        button.setTitle("", for: UIControl.State.normal)
        button.backgroundColor = #colorLiteral(red: 1, green: 0.5763723254, blue: 0, alpha: 1)
    } else { // 유령이 없다면 유령을 넣어줘야 함
        button.setTitle(emoji, for: UIControl.State.normal)
        button.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
    }
}

button.currentTitle에 따라 다른내용을 수행하게 한다.

토글? 하나의 설정값으로부터 다른 값으로 전환하는것으로 오직 두가지 상태밖에 없는 상황에서 스위치를 한번 누르면 한 값이 되고, 다시 한번 누르면 다른 값으로 변하는 것을 의미한다.

몇번 뒤집는지 확인해보기

이와 같은 방식으로 선언을 하게 되면 에러를 만나게 된다.

swift는 모든 인스턴스 변수(속성)은 초기화를 해야한다. 즉, 속성엔 초기값이 항상 있어야 한다.

초기화 방법에는 두가지가 있다.

  1. 이니셜라이저를 이용.
    • 이니셜라이저는 단지 특별한 이름을 가진 메소드일 뿐이다. -> init
    • init은 어떤 인수든 다 가질 수 있고 다른 인수를 가진 여러개의 init을 만드는것도 가능하지만 각 init은 모든 변수를 초기화한다.
  2. 값에 0을 쓰는것
var flipCount = 0

위와 같이 0을 할당함으로써 간단하게 초기화하도록 한다. swift의 경우 타입에 매우 엄격하다. 사용되는 거의 모든 변수는 타입을 가지며, Objective-C와의 호환을 위해 타입이 없는 경우가 종종있을 뿐이다. 변수의 타입을 명시적으로 적을수도 있지만, 특정 값을 할당하게 되면 해당 변수의 타입을 추론하기도 한다.

다른 카드 만들기 - UILabel 써보기

UILabel: 읽기전용 텍스트 필드

outlet: 인스턴스 변수(속성)를 만든다.
이 속성은 UILabel을 가리키고 횟수가 바뀌면 스스로를 업데이트 하라고 말해준다.

@IBOutlet weak var flipCountLabel: UILabel!

!는 일단 굳이 초기화를 하지 않아도 된다는 특징이 있다. 초기화를 하지않아도 에러가 뜨지 않는다.

모든 속성을 원한다면 속성 다음 didSet이라는 코드를 추가할 수 있다. didSet을 사용하게 되면 매번 flipCountLabel의 텍스트 값을 할당하는 코드를 넣어주지 않고도 원하는 결과값을 얻을 수 있게 된다. 프로퍼티의 값이 바뀐 후에 실행되는 프로퍼티 감시다(didSet)이다.

var flipCount = 0 {
     didSet {
         flipCountLabel.text = "Flpis: \(flipCount)"
     }
 }

그러면 이 속성이 설정될때마다 이 안의 코드가 실행된다.
이를 속성감시자 라고 하고 이 코드가 변화를 감시하고 있기 때문에 해당 코드를 꺼내 붙이고 없앨 수도 있다.

즉, filpCount가 바뀔때마다 didSet을 실행하고 레이블을 통해 업데이트를 할 것이다.
UI와 인스턴스 변수의 싱크를 맞추기 위해 속성감시자는 많이 사용된다

여러장의 카드 - 배열

여러장의 카드를 만들기 위해서 버튼들을 배열에 넣고 인덱스를 통해 조작하도록 하면 새롭게 생기게 되는 모든 카드에 대해 반복적으로 함수를 선언할 필요가 없다.

UI와 코드의 연결이기 때문에 드래그로! var를 하나 더 만든다.

connection:Outlet Collection > UI에 잇는 것들의 배열(ULButton의 배열이라는 뜻의 특별한 문법이다)

@IBOutlet var cardButtons: [UIButton]!

optional

여러장의 카드를 만들기 위해 배열을 만들엇다면 이 배열속에 저장된 버튼의 인덱스 값을 접근하기 위해 아래와 같은 코드를 작성한다.

let cardNumber = cardButtons.lastIndex(of: sender)

이때 인덱스에 Option+click을 하게 되면 반환값이 Int가 아닌 Int?임을 알 수 있다.

index의 리턴값은 optional 값이다. 물음표가 그 뜻이고 ?는 optional이라는 뜻이다.
optional과 int는 전혀 다른 타입이고 int와는 아무런 상관이 없다

optional은 값이 있거나 없는 두가지 경우만 존재한다. optional 타입인 변수에 값이 할당되지 않은 상태에서 값이 있다고 변수를 사용하게 되면 충돌이 일어나게 된다.

예상하지 못한 nil을 발견했다는 뜻으로 optional을 푸는 과정에서 발생했다는 의미의 에러이다. 이 optional을 정상적으로 사용하기 위한 방법으로 해당 타입의 뒤에 느낌표(!)를 붙이거나 if let 조건문을 활용하는 방법이 있다.

optional 문법은 정말 간단하게 ? ! 이런식으로 되어있는데, 이는 optional이 굉장히 흔히 쓰이기 때문이다.

충돌을 일으키는 느낌표(!)

let cardNumber = cardButtons.lastIndex(of: sender)!

느낌표를 사용하는 경우 연결되지 않은 애를 클릭하면 충돌이 일어나고 이 충돌이 일어나는 이유는 설정되지 않은 optional을 리턴하기 때문이다. 즉 관련된 값이 없으니 프로그램을 충돌시킨다.

그러나 이렇게 충돌이 일어나지 않는 방법을 쓰고 싶은 경우도 있다.

충돌을 일으키지 않는 if let 조건문

조건적으로 설정된 상태에 있는 지 보고 맞으면 쓰고 아니면 쓰지 않도록 한다.

if let cardNumber = cardButtons.lastIndex(of: sender)

이제 이 optional이 설정된 상태에 있다면 코드가 실행되고 아니라면 실행되지 않는다.충돌은 일어나지 않는다.

cardNumber를 이모티콘 배열에서 찾아보기

var emojiChoices = ["👻","🎃","👻","🎃"]

전체 소스코드

import UIKit

class ViewController: UIViewController {

    var flipCount = 0 {
        didSet {
            flipCountLabel.text = "Flpis: \(flipCount)"
        }
    }

    @IBOutlet var cardButtons: [UIButton]!

    @IBOutlet weak var flipCountLabel: UILabel!

    var emojiChoices = ["👻","🎃","👻","🎃"]

    @IBAction func touchCard(_ sender: UIButton) {
        flipCount += 1
//        flipCountLabel.text = "Flpis: \(flipCount)"
//        flipCard(withEmoji: "👻", on: sender)
        if let cardNumber = cardButtons.lastIndex(of: sender) {
            flipCard(withEmoji: emojiChoices[cardNumber], on: sender)
            print("cardNumber: \(cardNumber)")
        } else {
            print("chosen card was not in cardButtons")
        }
    }


    func flipCard(withEmoji emoji:String, on button: UIButton) {
        // print("flipCard(withEmoji: \(emoji))")
        // 버튼의 현재 타이틀이 유령으로 되어있는지를 확인
        if button.currentTitle == emoji {
            button.setTitle("", for: UIControl.State.normal)
            button.backgroundColor = #colorLiteral(red: 1, green: 0.5763723254, blue: 0, alpha: 1)
        } else { // 유령이 없다면 유령을 넣어줘야 함
            button.setTitle(emoji, for: UIControl.State.normal)
            button.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
        }
    }
}
  • 참고: UI와 코드 모두 건드리는 것의 이름을 바꿀때는 cmd+rename