swift 기본문법 - 자동참조 카운팅, ARC(Automatic Reference Counting)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
참고씩이머릿속-자동참조카운팅


자동참조 카운팅, ARC(Automatic Reference Counting)

메모리 사용을 관리하고 추적하기 위해 사용

대부분의 경우 메모리 관리는 ARC가 알아서 하고, 우리는 메모리 관리를 신경쓸 필요가 없다.
ARC는 클래스 인스턴스가 더이상 필요하지 않을 때 자동으로 그 인스턴스가 사용하는 메모리를 제거한다. 참조 카운팅은 클래스의 인스턴스에만 적용되며 구조체와 열거형은 참조 타입이 아니라 값 타입이라 해당사항이 없다.

ARC 작동원리

  • 클래스의 새로운 인스턴스를 생성할 때 마다 ARC는 인스턴스의 정보를 저장하기 위해서 메모리를 할당한다.
  • 메모리에는 해당 인스턴스와 연결된 모든 저장 프로퍼티의 값과 함께 인스턴스의 타입에 대한 정보가 저장된다.
  • 인스턴스가 필요 없어질 때, ARC 는 메모리를 다른 목적으로 사용될 수 있게 하기 위해 그 인스턴스가 사용하는 메모리를 해제한다.

이렇게 하면 클래스 인스턴스가 더 이상 필요하지 않을때 메모리 공간을 차지하지 않도록한다.(효율성 업!)

ARC는 해당 인스턴스에 대한 참조가 적어도 하나 이상 있다면, 인스턴스를 해제하지 않으며 이를 가능하게 하려면 클래스 인스턴스를 프로퍼티나 변수, 상수에 할당할 때마다 강한 참조(strong reference)를 만들어야 한다 ( 변수가 인스턴스를 꽉 붙잡고 있다고 생각하면 될듯. )

class Person { // Person 클래스에는 이름과 초기화 상태를 나타내는 초기화 함수와 초기화 해제 함수가 있음
   let name: String
   init(name: String) {
       self.name = name
       print("\(name) is being initialized")
   }
   deinit { // 인스턴스가 메모리 해제될 때 메세지 출력하게 하는 deinit
       print("\(name) is being deinitialized")
   }
}
// Person 클래스 타입의 세 변수 옵셔널로 생성. ARC 의 상태 변화 알아보기 위함
var reference1: Person? // 옵셔널 타입이므로 현재는 nil 로 초기화
var reference2: Person?
var reference3: Person?

reference1 = Person(name: "John Appleseed")// "John Appleseed is being initialized" 출력
// Person 인스턴스의 메모리가 생기는 시점
// referece1 에서 새로운 Person 인스턴스에 대한 강력한 참조가 생긴다
// 강한참조가 하나 이상 생겼기 때문에  ARC 는 이 인스턴스를 해제하지 않는다

reference2 = reference1
reference3 = reference1
// 같은 인스턴스 참조를 2, 3 에 모두 할당하면?
// 해당 인스턴스에 대한 강한 참조가 2개 더 생기는 것
// 그럼 현 시점에서는 Person 인스턴스에 대한 강한 참조는 3개 인 상태

reference1 = nil
reference2 = nil
// 2곳에 nil 할당해서 2개의 강한 참조를 끊으면?
// 아직 1개, 마지막 강한 참조가 남아있으므로 ARC 는 Person 인스턴스 메모리를 해제하지 않음
reference3 = nil // "John Appleseed is being deinitialized" 출력
// ARC 가 Person 인스턴스 메모리 해제시키는 시점
// 마지막 참조가 남아있는 reference3 에도 nil 할당하면, 인스턴스 안쓰는걸로 알고 ARC 가 메모리 없애버림

ARC와 GC의 차이

