0. 변수와 자료형

0.1 변수의 형태

var - 변경 가능한 변수.
let - 변경 불가능한 상수.

// 형식1
let (또는 var) 이름: 타입 = 값
let role: String = "Captain"

// 형식2 - 값의 타입이 명확할 때 사용.
var (또는 let) role = "Captain" 

0.1.1 let 값 변경 시도하기

var one = 1
let two = 2

one = 3 // ok
two = 3 // compile error

0.1.2 상수 선언 후 값 변경하기

선언을 한 뒤에 값을 할당하려면 반드시 타입을 명시해야 한다.

// 선언
let sum: Int
let inputA: Int = 100

// 선언(첫 번째 줄) 후 할당.
sum = inputA + 200

// let이기 때문에 아래 줄은 오류를 발생시킴
sum = inputA + 300

0.2 자료형

let inplicitDouble = 80.0
// 여기서는 swift가 변수의 타입을 Double이라고 '추측'한다.

let explicitDouble: Double = 80
// 여기서 'Double'을 type annotation이라고 한다.

숙제 : 자바 사용자들에게 자바는 타입 추론이 가능하냐고 약올려보자.


0.2.1 자료형의 종류

자료형에는 다음의 종류가 있다.

  • bool
  • Int, UInt
  • Float, Double
  • Character, String

여기서 UIntUnsigned Int 의 줄임말로, 양수값을 가지는 Int 를 의미한다.


Int, UInt - 64bit

Float - 32bit

Double - 64bit

Character, String - 큰따옴표("") 사용

var someBool: Bool = true // or false

var someInt: Int = -100 // 100.1 이면 error

var someUInt: UInt = 100 
// -100 이면 error
// someUInt = someInt -> error

var someFloat: Float = 3.14

var someDouble: Double = 3.14

var someCharacter: Character = "A" // "ABC" -> error

var someString: String = "ABC"

0.2.2 bool

다른 언어의 경우, 특히 swift와 많이 닮아 있는 c 의 경우 bool 타입에 0을 주면 false, 1을 주면 true 로 인식한다.

과연 swift에서도 그럴까?

정답은 아니오 이다. swift에서는 다음 코드들에 대해 오류를 발생시킨다.

var someBool: Bool = true
someBool = 0 // error
someBool = 1 // error

0.3 형변환

var width = 94

var stringWidth = width - (x) 

var stringWidth = String(width)  - (o) 

0.4 문자열 안에 숫자 변수 넣기

let apples = 3
let appleString = "I have \(apples) apples."

\(변수명)


0.5 배열과 딕셔너리

let shoppingArr = ["bag", "shoes", "ball", "clothes"]
shoppingArr[0] = "Chanel Bag"

let nameJobDic = [
  "kchoi" : "Captain",
  "ki" : "Soccer Player",
]
nameJobDic["kchoi"] = "Programmer"

0.5.1 배열, 딕셔너리, 세트 타입 어노테이션

Array - 순서가 있는 리스트 컬렉션

Dictionary - 키-값 으로 이우러진 컬렉션

Set - 순서가 없고 멤버가 유일한 컬렉션

let shoppingArr: Array<String> = Array<String>()
let shoppingArr2: [String] = ["bag", "shoes"]

let nameJobDictionary: Dictionary<String, Any> = [String, Any]()
let nameJobDicionary: [String : Any] = [
  "kchoi" : "Captain",
  "ki" : 3,
]

let integerSet: Set<Int> = Set<Int>()
integerSet.insert(1)
integerSet.insert(2)
integerSet.insert(2) // 오류는 안 일어나지만 Set 에 반영 x

0.5.2 빈 배열과 빈 딕셔너리

var shoppingArr = [String]()
var nameJobDic = [String : String]()

0.6 튜플

튜플은 어떤 값들을 모아놓은 것이다. 배열과 비슷하지만 길이가 고정되어 있다.


튜플의 선언 및 접근

var fruitTuple = ("banana", 5100)
fruitTuple.0 // banana
fruntTuple.1 = 3200
fruitTuple.1 // 3200

튜플의 파라미터는 이름을 가질 수 있다.

var namedFruitTuple = (kind: "banana", price: 5000)
namedFruitTuple.kind // banana
namedFruitTuple.price = 3200
namedFruitTuple.price // 3200

0.6.1 튜플을 이용해 여러 변수에 값 한번에 담기

let (fruit, price) = ("banana", 2300)
fruit // banana
price // 2300

0.6.2 튜플을 반환하는 함수 만들기

func fruitInfo(name: String) -> (name: String, price: Int)? {
  let fruitInfoList: [(name: String, price: Int)] = [
    ("banana", 5100),
    ("strawberry", 3200)
  ]
  for fruit in fruitInfoList {
    if fruit.name == name {
      return fruit
    }
  }
  return nil
}

fruitInfo(name: "banana")?.price //5100
fruitInfo(name: "starwberry")?.price //nil

1. 흐름 제어


1.1 for, while, do-while, for in

let scores = [41, 123, 53, 27, 86];
var max = 0;
for score in scores {
  if max < score
    max = score
}

while scores.count > 1 {
  scores.removeLast()
}

1.1.1 for in 응용 - 이중 for in

let interestingNumbers = [
  "Prime": [2, 3, 5, 7, 11, 13],
  "Fibonacci": [1, 1, 2, 3, 5, 8],
  "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
  for number in numbers {
    if number > largest {
        largest = number
    }
  }
}

1.1.2 for in 응용 - .. 그리고 ...

for i in 0..3 {
  println(i)
} // 0 1 2

for i in 0...3 {
  println(i)
} // 0 1 2 3

1.1.3 for in 응용 - _

단순 반복의 경우 i라는 키워드 대신 _ 를 써도 무방하다.

for _ in 0..<10 {
  println("Hello");
}

1.1.4 repeat-while

기존의 do-while과 유사.

repeat {
  scores.removeLast()
} while scores.count > 0

1.2 if문

var age = 19
var grade = ""

if age > 7 && age < 14 {
  grade = "elementary"
} else if age < 17 {
  grade = "middle"
} else {
  grade = "high"
}

1.2.1 if문 주의사항

var number = 0
if !number {
  ...
} // compile error!

다른 언어처럼 !number 이렇게 쓰면 안 된다.

대신 if number == 0 이렇게 쓸 수는 있다.


1.3 switch-case

let hero = "IronMan"
switch hero {
  case "BlackWidow":
    let say = "That's not my story"
  case "SpiderMan", "BlackPanther":
    let say = "Lee bum bae"
  case let marvel where marvel.hasSuffix("Man"):
    let say = "I am the IronMan"
  default:
    let say = "I can do this all day"
}

1.3.1 switch-case 패턴 매칭

다른 언어와는 달리 swift에서는 switch-case 에서 범위 탐색이 가능하다.

그러니까 특정 숫자가 특정 범위 내에 있는지 검사가 가능하다는 뜻이다.


switch age {
  case 8..<14:
    grade ="elementary"
  case 14..<17:
    grade = "middle"
  default
    grade = "high"
}

1.4 옵셔널(Optional)

옵셔널은 Swift 만의 특징이다.

해석하자면 값이 있을 수도 있고 없을 수도 있는, 선택적인 것이다.

nil의 가능성을 명시적으로 표현하기 위해 사용한다.


값이 있는 것 - "hi", "hello", "", 10, -2, 0, [], [:]

값이 없는 것 - nil


변수에 nil 을 할당할 수 있을까 ?

var name: String = "kchoi"
name = nil
// compile Error!

일반 변수에 nil 을 할당하려 하면 에러가 난다.

값이 있을 수도 있고 없을 수도 있는 변수의 정의에는 타입 어노테이션에 ? 를 붙인다.

(?는 붙어있어야 한다.)

이렇게 정의한 변수를 옵셔녈(Optional) 이라고 한다.

옵셔널에 초기값을 지정하지 않으면 nil 이다.


ex)

var name: String?
print(name) // nil

name = "swift"
print(name) // Optional("swift")

옵셔널로 정의한 변수는 다른 타입의 변수와 확실히 구별되는 것이다.

따라서 상호 간에 자동 변환되지 않는다.


var optinalName: String? = "swift"
var name: String = optionalName
// compile error!

1.4.1 옵셔널 바인딩(Optional Binding)

옵셔널 바인딩은 옵셔널의 값이 존재하는지를 검사한 뒤, 존재한다면 그 값을 다른 변수에 대입시킨다.

nil을 체크하는 것과 동시에 값을 안전하게 추출하는 방식이다.

이 경우 if let 또는 if var 를 사용한다.


if let email = optionalEmail {
  print (email)
}

위의 코드에서 optionalEmail 에 값이 있다면 email 변수에 optionalEmail 의 값이 담기고 if 문이 실행된다.

그러나 optionalEmail 의 값이 nil 이라면 if 문을 그냥 지나쳐버린다.


1.4.2 옵셔널 바인딩 여러 개 사용하기

옵셔널 바인딩은 여러 가지를 함께 사용할 수 있다.

이 경우 하나라도 nil 이 들어있으면 전부 다 뛰어넘는다.


var optionalName: String ? = "kchoi"
var optinalEmail: String ? = "github.com/choikanghun"

if let name = optinalName, let email = optionalEmail {
  ...
}

위 코드는 다음과 같이 쓸 수도 있다.

if let name = optinalName {
  if let email = optionalEmail {
    // name과 email 모두 nil 이 아니어야 실행.
  }
}

1.4.3 옵셔널바인딩 + 조건

옵셔널 바인딩을 하면서 조건을 걸 수도 있다.

다음 코드에서는 age에 optionalAge의 값이 할당되지만 if 문은 실행되지 않는다.


var optionalAge: Int? = 19

if let adult = optionalAge, adult >= 20 {
 ... 
}

1.5 옵셔널 체이닝(Optinal Chaining)

Swift는 처음에는 접근하기 불편하더라도 한번 맛을 들이고 나면 다른 언어를 쓰기 싫을 만큼 편한 요소들이 있다. 그 중 하나가 옵셔널 체이닝이다.


옵셔널 체이닝을 이해하는 데에는 코드를 보는 게 더 빠를 것이다.

// 옵셔널 체이닝 x
func guardJob(owner: Person?) {
  if let owner = owner {
    if let home = owner.home {
      if let `guard` = home.guard {
        if let guardJob = `guard`.job {
          print("경비원의 직업은 \(guardJob)입니다.")
        } else {
             print("경비원의 직업이 없습니다.") 
        }
      }
    }
  }
}

// 옵셔널 체이닝을 사용한다면
func guardJobWithOptionalChaining(owner: Person?) {
  if let guardJob = owner?.home?.guard?.job {
    print("경비원의 직업은 \(guardJob)입니다.")
  } else {
        print("경비원의 직업이 없습니다.")
  }
}

만약 옵셔널로 선언된 배열이 '빈 배열(nil이 아닌 것)'인지 검사하려면 어떻게 해야 할까?

다음과 같이 확인할 수 있을 것이다.


let array: [String]? = []
var isEmptyArray = false

if let array = array, array.isEmpty {
  isEmptyArray = true
} else {
  isEmptyArray = false
}

isEmptyArray

그러나 위의 코드는 옵셔널 체이닝 을 사용하면 더 간결하게 표현할 수 있다.

let isEmptyArray = array?.isEmpty == true

위의 코드에서 옵셔널 바인딩 과정을 ? 키워드로 하였다.

좀 더 쉽게 이해하기 위해 세 가지 경우의 수를 따져보자.


첫째, arraynil 일 때

array?isEmpty == true
______
여기까지 실행되고 nil 을 반환한다.

둘째, array 가 빈 배열인 경우

array?isEmpty == true
---------------------
여기까지 실행되고 true 를 반환한다.

셋째, array 에 요소가 있는 경우

array?isEmpty == true
---------------------
여기까지 실행되고 false 를 반환한다.

1.6 옵셔널 벗기기


개발을 하다 보면 값이 분명히 존재해야 하는 변수도 옵셔널로 사용하는 경우가 생긴다.

이 때에는 옵셔널에 값이 있다고 가정하고 바로 접근할 수 있도록 도와주는 키워드를 쓴다.

그 키워드는 ! 이다.


Ex)

print(optionalName) // Optional("kchoi")
print(optionalName!) // kchoi

참고로 다음처럼 nil 옵셔널에 ! 를 쓰면 런타임 에러를 발생시킨다.

var optionalName: String?
print(optionalName!) // Runtime error

참고로 런타임 에러 발생시 ios 앱은 강제종료된다.


1.7 nil 병합 연산자

Optional 이 아닌 일반 변수에, 어떤 옵셔널 변수의 값을 넣는다고 칠 때 if else 구문을 쓰지 않고 값을 간편하게 할당하는 방법이 있다. 바로 nil 병합 연산자를 쓰는 것이다. 형태는 ?? 이다.


ex)

var job: String
home?.guard?.job = "경비원"
job = home?.guard?.job ?? "슈퍼맨"

print(job) // "경비원"

home?.guard?.job = nil
job = home?.guard?.job ?? "슈퍼맨"
print(job) // "슈퍼맨"

2. 함수와 클로저(Function & Closure)


2.1 함수(func)

func 키워드를 사용한다. -> 를 사용하여 반환 타입을 지정한다.

swift의 특징은, 함수를 호출할 때마다 함수 인자의 이름을 써주어야 한다는 것이다.

func 함수명(인자1: 인자타입, 인자2: 인자타입) -> 반환타입 {
  return ~
}

func heroes(name: String, say: String) -> String {
  return "\(name): \(say)."
}
heroes(name: "Captain", say: "I can do this all day")

2.1.1 함수 파라미터 이름 생략하기