메모리 관리 기법 ARC GC
참조 카운팅 시점 컴파일 시 프로그램 동작 중
장점 컴파일 당시 이미 인스턴스의 해제 시점이 정해져 있어 인스턴스가 언제 메모리에서 해제될 지 예측 가능하다. 따라서 메모리 관리를 위한 시스템 자원을 추가할 필요가 없다 상호 참조 상황등의 복잡한 상황에서도 인스턴스를 해제할 수 있는 가능성이 더 높으며 특별히 규칙에 신경 쓸 필요가 없다.
단점 ARC의 작동 규칙을 모르고 사용하면 인스턴스가 영영 메모리에서 해제되지 않을 가능성이 있다 프로그램 동작 외의 메모리 감시를 위한 추가 자원이 필요해 한정적인 자원 환경에서 성능 저하가 발생할 수 있다. 더 나아가 명확한 규칙이 ㅇ벗기 때문에 인스턴스가 정확히 언제 메모리에서 해제될 지 에측이 어렵다.

강한 참조와 강한참조 순환 문제

인스턴스가 계속해서 메모리에 남아있어야 하는 명분을 만들어주는 것이 강한참조이다. 참조의 기본은 강한 참조이기 때문에 클래스 타입의 프로퍼티, 변수, 상수 등을 선언할 때 별도의 식별자를 명시하지 않으면 강한 참조가 된다.

class Person {
  let name: String
}

기본적으로 인스턴스는 참조 횟수가 0이 되는 순간 메모리에서 해제되는데, 인스턴스를 다른 인스턴스의 프로퍼티나 변수, 상수 등에 할당할 때 강한 참조를 사용하면 참조 횟수가 +1 되며 강한 참조를 사용하는 프로퍼티, 변수, 상수 등에 nil을 할당해주면 그때 -1가 된다. 참조 횟수가 1이라도 남아있으면 메모리에서는 이를 해제하지 못하는데, 이렇게 강한 참조를 걸게 됨으로써 인스턴스가 메모리 해제되지 않아 계속된 메모리 누수를 만드는 경우가 있다. 그게 바로 강한 참조 순환 문제 이다.

class Person {
  let name: String

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

  var room: Room?

  deinit {
    print("\(name) is being deinitialized")
  }
}

class Room {
  let number: String

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

  var host: Person?

  deinit {
    print("Room \(number) is being deinitialized")
  }
}

var zehye: Person? = Person(name: "zehye")  // Person의 인스턴스 참조: 1
var room: Room? = Room(number: "22")  // Room의 인스턴스 참조: 1

room?.host = zehye  // Person의 인스턴스 참조: 2
zehye?.room = room  // Room의 인스턴스 참조: 2

zehye = nil  // Person의 인스턴스 참조: 1
room = nil   // Room의 인스턴스 참조: 1

zehye와 room에 nil을 할당하게 됨으로써 더이상 zehye 변수가 참조하던 Person클래스의 인스턴스에 접근할 방법도 room변수가 참조하던 Room 클래스의 인스턴스에 접근할 방법이 사라지게 되었다. 참조 횟수가 0이 되지않는 한, ARC의 규칙대로라면 인스턴스를 메모리에서 해제시키지 않기 떄문에 이렇게 두 인스턴스 모두 참조 횟수가 1이 남은체 메모리에 좀비처럼 남아있게 된다.

이를 해결하기 위한 방법으로 약한참조미소유참조 가 있다.

약한 참조

약한 참조는 강한 참조와는 다르게 자신이 참조하는 인스턴스의 참조 횟수를 증가시키지 않는다. 참조타입의 프로퍼티나 변수의 선언 앞에 weak 를 써주면 약한참조를 할 수 있다. 이는 참조횟수를 증가시키지 않기 떄문에 이스턴스를 강한 참조하던 프로퍼티나 변수에서 참조 횟수를 0으로 만들면 자신이 참조하던 인스턴스가 메모리에서 해제될 수 있다.