파라미터 이름에 _ 를 붙이면 파라미터 이름을 생략할 수 있게 된다.

func helloFunc(_ name: String, time: Int) {
  ...
}

helloFunc("Captain", time: 3)

2.1.2 함수 - 여러 값 반환

func getHeroesName() -> (String, String, String) {
  return ("Mother", "Father", "IronMan")
}
getHeroesName()

2.1.3 함수 - 여러 인자 받기

func getSum(numbers: Int...) -> Int {
  var answer = 0
  for number in numbers {
    answer += number
  }
  return number
}
println(getSum(42, 123, 1442))

2.1.4 함수 - 함수 중첩시키기

func ten() -> Int {
  var y = 5
  func add() {
    y += 5
  }
  add()
  return y
}
ten() //10

2.1.5 함수 - 함수를 리턴하기

func getAddFunc() -> (Int -> Int) {
  func addFunc(num: Int) -> Int {
    return 1 + number
  }
  return addFunc
}
var increment = getAddFunc()
increment(5) //6

2.1.6 함수 - 인자로 함수 받기

func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool
{
  for item in list {
    if condition(item) {
      return true
    }
  }
  return false
}
func lessThanTen(number: Int) -> Bool {
  return number < 10
}
var numbers = [12, 45, 23, 8]
hasAnyMatches(numbers, lessThanTen)

2.2 함수 인자 이름 다르게 받아 쓰기

함수 내에서 쓰는 용어와 받을 때 쓰는 용어를 다르게 쓰고 싶다면 다음과 같이 하면 된다.

func helloFunc(to name: String, numberOfTimes time: Int) {
  for _ in 0..<time {
    print(name)
  }
}

helloFunc(to: "Captain", numberOfTimes: 3);

2.3 함수 인자의 기본값 설정하기

파라미터에 기본값을 지정하면 함수 호출 시 생략 가능한 인자가 된다.

func helloFunc(name: String, time: Int = 1) {
  ...
}

2.4 개수가 정해지지 않은 인자 받기

... 을 사용하면 개수가 정해지지 않은 파라미터를 받을 수 있다.

func sum(_ numbers: Int...) -> Int {
  var res = 0;
  for num in numbers {
    res += num
  }
  return res;
}

sum(1, 2) // 3 
sum(1, 2, 3, 4, 5) // 15

2.6 클로저 Closure

클로저를 사용하면 코드가 간결해진다.

클로저는 중괄호{ } 로 감싸진 실행 가능한 코드 블럭이다.


형식: { (매개변수 목록) -> 반환타입 in

​ 실행코드

}

func helloFunc(message: String) -> (String, String) -> String {
  return { (firstName: String, lastName: String) -> String in
          return lastName + firstName + message
  }
}

위의 코드를 보면 상당히 복잡하다. 위의 코드에서 클로저에 해당하는 부분은 첫 번째 return 문 내의 { ~ } 이다.

클로저도 함수처럼 파라미터를 받을 수 있고, -> 를 이용해서 반환 타입도 쓸 수 있다.

함수와 하나 다른 점은 in 이라는 키워드를 사용하여 파라미터, 반환 타입 영역과 실제 클로저 영역을 분리한다.

클로저는 함수와 매우 유사하다. 참고로 함수를 이름 있는 클로저라고 하기도 한다.


위의 코드에서 클로저 부분을 좀 더 줄일 수도 있다.

먼저, 1번 라인에서 String, String을 받아 String을 내보낸다는 것을 이미 알려줬다.

따라서 코드를 다음과 같이 줄일 수 있다.

func helloFunc(message: String) -> (String, String) -> String {
  return { firstName, lastName in
         return lastName + firstName + message
  }
}

여기서 더 줄일 여지도 남아 있다.

클로저는 첫 번째 인자를 $0 두 번째 인자를 $1 이라고 쓸 수 있다.

즉, 위의 코드를 아래와 같이 바꿀 수 있는 것이다.

func helloFunc(message: String) -> (String, String) -> String {
  return {
    return $1 + $0 + message
  }
}

만약 클로저 내부의 코드가 한 줄이라면 return 을 생략해도 된다.

func helloFunc(message: String) -> (String, String) -> String {
  return { $1 + $0 + message }
}

2.6.1 클로저를 변수처럼 정의하기

let hello: (String, String) -> String = { $1 + $0 + "님 안녕하세요~"}
hello("America", "Captain")

2.6.2 클로저를 옵셔널처럼 정의하기

let hello: ((String, String) -> String)?
hello?("America", "Captain")

2.6.3 클로저를 파라미터로 받기

func helloFunc(number: Int, block: Int -> Int) -> Int {
  return block(number)
}

helloFunc(number: 5, block: { (number: Int) -> Int in
  return number * 2
})

아까 말했듯 위 코드는 생략가능한 부분이 존재한다.

helloFunc(number: 5, block: {
  return $0 * 2
})

2.6.4 함수와 클로저 비교

함수의 경우

func sumFunction(a: Int, b: Int) -> Int {
    return a + b
}

var sumRes: Int = sumFunction(a: 1, b: 2)

클로저의 경우

var sumClosure: (Int, Int) -> Int = { (a: Int, b: Int) in 
    return a + b
}

var sumResult = sumClosure(1, 2)

2.6.5 후행 클로저

2.6.5 부터 3. 이전까지는 다음의 함수가 있다고 가정한다.

func calculate(a:Int, b:Int, method (Int, Int) -> Int) -> Int {
  return method(a, b)
}

클로저가 함수의 마지막 전달인자인 경우, 마지막 매개변수 이름을 생략한 후 함수 소괄호 외부에 클로저를 구현할 수 있다.

var res: Int
res = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
  return left + right
}

2.6.6 반환타입 생략하기

swift는 method라는 클로저가 Int를 리턴한다는 것을 이미 알고 있다.

따라서 반환타입을 생략할 수 있다.

var res:Int
res = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> in
    return left + right                                      
})

3. 객체(Object)와 클래스(Class)


3.1 클래스

클래스는 내부에 변수, 함수 등을 가짐.

class Person {
    var name: String = ""

    func selfIntroduce() {
        print("저는 \(name)입니다")
    }

    // final 키워드를 사용하여 재정의를 방지할 수 있습니다
    final func sayHello() {
        print("hello")
    }

    // 타입 메서드
    // 재정의 불가 타입 메서드 - static
    static func typeMethod() {
        print("type method - static")
    }

    // 재정의 가능 타입 메서드 - class
    class func classMethod() {
        print("type method - class")
    }

    // 재정의 가능한 class 메서드라도 
    // final 키워드를 사용하면 재정의 할 수 없다
    // 메서드 앞의 `static`과 `final class`는 똑같은 역할을 한다
    final class func finalCalssMethod() {
        print("type method - final class")
    }
}

3.1.1 클래스의 인스턴스(객체) 만들기.

class kchoi = Person()
kchoi.name = "kchoi"
var kchoiFunc = kchoi.classMethod()