약한 참조는 상수에서 쓰일 수 없다. 자신이 참조하던 인스턴스가 메모리에서 해제된다면 nil이 할당될 수 있어야 하기 때문이다. 그래서 약한 참조를 할 때 자신의 값을 변경할 수 있는 변수로 선언해야하며 nil이 할당될 수 있어야 하기에 약한 참조는 항상 옵셔널이어야 한다. 즉 옵셔널 변수만이 약한참조를 할 수 있다.

class Room {
  let number: String

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

  weak var host: Person?

  deinit {
    print("Room \(number) is being deinitialized")
  }
}

var zehye: Person? = Person(name: "zehye")  // Person의 인스턴스 참조: 1
var room: Room? = Room(number: "22")  // Room의 인스턴스 참조: 1

room?.host = zehye  // Person의 인스턴스 참조: 1
zehye?.room = room  // Room의 인스턴스 참조: 2

zehye = nil  // Person의 인스턴스 참조: 0, Room의 인스턴스 참조: 1
// zehye is being deinitialized
room = nil   // Room의 인스턴스 참조: 0
// Room 22 is being deinitialized

미소유 참조

약한 참조와 마찬가지로 미소유 참조 또한 인스턴스의 참조 횟수를 증가시키지 않는다. 그러나 미소유 참조는 자신이 참조하는 인스턴스가 항상 메모리에 존재할 것이라는 전제를 기반으로 동작한다. 즉, 자신이 참조하는 인스턴스가 메모리에서 해제되더라도 스스로 nil을 할당해주지 않는다는 것을 의미한다. 따라서 미소유참조를 하는 변수나 프로퍼티는 옵셔널이나 변수가 아니어도 된다. 그러나 미소유 참조를 하면서 메모리에서 해제된 인스턴스에 접근하려 한다면 잘못된 메모리 접근으로 런타임 오류가 발생해 프로세스가 강제로 종료된다. 따라서 미소유 참조는 참조하는 동안 해당 인스턴스가 메모리에서 해제되지 않을 것이라는 확신이 있을때만 사용해야 한다.

이러한 미소유참조는 참조타입의 변수나 프로퍼티 앞에 unowned 키워드를 사용해주면 된다.

class Person {
  let name: String

  // Person은 카드를 소지할 수도 소지하지 않을 수도 있다.
  // 카드를 한번 가진 후 잃어버리면 안되기에 강한참조로!
  var card: Card?
  init(name: String) {
    self.name = name
  }

  deinit { print("\(name) is being deinitialized")}
}

class Card {
  let number: Int

  // 카드는 소유자가 분명히 존재해야한다.
  unowned let owner: Person
  init(number: UInt, owner: String) {
    self.number = number
    self.owner = owner
  }

  deinit { print("Card \(number) is being deinitialized")}
}

var zehye: Person? = Person(name: "zehye")  // Person 인스턴스 참조: 1

if let person: Person = zehye {
  // Card의 인스턴스 참조: 1
  person.card = Card(number: 11, owner: person)
  // Person 인스턴스 참조: 1
}

zehye = nil  // Person 인스턴스 참조: 0
// Card 인스턴스 참조: 0
// zehye is being deinitialized
// Card 11 is being deinitialized

swift 기본문법 - 접근 제한(Access Control)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


접근 제한(Access Control)

접근권한을 말한다.
class, struct, enum등의 type들을 비롯해 property. method, subscript 등 모두에게 접근제한을 부여할 수 있다.

이를 이해하기 위한 간단한 개념부터 봐보자.

Module

특정 기능을 하는 어플리케이션이나 프레임워크의 배포 단위로서 그냥 하나의 프로젝트와 연관된 모든 파일이라고 이해하면 된다. 예로 들어 가계부 앱을 만들었다고 하면, 이 가계부 앱과 관련된 XCode에서 작업하고 배포한 코드 파일 모두를 뜻한다.

Source File

프로젝트 파일 하나하나를 의미한다. 보통 한 Source File안에 하나의 class나 하나의 struct를 만드는 것이 일반적이지만 꼭 그럴필요는 없다.

Hiding Implementation Detail

어떤 함수나 class 인스턴스 등의 안에서 실행되는 코드를 외부에서는 알 수 없게 가린다는 의미이다. 이에 따라 밖에서는 안에 어떤 property나 method가 있는 지도 알 수 없으며, 이름을 알더라도 접근할 수 없게 만든다.

5가지의 Access Level

Swift에는 open, public, internal, fileprivate, private 5가지의 Access Level을 제공한다. 가장 위를 access level이 높다 또는 제한이 가장 없다고 하고, 아래로 갈수록 access level이 낮고 제한이 강하다고 표현한다. 주로 사용되는 것은 public, private 두개이다. internal은 모든 객체들이 특별히 access level을 지정해주지 않을 경우 가지는 default access level로서 작업하는 모듈 내에서만 접근 가능하도록 설정되어 있다.

open

가장 열린 Access Level. 외부의 다른 모듈이 import 해서 접근 가능하다. 심지어 import하는 외부 모듈안에서 subclass와 override도 가능하기 때문에 위험할 수도 있다. Open API 및 framework 구성시 활용한다.

public

open과 마찬가지로 외부 모듈이 import해서 접근 가능하지만, subclass와 override는 오로지 자신의 모듈 안에서만 가능하다. 이 또한 open API 및 framework 구성시 활용한다.

internal

자신의 모듈 안에서만 접근 가능하고 외부 모듈에서는 접근 불가하다. 한마디로 프로젝트 내부용이다. 모든 객체는 별도의 지정이 없으면 기본적으로 internal로 셋팅되며 Open API를 제공하지 않는 앱은 대체로 객체들을 internal로 두면 문제될 것이 없다. 다만, 다른 Source file이나 declaration 밖에서의 접근이 없다면 internal로 놔두는 것보다 private으로 두는 것이 더좋다.

fileprivate

같은 Source file안에서만 접근 가능하다. 즉, .swift라는 파일 안에서 fileprivate인 함수가 있다면 해당 swift파일안에서는 접근이 가능하지만, 같은 프로젝트의 다른 swift 파일에서는 접근이 불가능하다.

private

가장 폐쇄된 Access Level. 같은 declaration 공간({})안에서만 접근가능하다. 즉, 함수 안에서 private으로 선언된 변수는 해당 함수안에서, class안에서 private으로 선언된 변수는 해당 class 안에서만 접근이 가능하다.

swift 기본문법 - 서브스크립트(Subscript)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


서브스크립트(Subscript)

컬렉션, 리스트, 시퀀스 타입의 개별 요소에 접근할 수 있는 지름길을 제공하는 것

let array = [1, 2, 3]

위와 같은 배열이 있을때 배열 내부의 2라는 값을 얻기 위해 array[1] 이라는 문법을 사용한다.

여기에서 array [1] 이 부분이 바로 서브스크립트이다. 즉, 원하는 값을 쉽게 찾기 위해 정의하는 문법을 서브스크립트라고 한다.
array가 서브스크립트 문법을 구현하지도 않았는데 사용할 수 있는 이유는 스위프트 표준 라이브러리에 정의된 array 구조체 내부에 서브스크립트가 이미 구현되어 있기 때문이다.

구현해보기

  • subscript 키워드를 사용
  • 연산 프로퍼티에서 사용했던 get, set 키워드 사용
subscript(index: Int) -> Int {
    get {
        // return an appropriate subscript value here
    }
    set(newValue) {
        // perform a suitable setting action here
    }

예제

1.구구단

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index  // subscript에 입력한 정수만큼 곱하기, set없이 읽기전용
    }
}

let threeTimesTable = TimesTable(multiplier: 3)  // 구구단 3단
print("six times three is \(threeTimesTable[6])")  // 18