3.2 상속과 오버라이드

상속은 class 이름 뒤에 : 상속받을 클래스 를 붙여 만든다.

override할 때에는 반드시 override를 써준다. 써야 하는데 안 쓰면 컴파일러가 오류를 발생시키고, override할 게 아닌데 override가 붙어있어도 컴파일러가 오류를 던진다.


// Person을 상속받는 Student
class Student: Person {
    var major: String = ""

    override func selfIntroduce() {
        print("나는 \(name)이고, 전공은 \(major)")
    }

    override class func classMethod() {
        print("overriden type method - class")
    }

// static을 사용한 타입 메서드는 재정의 할 수 없다
// override static func typeMethod() {    }

// final 키워드를 사용한 메서드, 프로퍼티는 재정의 할 수 없다
// override func sayHello() {    }
// override class func finalClassMethod() {    }
}

3.3 클래스의 생성자(init)

클래스나 구조체의 필드(아래의 name)는 optional 이거나 초깃값을 가져야 한다.

다만 생성자에서 초깃값을 지정하는 경우에 한해 초깃값을 생략할 수 있다.

class Person {
    var name: String
    init (name: String) {
      self.name = name //여기서 self는 다른 언어의 this의 역할
    }
 ...
}

3.3.1 클래스를 상속한 클래스의 생성자

어떤 클래스가 다른 클래스를 상속하는 경우, 생성자에 특별한 규칙이 생긴다.

첫째, 옵셔널이 아닌 모든 속성(필드)에 초기값을 설정할 것.

둘째, 상위 클래스 생성자를 호출할 것.


ex)

class Animal {
  var breed: String;

  init() {
    self.breed = "lebrado"
  }
}

class Dog: Animal {
  var name: String?
  var age: Int

  override init() {
    self.age = 4 // 1번 규칙. 초기값 설정
    super.init() // 2번 규칙. 상위클래스 생성자 호출.
    print(self.description()) // 여기서부터 self 호출 가능.
  }

  func description() -> String {
    if let name = self.name {
      return "I am \(name)"
    } else {
      return "I have no name"
    }
  }
}

3.3.2 두 개의 init

Swift 에서는 두 개의 생성자를 만들 수 있다.

아래 예제에서 인자를 두 개만 넣으면 nickName 에는 nil 이 담긴다.

class Person {
  var name: String
  var age: Int
  var nickName: String?

  init(name: String, age: Int, nickName: String) {
    self.name = name
    self.age = age
    self.nickName = nickName
  }

  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

3.3.3 init 실패시키기

생성자가 nil 을 반환하게 되면 그것은 생성에 실패한 것이다.

아래 코드에서 만약 사용자가 0 ~ 120에 해당하지 않는 숫자를 age에 세팅하고자 하면 구조체는 nil 을 반환한다.

struct PersonS {
  var age: Int

  init?(age: Int) {
    if (0...120).contains(age) == false {
      return nil
    }
    self.age = age
  }
}

let john: PersonS? = PersonS(age: 30)
let john: Person = PersonS(age: 20) // error! optional 요구됨.

3.3.4 deinit

클래스 또는 구조체가 메모리에서 해제되는 시점에 실행되는 것을 deinit 으로 구현 가능하다.

예를 들어 다음 코드에서는

struct PersonE {
  var age;

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

  deinit {
    print("died age of \(self.age)")
  }
}

var p: PersonE? = PersonE(age: 99)
print(p.age) // 99
PersonE = nil // died age of 99

3.4 속성(Properties)

예로 든 클래스 내의 var age: Int 같은 것을 속성이라고 한다.

이 속성은 크게 두 가지로 분류된다.

위의 age 같은 것이 값을 저장하는 속성(Stored Property) 이고,

다른 하나는 계산되는 속성(Computed Property)이다.

둘의 차이점은 속성이 값을 가지고 있는지, 아니면 어떠한 연산을 수행하고 그 결과를 반한하는지의 차이다.


우리가 지금까지 사용한 속성은 모두 Stored Property 이다.

Computed Property는 get, set을 사용하여 정의할 수 있다.

예제를 두 가지 정도 살펴보자.


예제1.

struct Money {
  var currencyRate: Double = 1100
  var dollar: Double = 0
  var won: Double {
    get {
      return dollar * currencyRate
    }
    set {
      dollar = newValue / currencyRate
    }
  }
}

var moneyIHave = Money()
moneyIHave.won = 11_000
print(moneyIHave.won) //11000
print(moneyIHave.dollar) //10

예제2.

Set 에서는 새로 설정될 값을 newValue라는 예약어를 이용해 접근할 수도 있다.

class DecToHex {
  var dec: Int?
  var hexString: String? {
    get {
      if let dec = self.dec {
        return String(dec, radix: 16)
      } else {
        return nil
      }
    }
    set {
      if let newValue = newValue {
        self.dec = Int(newValue, radix: 16)
      } else {
        self.dec = nil
      }
    }
  }
}

var decToHex = DecToHex()
decToHex.dec = 10
decToHex.hexString // "a", get

decToHex.hexString = "b" // set
decToHex.dec // 11

위 코드에서 hexString 은 직접 값을 가지는 것이 아닌, dec의 값을 계산하여 반환하는 값이다.

즉, dec은 Stored Property, hexString 은 Computed Property 인 것이다.

참고로, get만 정의할 경우에는 get 키워드를 생략할 수 있다. 이런 속성을 읽기 전용(Read Only) 라고 한다.


3.4.1 willSet, didSet(프로퍼티 감시자)

willSet - 속성이 set 되기 전에 실행. set될 때마다 실행.

didSet - 속성이 set 된 이후에 실행. set될 때마다 실행.

(React의 componentDidMount 같은 역할.)


class HexToDec {
  var dec: Int? {
    willSet {
      print("\(self.dec)에서 \(newValue)로 값이 바뀔 것이다.")
    }
    didSet {
      print("\(oldValue)에서 \(self.dec)로 값이 바뀌었다.")
    }
  }
}

willSet 은 새로운 값을 newValue 로 가져오고, didSet 에서는 예전 값을 oldValue 라는 예약어로 받아온다.


참고로 wiliest, didset 은 set, get 과 함께 쓸 수 없다.

4. 구조체

Swift 에서는 구조체가 대부분의 역할을 담당하고 있을 정도로 구조체가 아주 중요한 개념이다.

선언 방식은 다음과 같다.

struct 이름 {
  코드
}

예를 들어 다음과 같은 구조체를 선언했다고 할 때, 접근 방식은 다음과 같다.

struct TestStruct {
  var mutableProperty = 200
  let immutableProperty = 200
}

var mutableStruct: TestStruct = TestStruct()
mutableStruct.mutableProperty = 300

4.1.1 타입 메소드와 타입 프로퍼티

static 이라는 키워드를 붙여주면 타입 메소드 그리고 타입 프로퍼티를 사용할 수 있다.

조금 쓰면서 익숙해지는 게 물론 좋겠지만, 그래도 이해하기 쉽게 설명하자면

static 이라는 키워드가 붙는 녀석들은 메모리에 미리 올라가게 된다고 생각하자.

그렇기 때문에 프로그램이 돌아가는 동안 언제든지 불러서 쓸 수 있다.

인스턴스(객체)를 만들지 않아도 사용할 수 있는 것이다.


struct SampleStruct {
  var prop1: Int = 30
  static var typeProperty: Int = 100