2.영화 리스트

class MovieList{
    private var tracks = ["s", "d", "r"]
    subscript(index: Int) -> String {
        get {
            return self.tracks[index]
        }
        set {
            self.tracks[index] = newValue
        }
    }
}
 var movieList = MovieList()
print("영화리스트에서 두번째 영화는: \(movieList[1])")  // d

swift 기본문법 - 제네릭(Generics)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


제네릭(Generics)

제네릭은 자료형에 구애받지 않는 코드를 의미한다. 스위프트는 태생적으로 강한 타입의 언어이기 때문에 변수와 인자, 리턴값 등 모두 자료형의 구속을 받게 된다. 이것때문에 때로는 비생산적이고 반복적인 코딩을 해야할 때가 발생하는데 제네릭은 이 부분을 해결해주는 유용한 코드로서 스위프트의 가장 강력한 기능 중 하나이다.

사실 스위프트의 array이나 dictionary도 제네릭이다. 예로 array에는 Int들을 담을 수도 있고 String도 담을 수 있기 때문이다.

따라서 이 제네릭을 사용하게 되면 유연하고 재사용성 높은 코드를 작성할 수 있다는 장점이 있다.

자. 아래 두 정수의 값을 바꿔주는 함수가 있다고 하자.

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
  let temporaryA = a
  a = b
  b = temporaryA
}

그런데 아래 사진에서와 같이 파라미터에 Double형을 넣으면 에러가 뜨게 된다.
swapTwoInts의 파라미터는 Int형이기 때문이다.

그런데 우리가 만약에 같은 기능을 하는 함수임에도 불구하고 String, Double 등 각각 다양한 타입의 파라미터를 받아주는 함수를 매번 만들어야 한다고 생각해보자. 매우…매우!!!! 비효율적이라는 생각이 들것이다.

바로 이때 generic을 사용하면 된다.

func swapTwoInts<T>(_ a: inout: T, _ b: inout: T) {
  let temporaryA = a
  a = b
  b = temporaryA
}

이렇게만 해주면 이 함수 하나로 Int, String, Double 변수들의 값을 바꿔줄 수 있다.
단 a, b가 모두 같은 타입일 때만 말이다!

그렇다면 이때 T 는 무엇을 의미할까?

T 타입 파라미터

1. 파라미터: 타입이 들어갈 부분에 T를 적었다.

이때 T는 Placeholder 타입 “이름”을 의미한다. String, Int타입도 이름으로 이 대신 T라는 타입이름 이 들어간것!
이 T는 swapTwoInts라는 함수가 호출될 때마다 결정된다. 그리고 이때 a와 b는 이 T타입과 반드시 일치해야 한다.

2. <T>

generic함수와 일반함수에서의 가장 큰 차이일 것이다.

제네릭함수는 함수 이름 옆에 위에서 말한 Placeholder 타입 이름이 온다. 대괄호(<>)로 묶어준 이유는 Swift에게 함수 정의 내 Placeholder 타입이름인 것을 알리기 위해서이다. 그냥 “T”라는 것은 Placeholder이므로, swiftsms “T”라는 실제 타입을 찾지 않는다.

따라서 아래와 같이 작성해도 문제 없다.

func swapTwoInts<zehye>(_ a: inout: zehye, _ b: inout: zehye) {
  let temporaryA = a
  a = b
  b = temporaryA
}

뿐만 아니라 <T>자리에는 여러개가 들어갈 수 있다(,로 구분)

그러나 swift는 매~우 안전한 언어이며 타입에 굉~장히 민감하기 때문에 a와 b의 타입은 반드시 같아야 한다.
즉, Int와 String을 서로 바꿀 수 없게 하며, 만약 이렇게 하면 컴파일 에러가 뜰 것이다!

뿐만 아니라 swift에는 이미 swap 이라는 함수가 있는데 이또한 제네릭 함수이다.