  static func typeMethod() {
    print("this is type method")
  }
}

...

SampleStruct.typeProperty = 300
SampleStruct.typeMethod() // this is type method

var testStruct: SampleStruct = SampleStruct()
testStruct.typeProperty = 500 
// error!!!
// 인스턴스에서는 타입 메소드나 타입 프로퍼티를 사용할 수 없다. 

4.1.2 상수형 구조체

상수형 구조체에서는 값의 변경이 불가능하다. 아래처럼.

struct SampleStruct {
  var prop1: Int = 3
}

let testStruct: SampleStruct = SampleStruct()
testSTruct.prop1 = 50 //error!!

4.2 구조체 == 클래스?

구조체와 클래스는 전반적으로 다른 점이 거의 없다.

안에 속성을 가지고, 함수를 가지는 것 모두 동일하다.

struct Animal {
  var name

  init(name: String) {
    self.name = name
  }

  func introduceFunc() -> String {
    return "\(self.name)"
  }
}

4.2.1 구조체와 클래스의 차이점 1 - 상속

클래스는 상속이 가능하지만 구조체는 불가능하다. 추가적으로 Enum 또한 상속 불가.

클래스 구조체
단일상속 상속 불가
참조 타입 값 타임
Apple 프레임워크 대부분의 뼈대는 class Swift의 대부분의 뼈대는 구조체

4.2.2 구조체와 클래스의 차이점 2 - 참조와 복사

클래스는 참조(Reference)하고, 구조체는 복사(Copy)한다.

값 복사는 데이터를 전달할 때 값을 복사하여 전달한다.

참조는 데이터를 전달할 때 값의 메모리 주소를 전달한다.

var cat1 = Cat()  // cat1은 새로 만들어진 Cat()
var cat2 = cat1   // cat2는 cat1이 참조하는 Cat()을 참조.
cat1.name = "Tom" // cat1의 이름을 바꾸면
print(cat2.name)  // cat2가 참조하는 곳의 이름도 바뀜. 출력값: Tom

var fruit1 = Fruit()   // fruit1은 새로 만든 Fruit()
var fruit2 = fruit1    // fruit2는 fruit1을 '복사'
fruit1.name = "banana" // fruit1의 이름이 banana가 돼도
fruit2.name            // fruit2의 이름은 바뀌지 않음.

4.3 구조체는 언제 사용할까?

  • 다른 객체 또는 함수로 전달되는 경우 '참조'가 아닌 복사되길 원할 때
  • 상속의 필요가 없는 경우
  • Apple 프레임워크(iOS, Mac OS) 위에서 프로그래밍을 할 때에는 주로 클래스를 많이 씀.

5. Enum

Enum은 열거라는 뜻을 가진 Enumeration에서 나온 용어다. 한글로는 열거형 이라는 말을 자주 쓴다.

1월부터 12월까지를 열거 한다면 다음과 같다.


형식: var 변수명: enum이름 = enum이름.프로퍼티

enum Month: Int {
  case january = 1
  case february // 2, 1씩 증가
  case march  // 3
  case april
  case may
  case june
  case july
  case august
  case september
  case october
  case november
  case december  // 12


  func simpleDescription() -> String {
    switch self {
      case .january
        return "1월"
      case .february:
        return "2월"
      case .march:
        return "3월"
      ...
      case .december:
        return "12월"
    }
  }
}

var month = Month.november
month = .december
print(month.simpleDescription()) // 12월
print(month.rawValue) // 12

위의 예시에서는 rawValue 를 통해 december의 원시값 12를 가져온다.

반대로 원시값으로 Enum을 만들 수도 있다.

let oct = Month(rawValue: 10)
print(oct) // Opitional(Month.october)

위에서 Month(rawValue: 10) 의 반환값이 옵셔널인 이유는 정의되지 않은 원시값의 경우 Nil 을 반환하기 때문이다.

Month(rawValue: 13) // nil

5.1 다른 언어의 Enum과 스위프트의 Enum

다른 언어의 경우 Enum이 주로 int 값만 rawValue로 가진다.

그러나 swift의 경우 String을 원시값으로 가질 수 있다.

enum Door: String {
  case open = "open"
  case closed = "closed"
}

5.2 옵셔널은 사실 Enum 이다.

옵셔널은 다음과 같은 코드로 짜여져 있다.

enum Optional<Wrapped> : ExpressibleByNilLiteral {
  case none
  case some(Wrapped)
}

let optionalValue: Optional<Int> = nil
let optionalValue: Int? = nil

6. 프로토콜

프로토콜은 다른 언어의 인터페이스와 비슷하다.

가져야 할 properties 그리고 메소드를 정의'만' 한다.

프로토콜은 프로퍼티, 메서드, init 을 요구할 수 있다.

다만 프로토콜 내의 프로퍼티는 반드시 var 이어야 한다.

protocol Sendable {
  var from: String {get}
  var to: String {get, set}

  func send()

  init(from: String, to: String)
}

클래스 또는 구조체 또는 enum에 프로토콜을 적용(conform) 시킬 수 있다.

이 경우 클래스 또는 구조체가 프로토콜을 채택(Adopt) 했다고 표현한다.

프로토콜을 적용하는 경우, 프로토콜에서 정의한 프로퍼티와 메서드를 모두 구현해야 한다.


struct Mail: Sendable {
  var from: String?
  var to: String

  func send()
  {
    print("Send a mail from \(self.from) to \(self.to)");
  }
}

struct Feedback: Sendable {
  var from: String? {
    return nil
  }
  var to: String

  func send() {
    print("Send a feedback to \(self.to)")
  }
}

6.1.1 프로토콜은 다중상속이 가능하다.

프로토콜은 클래스(단일 상속만 가능)와는 달리 다중상속이 가능하다.

protocol Readable {
  func read()
}

protocol Writable {
  func write()
}

protocol ReadWriteSpeakable: Readable, Writable {
  func speak()
}

struct testStruct: ReadWriteSpeakable {
  func read() {
    print("Read")
  }
  func write() {
    print("write")
  }
  func speak() {
    print("speak")
  }
  //세 개 다 구현해줘야 한다.
}

6.1.2 클래스가 클래스와 프로토콜 모두를 상속받으려 하는 경우

클래스가 클래스를 상속받으면서 프로토콜도 상속받으려고 하는 경우,

상속받으려는 클래스를 먼저 써주고 그 뒤에 프로토콜을 나열한다.

class SuperClass: Readable {
  func read() {
    print("read") 
  }
}

class SubClass: SuperClass, Writable, Speakable {
  func write() {
    print("write")
  }
  func speak() {
    print("speak")
  }
} 

6.2 Swift 에서 알아두면 좋은 프로토콜

개발할 때 알아두면 좋은 프로토콜들로는 다음이 있다.


6.2.1 CustomStringConvertible

자기 자신을 표현하는 문자열을 정의한다.

public protocol CustomStringConvertible {
  public var description: String {get}
}

적용:

struct Dog: CustomStringConvertible {
  var name: String
  var description: String {
    return " \(self.name)"
  }
}

let dog = Dog(name: "Tom")
print(dog)

6.2.2 ExpressibleBy

일반적으로, 그냥 우리가 생각했을 때에는 10 은 Int 요 "hi" 는 String 이다.

그러나 엄밀히 따지면 10은 Int(10) 으로 선언돼야 하고, "hi" 는 String("hi") 로 선언돼야 한다.

Int 와 String 모두 생성자를 가지는 구조체 이기 때문이다.


반대로 생성자를 사용하지 않고도 생성할 수 있게 만드는 것을 리터럴literal 이라고 한다.

리터럴의 해석은 문자 그대로 라는 뜻이다.

아래 코드는 문자 그대로 10, 문자 그대로 "hi", 문자 그대로 배열, 딕셔너리 이다.


let number = 10
let string = "hi"
let array = ["amat","loren","ipsum"]
let dict = [
  "key":"value"
]

이 리터럴을 가능하게 해주는 프로토콜이 바로 ExpressibleByXXXLiteral 이다.

예를 들어 IntExpressibleByIntegerLiteral ,

StringExpressibleByStringLiteral,

ArrayExpressibleByArrayLiteral,

DictionaryExpressibleByDictionaryLiteral 프로토콜을 따른다.

각 프로토콜은 리터럴 값을 받는 생성자를 정의한다.


struct DollarConverter: ExpressibleByIntegerLiteral {
  typealias IntegerLiteralType = Int

  let price = 1_200
  var dollars: Int

  init(integerLiteral value: IntegerLiteralType) {
    self.dollars = value * self.price
  }
}

let converter: DollarConverter = 100
converter.dollars //120_000

참고로 typealias 는 C 언어의 typedef 와 동일하다. typealias I = Int 라고 한다면 IInt 처럼 쓸 수 있는 것이다.

1200 은 가독성을 위해 1_200이라고 쓸 수 있다. 120_0 또한 1200을 의미한다.


7. Any / AnyObject

Any 는 모든 타입 대신, AnyObject 는 모든 객체 대신 쓸 수 있는 키워드이다.

(nil은 받을 수 없다.)


let anyNum: Any = 10
let anyStr: Any = "hello"

let anyObj: AnyObject = Dog()

참고로 Any, AnyObject 는 프로토콜이다. Swift에서 모든 타입은 Any를 따르게 돼 있고, 모든 클래스는 AnyObject를 따르게 돼 있다.


7.1 타입캐스팅

anyNum 에 10을 넣으면 anyNum은 Int일까?

정답은 아니오 이다.

Any 타입을 Int로 전환하기 위해서는 다운 캐스팅 Down Casting 을 해야 한다.

Any가 Int 를 포괄하는, 더 큰 개념이기 때문에 작은 범위로 줄인다는 뜻이다.


캐스팅에는 as 를 사용한다.

형식: 변수: 자료형 = 값을 넣어줄 변수 as? 담기길 원하는 자료형

Ex)

let number: Int? = anyNum as? Int

옵셔널이기 때문에 옵셔널 바인딩 문법도 사용 가능하다. 실제로 다음과 같이 많이 쓴다.

if let number = anyNum as? Int {
  print(number + 1)
}

7.1.1 업 캐스팅

목차의 7.1.1 부터 8. 전까지는 다음 클래스들을 전제로 한다.

class Person {
    var name: String = ""
    func breath() {
        print("숨을 쉽니다")
    }
}

class Student: Person {
    var school: String = ""
    func goToSchool() {
        print("등교를 합니다")
    }
}

class UniversityStudent: Student {
    var major: String = ""
    func goToMT() {
        print("멤버쉽 트레이닝을 갑니다 신남!")
    }
}

업 캐스팅

  • as를 사용하여 부모클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 타입정보를 전환해준다.
  • Any 혹은 AnyObject로도 타입정보를 변환할 수 있다.
  • 암시적으로 처리되므로 꼭 필요한 경우가 아니라면 생략해도 무방하다.
// UniversityStudent 인스턴스를 생성하여 Person 행세를 할 수 있도록 업 캐스팅
var mike: Person = UniversityStudent() as Person

var jenny: Student = Student()
//var jina: UniversityStudent = Person() as UniversityStudent // 컴파일 오류

// UniversityStudent 인스턴스를 생성하여 Any 행세를 할 수 있도록 업 캐스팅
var jina: Any = Person() // as Any 생략가능

7.1.2 다운 캐스팅

as? 또는 as! 를 사용하여 자식 클래스의 인스턴스로 사용할 수 있도록 타입을 전환해준다.


조건부 다운 캐스팅 as?

캐스팅에 실패하는 경우, 즉 캐스팅하려는 타입에 부합하지 않는 인스턴스라면 nil을 반환하므로 결과 타입은 옵셔널이다.

var optionalCatsed: Student? // 옵셔널 타입

optionalCasted = mike as? UniversityStudent
optionalCasted = jenny as? UniversityStudent // nil
optionalCasted = jina as? UniversityStudent // nil
optionalCasted = jina as? Student // nil

강제 다운 캐스팅 as!

캐스팅에 실패하면, 즉 캐스팅하려는 타입에 부합하지 않으면 runtime error 가 발생한다.

캐스팅에 성공한 경우 옵셔널이 아닌 일반 타입을 반환한다.

var forcedCasted: Student // 일반 타입

forcedCasted = mike as! UniversityStudent
forcedCasted = jenny as! UniversityStudent // runtime error
forcedCasted = jina as! UniversityStudent // runtime error
forcedCasted = jina as! Student // runtime error

8. 익스텐션(Extension)

Swift 에서는 이미 정의된 타입에 새로운 속성이나 메서드를 추가할 수 있다.

바로 익스텐션(extension) 키워드를 이용하는 것이다.


Ex1 - 변수 추가.

extension Int {
  var isEven: Bool {
    return self % 2 == 0
  }
  var isOdd: Bool {
    return self % 2 == 1
  }
}

print(1.isEven) // false
print(1.isOdd) // true

Ex2 - 메서드 추가

extension Int {
  func multiply(by n: Int) -> Int {
    return self * n
  }
}

var num: Int = 3
print(num.multiply(by: 5)) // 15

Ex3 - init 추가

extension String {
  init(intTypeNumber: Int) {
    self = "\(intTypeNumber)"
  }
}