public func swap<T>(_ a: inout T, _ b : inout T)

타입 제약

위에서 구현한 swapTwoInts 함수는 이제 제네릭으로 구현되어 있기 때무에 모든 타입에서 잘 동작할 것이다.

그러나 가끔 특정한 타입에서만 제네릭 함수를 사용하고 싶을때도 있을 것이다. 이를 위해 타입제약(Type Constraint)이 있다.
타입 제약을 통해 타입 파라미터(Type Parameter, swapTwoInts에서는 T)가 특정 클래스로부터 상속되거나, 특정 프로토콜을 준수해야만 제네릭 함수를 쓸 수 있도록 제약을 걸 수 있다.

우리는 위에서 딕셔너리도 제네릭의 콜렉션이라는 것을 인지했다.

그런데 딕셔너리에서의 key에는 아무 타입이나 들어갈 것처럼 보이지만 사실 Hashable 프로토콜 을 준수해야만 Key로 들어올 수 있다.

public struct Dictionary<Key, Value> : Collection, ExpressibleByDictionaryLiteral where Key: Hashable {

}

즉, 우리가 이때까지 넣었던 Int, String, Double, Bool 등은 Hashable 프로토콜을 준수하고 있다는 것을 의미한다.

따라서 Dictionary는 타입 제약을 통해 특정한 클래스를 상속받거나, 특정한 프로토콜을 준수한 타입만이 들어올 수 있도록 구현되어 있다. 물론 제네릭을 통해서 말이다! 그럼 우리도 한번 만들어보도록 하자!

func somwFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
  .. function body goes here
}

위 코드는 타입 제약이 들어간 제네릭 함수의 syntax이다. 위 함수에서의 T,U모두 타입 파라미터이다.
T 옆에는 특정 클래스, U 옆에는 특정 프로토콜이 존재하며 두개의 구분은 , 으로 하였다.

  • T: SomeClass의 하위클래스여야 한다는 제약
  • U: SomeProtocol을 준수해야한다는 제약
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
  for (index, valiue) in array.enumerated() {
    if value == valueToFind {
      return index
    }
  }
  return nil
}

위 findIndex함수는 String 배열과 찾고싶은 String 하나를 파라미터로 주면 해당 String이 있으면 그 인덱스를 반화해주고 없으면 nil을 반환해주는 함수이다. 즉 [1,2,3,4,5,6]이라는 배열을 가지고 있는데 내가 찾고 싶은 Int가 몇번째 인덱스에 있는지 궁금할때 사용하는 함수를 의미한다.

위 함수를 이제 제네릭으로 바꿔보자!

func findIndex<T>(of valueToFind: T, in array: [T]) -> Int? {
  for (index, value) in array.enumerated() {
    if value == valueToFind {
      return index
    }
  }
  return nil
}

그러면 위와 같은 오류가 발생할 것이다.

value == valueToFind 구문으로부터 발생하는 오류로 swift의 모든 타입이 저렇게 == 라는 여산자로 비교될 수 있는 것이 아니기 때문에 발생하는 에러이다. 즉, swift가 가능한 모든 타입 T에 대해 이 코드가 작동한다는 것을 보장할수 없기 때문에 컴파일 에러가 발생하는 것이다!

이때 사용할 개념이 Equatable 이다.

func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
  for (index, value) in array.enumerated() {
    if value == valueToFind {
      return index
    }
  }
  return nil
}

즉, Equatable을 준수하는 모든 타입은 위 findIndex 함수를 안전하게 사용할 수 있게 된다.

swift 기본문법 - 고차 함수(higher order function)

|

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


고차 함수(higher order function)

전달인자로 함수를 전달받거나 함수 실행의 결과를 함수로 반환하는 함수 » map, filter, reduce

map

컨테이너 내부의 기존 데이터를 변형(transform)하여 새로운 컨테이너를 생성한다.
파라미터로 받은 클로저를 모든 요소에 실행한 그 결과를 반환

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