let IntToString: String = String(intTypeNumber: 100)
// "100"

9. assert / guard

assert 함수는 디버깅 모드에서 사용 가능하고,

guard 함수는 어디서든 사용 가능하다.


9.1 assert 함수

아래의 코드에서 만약 testInt0 이라면 assert 함수는 실행되지 않고 지나간다.

그러나 0이 아니라면, 두 번째 인자로 들어온 문자열을 콘솔로그에 출력한다.

그리고 작동을 중지한다.

var testInt: Int = 0
assert(testInt == 0, "testInt is not 0")

testInt = 1
assert(testInt == 0) // 디버거가 여기서 멈춤.
assert(testInt == 0, "testInt is not 0") 
// 디버거가 여기서 멈춤. 콘솔로그에 testInt is not 0 출력

함수 안에서는 어떻게 활용할 수 있는지 살펴보자.

func funcWithAssert(age: Int?) {
  assert(age == nil, "age == nil")

  assert((age! >= 0) && (age! <= 130), "나이값 입력이 잘못됐습니다.")
  print("당신의 나이는 \(age!)세 입니다.")
}

funcWithAssert(age: 50)
funcWithAssert(age: -1)  // 동작 중지.
funcWithAssert(age: nil) // 동작 중지.

9.2 guard 함수.

Guard 함수는 빠르게 종료시키는 것이 목적이다.

그렇기 때문에 return, break 같은 키워드를 사용한다.

func funcWithGuard(age: Int?) {
  guard let unwrappedAge = age, // nil 이면 else로.
      unwrappedAge < 130,
      unwrappedAge >= 0 else { // 0..130이 아니면 else
        return // return break 같은 키워드 필수로 있어야 함.
      }

  print("당신의 나이는 \(unwrappedAge) 세 입니다.")
}

9.2.1 반복문 안에서 guard 사용

var count = 1
while true {
  guard count < 3 else {
    break
  }
  print(count)
  count += 1
}

9.2.2 딕셔너리와 guard(guard가 가장 많이 활용되는 곳)

Guard 는 딕셔너리를 만났을 때 가장 많이 활용된다.

func testFunc(info: [String: Any]) {
  guard let name = info["name"] as? String else {
    return
  }
  guard let age = info["age"] as? Int, age >= 0 else {
    return
  }

  print ("\(name): \(age)")
}

testFunc(info: ["name": "captain", "age": "10"])
// age가 String이라서 아무것도 출력 x
testFunc(info: ["name": "robert"])
// age가 없어서(nil) 아무것도 출력 x
testFunc(info: ["name": "ironman", "age": "40"])
// ironman: 40 출력

10. 오류 처리

Swift에서 오류 처리는 Error 프로토콜과 (주로) 열거형을 통해 표현한다.

형식)

enum 오류종류이름: Error {
    case 종류1
  case 종류2
  case 종류3
  ...
}

자판기를 예로 들어 Error 를 표현해보자.

주의할 점은 오류발생의 여지가 있는 throws 메서드는 try를 사용해야 한다는 것.

enum VendingMachineError: Error {
  case invalidInput
  case insufficientFunds(moneyNeeeded: Int)
  case outOfStock
}

// class 에서는 `throws, throw` 라는 키워드로 오류를 던진다.
// 메소드 - throws / 조건문 - throw
class VendingMachine {
  let itemPrice: Int = 100
  var itemCount: Int = 5
  var deposited: Int = 0

  // 돈 받기
  func receiveMoney(_ money: Int) throws {
    // 입력한 돈이 0 이하면 오류를 던진다.
    guard money > 0 else {
      throw VendingMachineError.invalidInput
    } // 빠른 종료

    // 여기까지 오면 오류가 없음을 의미.
    self.deposited += money
    print("\(money)원 받음")
  }

  // 물건 팔기
  func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {

    // 원하는 아이템의 수량이 잘못 입력됐으면 오류를 던짐.
    guard numberOfItemsToVend > 0 else {
      throw VendingMachineError.invalidInput
    }

    // 구매하려는 수량보다 미리 넣어둔 돈이 적으면 오류를 던짐
    guard numberOfItemsToVend * itemPrice <= deposited else {
      let moneyNeeded: Int
      moneyNeeded = numberOfItemsToVend * itemPrice - deposited

      throw VendingMachineError.insufficientFunds(moneyNeeded: moneyNeeded)
    }

    // 오류가 없는 경우
    let totalPrice = numberOfItemsToVend * itemPrice

    self.deposited -= totalPrice
    self.itemCount -= numberOfItemsToVend

    return "\(numberOfItemsToVend) 개 제공함"
  }
}

// 자판기 인스턴스
let machine: VendingMachine = VendingMachine()

// 판매 결과를 받을 변수
var result: String?

// 오류발생의 여지가 있는 throws 함수(메서드)는
// try를 사용하여 호출해야 한다. 
// try 종류: try, try?, try!
// 또한 try를 사용할 때에는 do-catch 구문을 함께 사용하게 된다 

do {
  try machine.receiveMoney(0)
} catch VendingMachineError.invalidInput {
  print("입력이 잘못됐습니다.")
} catch VendingMachineError.insufficientFunds(let moneyNeeded) {
  print("\(moneyNeeded)원이 부족합니다.")
} catch VendingMachineError.outOfStock {
  print ("수량이 부족합니다.")
} // 입력이 잘못됐습니다.

위 코드에서 마지막의 do - catch 구문은 다음과 같이 써줄 수도 있다.

참고로 아래에서 주석처리한 (let eroor) 는 안 써줘도 자동으로 인식하게 돼 있다.

do {
  try machine.receiveMoney(300)
} catch /*(let error)*/ {
  switch error {
    case VendingMachineError.invalidInput:
        print("입력이 잘못됐습니다.")
    case VendingMachineError.insufficientFunds(let moneyNeeded):
        print("\(moneyNeeded)원이 부족합니다.")
    case VendingMachineError.outOfStock:
        print("수량이 부족합니다.")
    default:
        print("알 수 없는 오류 \(error)")
  }
} // 300원 받음

이렇게 오류 하나하나를 핸들링하기에는 너무 코드가 길다.

하나씩 핸들링할 필요가 없다면 다음과 같이 쓸 수도 있다.

// 방법 1
do {
  result = try machine.vend(numberOfItems: 4)
} catch {
  print(error)
} // isufficientFunds(100)

// 방법 2 - error 신경쓰지 않고 싶을 때
do {
  result = try machine.vend(numberOfItems: 4)
}

10.1 try?

try?는 별도의 오류처리 결과를 통보받지 않고 오류가 발생하는 경우 결과값을 nil로 돌려받을 수 있다.

정상동작 시 옵셔널 타입으로 반환값을 돌려받는다.

var result: String?

// 남은 상품 개수 3개
result = try? machine.vend(numberOfItems: 2)
// Optional("2개 제공함")

result = try? machine.vend(numberOfItems: 5)
result // nil

10.2 try!

try! 는 오류가 발생하지 않을 것이라는 확신이 들 때 사용한다.

정상동작 후에 바로 결과값을 돌려받지만, 정상동작하지 않으면 런타임오류를 발생시킨다.

result = try! machine.vend(numberOfItems: 1)
result // 1개 제공함

result = try! machine.vend(numberOfItems: 5)
// runtime error!

  • 오류 발생과 관련하여 추가적으로 알고 있으면 좋은 개념들 - rethrows, defer

11. 고차함수

고차함수란, 전달인자로 함수를 전달받거나 함수실행의 결과를 함수로 반환하는 함수이다.

대표적으로는 map, filter, reduce 가 있다.


11.1 map

map 은 컨테이너 내부의 기존 데이터를 변형하여 새로운 컨테이너를 반환한다.

여기서 컨테이너라는 것은 배열과 같은 자료형이다.

let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int]
var strings: [String]

// for구문 사용 시
doubledNumbers = [Int]()
strings = [String]()

for number in numbers {
  doubledNumbers.append(number * 2)
  strings.append("\(number)")
}

print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]


// map 사용 시
doubledNumbers = numbers.map({ (number: Int) -> Int in
  return number * 2                             
})

strings = numbers.map({number: Int} -> String in
  return "\(number)"                     
})

print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]

위의 doubledNumbers 는 인자인 number과 리턴타입의 자료형이 모두 Int 이므로 다음과 같이 줄여쓸 수 있다.

doubledNumbers = numbers.map { $0 * 2 }
// map 뒤에 ( ) 가 없는 이유는 후행클로저이기 때문.

11.2 filter

filter 는 하나하나의 요소를 필터링해서 조건에 부합하는 녀석만 컨테이너로 만들어 반환해준다.

filter 의 경우 요소의 리턴 타입은 Bool 이며 true 일 때만 요소를 반환한다.

ex)

// for 구문 사용 시
var filtered: [Int] = [Int]()
for number in numbers {
  if number % 2 == 0 {
    filtered.append(number)
  }
}

print(filtered) // [0, 2, 4]

// filter 사용 시
let evenNumbers: [Int] = numbers.fileter {
  (number: Int) -> Bool in

  return number % 2 == 0
}

print(evenNumbers) // [0, 2, 4]

후행 클로저로 표현한다면 좀더 쉽게 표현할 수 있다:

let oodNumbers: [Int] = numbers.filter { $0 % 2 != 0 }

print(oddNumbers) // [1, 3]

11.3 reduce

reduce 는 컨테이너 내부의 요소들을 하나로 통합하는 역할을 한다.

let someNumbers: [Int] = [2, 8, 15]

// for 구문 사용 시
var result: Int = 0

for number in someNumbers {
  result += number
}

print(result) // 25


// reduce 사용 시
// 초기값을 지정해줘야 한다. 아래의 0이 초기값임.
// 아래의 first는 초기값 ~ 저장되어 나가는 값 이라고 보면 된다.
// second 는 someNumbers 안에 있는 하나하나의 요소이다. 
let res: Int = someNumbers.reduce(0, {
  (first: Int, second: Int) -> Int in

  return first + second
})
print(sum) // 25

var subtract: Int = someNumbers.reduce(0, {
  (first: Int, second: Int) -> Int in

  return first - second
})
print(subtract) // -25

// 초기값을 3으로 설정하고 sum을 간단하게 표현하는 코드. 
let sumFromThree = someNumbers.reduce(3) { $0 + $1 }
print(sumFromThree) // 28

12. 공통

여기서는 주제를 따로 두지 않고 앞으로 swift 언어로 코딩을 하면서 알아두어야 할 공통적인 개념에 대해 소개하려고 한다.


12.1 명명법

함수 메소드 변수 상수(Function method variable constant) - Lower Camel Case

ex) variableName


클래스 구조체 같은 type들 : class, struct, enum, extension ... - Upper Camel Case

ex) ClassName, StructName, EnumName


12.2 키워드를 변수이름으로

키워드를 변수 이름처럼 쓰고 싶은 경우 `` 으로 감싸주면 된다.

//class 는 키워드이므로 그냥 변수처럼 쓸 수 없다.
//하지만 다음과 같은 표현법으로 class를 변수처럼 쓸 수 있다.
var `class`: Int = 30

13. 추가적으로 알아두면 좋을 개념들

많은 참고를 할 수 있게 해준 Yagom님의 추천 학습 개념 목록이다.


  1. 제네릭(Generics)
  2. 서브스크립트(Subscript)
  3. 접근 수준(Access Control)
  4. ARC(Automatic Reference Counting)
  5. 중첩타입(Nested Types)
  6. 사용자정의 연산자(Custom Operators)
  7. 불명확 타입(Opaque Types)
  8. 프로토콜 지향 프로그래밍(Protocol Oriented Programming)

by Yagom


야곰님's


REFS


https://seoh.github.io/Swift-Korean/

https://www.edwith.org/boostcamp_ios/lecture/11125/

https://www.edwith.org/boostcamp_ios/lecture/11126/

https://www.edwith.org/boostcamp_ios/lecture/11127/

https://www.edwith.org/boostcamp_ios/lecture/11128/

https://www.edwith.org/boostcamp_ios/lecture/11201/

https://www.edwith.org/boostcamp_ios/lecture/11202/

https://www.edwith.org/boostcamp_ios/lecture/11224/

https://www.edwith.org/boostcamp_ios/lecture/11225/

https://www.edwith.org/boostcamp_ios/lecture/11235/

https://www.edwith.org/boostcamp_ios/lecture/11236/

https://www.edwith.org/boostcamp_ios/lecture/11240/

https://www.edwith.org/boostcamp_ios/lecture/11270/

https://www.edwith.org/boostcamp_ios/lecture/11271/

https://www.edwith.org/boostcamp_ios/lecture/11272/

https://www.edwith.org/boostcamp_ios/lecture/11273/

https://www.edwith.org/boostcamp_ios/lecture/11274/

https://www.edwith.org/boostcamp_ios/lecture/11296/

https://www.edwith.org/boostcamp_ios/lecture/11297/

https://www.edwith.org/boostcamp_ios/lecture/11298/

https://www.edwith.org/boostcamp_ios/lecture/11299/

https://www.edwith.org/boostcamp_ios/lecture/11309/

https://www.edwith.org/boostcamp_ios/lecture/11310/

https://www.edwith.org/boostcamp_ios/lecture/11311/

https://www.edwith.org/boostcamp_ios/lecture/11312/

https://www.edwith.org/boostcamp_ios/lecture/11313/

https://www.edwith.org/boostcamp_ios/lecture/11319/

https://www.edwith.org/boostcamp_ios/lecture/11320/

https://www.edwith.org/boostcamp_ios/lecture/11321/

https://www.edwith.org/boostcamp_ios/lecture/11285/

https://www.edwith.org/boostcamp_ios/lecture/11323/


  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기
// custom