doubleNumbers = [Int]()
strings = [String]()

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

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

이를 map 메서드를 사용하여 변환해보자

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

map의 전달인자로 클로저가 들어와 각각의 요소를 어떻게 변형해 무엇으로 돌려줄 것인지 지정이 가능하다.
Int요소를 하나하나 받아와 Int로 반환해줄 것이다. 그래서 새로운 컨테이너에 넣어달라고 할당함

매개변수, 반환타입, 반환 키워드(return) 생략, 후행 클로저 사용

doubleNumbers = numbers.map { $0 * 2 }
print(doubleNumbers)  // [0,2,4,6,8]

filter

컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출 » for 구문 사용! 변수 사용에 주목!

하나하나 요소를 필터링해서 조건에 부합하는 애들만 새로운 컨테이너로 만들어주는 것! > 필터된 값을 반환

var filtered: [Int] = [Int]()

for number in numbers {
  if number % 2 == 0 {
    filtered.append(number)
  }
}

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

위 예시는 for문을 만들기 위해 filtered라는 변수를 만들어줘야 했지만 filter를 사용할때는 그럴 필요가 없다.

그냥 상수로도 바로 받아올 수 있다. 변수로도 받아도 상관은 없다.

let evenNumbers: [Int] = numbers.filter {
  (number: Int) -> Bool in return number % 2 == 0
}
print(evenNumbers)  // [0,2,4]

매개변수, 반환타입, 반환 키워드(return) 생략, 후행 클로저 사용

let oddNumbers: [Int] = numbers.filter { $0 % 2 ! = 0 }
print(oddNumbers)  // [1,3]

reduce

컨테이너 내부의 콘텐츠를 하나로 통합한다.
초기값이 있고 초기값과 첫번째 요소 ~ 끝번째 요소까지의 실행된 최종 결과값만을 반환

let someNumbers: [Int] = [2,8,15]
var result: Int = 0

for number in someNumbers {
  result += number
}

print(result)  // 25

그런데 reduce메서드를 이용하면 아래와 같다.

let sum: Int = someNumbers.reduce(0, {
  (first: Int, second: Int) -> Int in return first + second
})

print(sum)  // 25
  • 초기값이 0이고
  • 모두 더해줄 것이다 라고 할때 reduce를 한다.
// 초기값이 3이고 someNumbers의 내부 모든 값을 더합니다.
let sumFromThree = someNumbers.reduce(3) { $0 + $1 }
print(sumFromThree)  // 28

sort & sorted

  • sort: 정렬하여 반환 > 원본에 영향을 줌
  • sorted: 기존 리스트를 건들지 않고 새로운 리스트에 정렬된 값을 반환

Monad

값이 있을 수도 있고 없을 수도 있는 컨텍스트를 가지는 함수객체타입이며, 함수객체와 모나드는 특정 기능이 아닌 디자인 패턴 혹은 자료구조라고 할 수 있다.

  • 컨텍스트(Context): 옵셔널처럼 값이 옵셔널 타입으로 감싸져있듯이 값을 담는 컨테이너 역할을 하는것
  • 함수객체: 맵을 적용할 수 있는 컨테이너 타입 (array, dictionary, set 등 컬렉션 타입들)

FlayMap

맵과 사용법이 같으며, 컨테이너로 쌓여져있으면 컨테이너 안의 값으로 작업을 수행하여 다시 포장하여 반환해준다.
플랫맵은 체인 형식으로 사용 가능하나 맵은 불가능 (optionals.flatMap{ $0 }.flatMap{ $0 })

let optionals: [Int?] = [0, 1, 2, nil, 4]
let map = optionals.map { $0 }
let flatMap = optionals.flatMap { $0 }

print(map) // [Optional(0), Optional(1), Optional(2), nil, Optional(4)]
print(flatMap) //[0, 1, 2, 4]