맵, 필터, 리듀스

 

Map

  • 자신을 호출할 때, 매개변수로 전달된 함수를 실행하여 그 결과를 반환
  • Sequence, Collection 프로토콜을 따르거나 옵셔널은 모두 사용 가능
  • 사용 시, 컨테이너가 담고 있던 각각의 값을 매개변수를 통해 적용 후, 다시 컨테이너로 포장하여 반환
  • 이전에 알아본 for - in 구문과 크게 다르지 않다
  • B.U.T. 코드 재사용 측면, 컴파일러 최적화 측면에서 성능 차이 있다
  • 다중 쓰레드 환경에서도 예측지 못한 결과의 부작용을 방지 가능 -> (Thread-Safe ? )
let numbers = [1,2,3,4,5]
var doubledNum = []
var stringNum = []

//for - in
for number in numbers{
	doubledNum.append(number * 2)
    stringNum.append("\(number)")
}

//map
//클로저 간소화한 문법
doubledNum = numbers.map{ $0 * 2 }
stringNum = numbers.map{ "\($0)" }

 

Filter

  • 내부의 값을 걸러서 추출하는 고차함수
  • 맵과 마찬가지로 새로운 컨테이너에 값을 담아 반환
  • Map = 변형, Filter = 거르기
  • 반환 타입 = Bool
let numbers = [1,2,3,4,5]

//짝수 걸러내기
ler evenNumbers = numbers.filter{ $0 % 2 == 0 } // [2,4]

 

Reduce

  • 컨테이너 내부의 콘텐츠를 하나로 합하는 기능을 실행하는 고차함수
  • 두가지 형태로 구현됨
    • 1. 클로저가 각 요소를 전달받아 연산한 후 값을 다음 클로저 실행을 위해 반환하며 컨테이너를 순환
    • 2. 컨테이너를 순환하며 클로저가 실행되지만 클로저가 따로 결괏값을 반환하지 않은 형태
      • 대신 inout 매개변수를 사용하여 초깃값에 직접 연산을 실행함
let numbers = [1,2,3]

// 1번 형태 
//초깃값이 0이고 모든 값을 더한다
var sum: Int = numbers.reduce(0,{ (result: Int, next: Int) -> Int in
	print("\(result) + \(next)")
    // 0 + 1
    // 1 + 2
    // 3 + 3
    return result + next
})

// 2번 형태
//초깃값이 0이고 모든 값을 더한다
sum = numbers.reduce(into: 0,{ (result: inout Int, next: Int) in
	print("\(result) + \(next)")
    // 0 + 1
    // 1 + 2
    // 3 + 3
	result += next
})

옵셔널 체이닝과 빠른 종료

- 옵셔널 바인딩을 하는 것인데 여러 값이 중첩된 형태를 띄우는 형태를 말한다

- 옵셔널을 이해하지 못하고 Swift를 사용한다면 절반도 이해하지 못한다고 할 정도로 중요한 개념이 옵셔널이다

 

옵셔널 체이닝

- 옵셔널에 속해 있는 nil일지도 모르는 프로퍼티, 메서드, 서브스크립트 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정

  - 옵셔널이 nil이면 모두 nil을 반환한다.

  - 당연히 nil이 아니면 제 값을 반환/호출한다

- 옵셔널을 반복 사용하여 옵셔널이 체인처럼 꼬리를 물고 있는 모양을 말한다

 

느낌표(!)의 사용

  • 값을 강제로 추출하기 때문에 옵셔널에 값이 없다면 런타임 오류가 발생한다
  • 또한 반환값은 옵셔널이 아니라는 점이다
  • 따라서 옵셔널이 아닌 타입에 nil 이 들어오는? 오류가 발생한다
  • 무조건 100% nil이 아니라고 확신하더라도 사용을 지양하는 것이 좋다

 

let optionalChaining: Int? = person.address?.building?.room?.number // nil

let oprionalUnwrapping: Int = person.address?.building?.room?.number // 런타임 오류

 

빠른 종료

- 옵셔널을 추출할 때 사용되는 guard가 핵심이다

- if 문과 유사하게 Bool 타입으로 동작한다

- B.U.T. guard 뒤의 구문이 false 라면 else 블록 내부 코드를 실행하게 되는데, 여기서 블록 내부에는 자신보다 상위의 코드 블록을 종료하는 코드가 들어가야합니다

- guard 구문을 사용해서 함수나 블록에서 쓸데없이 모두 진행할 필요없이, 앞에서 옵셔널을 추출한 후에 nil이라면 바로 블록을 나올 수 있다 -> 이게 바로 빠른 종료를 이용하는 것이다

 

guard Bool타입 else{
	예외상황 실행문
    상위 코드 블록을 종료하는 코드
}

 

func enterClub(name: String?, age: Int?){
	guard let name: String = name, let age: Int = age, age > 19, name.isEmpty == false else{
    	print("넌 너무 어려서 못 들어간다!")
        return // 반환 타입이 Void기 때문에 어떠한 값도 반환하지 않는다
    }
    
    print("어서오세요 \(name)")
}

//대충 예가 기억이 안나서 이 사람이 클럽에 들어갈 수 있는가?라는 함수를 작성해보자

func canEnterClub(name: String?, age: Int?) -> Bool{
	guard let name: String = name, let age: Int = age, age > 19, name.isEmpty == false else{
    	print("넌 너무 어려서 못 들어간다!")
        return false // 반환 타입이 Bool이기 때문에 Bool 타입의 값을 반환하는데, 이 사람은 들어갈 수 없으므로 false를 반환
    }
    
    print("어서오세요 \(name)")
}

 

클로저

  • 제네릭, 프로토콕, 모나드 등과 결합이 가능하다
  • 람다와 비슷하다
  • 일정 기능을 하는 코드를 하나의 블록으로 모아놓은 것
  • 함수도 클로저의 일종

형태

  • 전역함수 : 이름 o, 값 획득 x
  • 중첩된 함수 : 이름 o, 다른 함수의 내부의 값 획득 o
  • 축약 문법으로 작성 : 이름 x, 주변 문맥에 따라 값을 획득 o

 

특징

  • 문맥을 통해 매개변수와 반환 값을 추론 가능하기 때문에, 생략이 가능하다
  • 단 한줄만 있다면 암시적으로 그것을 반환 값으로 취급
  • 축약된 전달인자 이름을 사용할 수 있다
  • 후행 클로저 문법을 사용할 수 있다

 

기본 클로저

정렬을 위한 함수 전달

func backWords(first: String, second: String) -> Bool{
	return first > second
}

var names: [String] = [.....]
let reversed: [String] = names.sorted(by: backWords) // 알파벳의 역순으로 정렬된 배열이 저장

정렬을 위한 클로저 전달

let reversed: [String] = names.sorted(by: { (first:String, second: String) -> Bool in
	return first > second
}) // 위의 코드보다 훨씬 간결해졌다.

후행 클로저

- 함수나 메서드의 마지막 전달인자가 클로저일 때, 소괄호를 닫고 작성할 수 있다.

let reversed: [String] = names.sorted(){ (first: String, second: String) -> Bool in 
	return first > second
}

//OR

let reversed: [String] = names.sorted{ (first: String, second: String) -> Bool in 
	return first > second
} // 소괄호 내에 어떠한 매개변수도 없기 때문에, 생략 가능

 

클로저의 간소화

- 클로저를 간소화 함으로써 가독성이 좋아지고, 코드가 간결해진다.

- 하지만 너무 많은 간소화를 통하면 소통이 되지 않을 수도 있으니 조심

 

문맥을 이용한 타입 유추

let reversed = names.sorted{ (first, second) in
	return first > second
} // 매개변수와 반환 타입을 생략 가능

단축인자 이름 사용

let reversed: [String] = names.sorted{
	return $0 > $1
} // $0 = first , $1 = second 라고 생각하면 된다

암시적 반환 표현

let reversed: [String] = names.sorted{ $0 > $1 }

아래로 내려오면서 코드가 훨씬 간결해지는 것을 볼 수 있다.

지금 예로 든 클로저는 간단해서 "어? 무조건 간소화하는 게 좋은 거 아냐?" 라고 생각할 수 있지만, 클로저 내부에서 많은 동작을 요구하고 매개변수 타입또한 복잡해지면 무턱대고 간소화하는 것은 오히려 역효과를 부를 수 있다

 

연산자 함수

- 연산자도 일종의 함수이다

 

// > 연산자 정의
public func ><T : Comparable>(lhs: T, rhs: T) -> Bool

T는 제네릭으로 이후에 자세히 포스팅할 것이다

여기서만큼은 T는 String이라고 생각하면 된다

 

우리는 위에서 reversed를 구하기 위해 (String, String) -> Bool 타입인 클로저를 사용했다

......

....?

> 연산자도 (String, String) -> Bool 타입이네?

 

그렇다, 클로저에도 타입이 존재하기 때문에, 매개변수의 클로저와 같은 타입의 클로저/함수는 매개변수로 전달할 수 있다

-> 쉽게 생각하면 된다.

var a: Int = 0 , var b: Int = 10  // 여기서 a, b 모두 Int지 않은가?

func printInt(num: Int){

    print(num)

} // 이 함수에서 필요한 매개변수인 num과 타입이 같은 a, b는 매개변수의 인자가 될 수 있는 것과 같다

 

다시 연산자 함수로 돌아가서

let reversed: [String] = names.sorted(by: >)

이렇게 간단하게 연산자 함수를 통해 작성도 가능하다

 

값 획득

- 클로저에서 개인적으로 가장 어려웠던 개념이다

- 이 자체가 어렵다기 보다는 다른 개념들까지 생각하며 염두하게 되면 헷갈리는 개념이기도 하다

- 값 획득을 통해 정의한 상수/변수가 더이상 존재하지 않아도 내부에서 그 값을 참조/수정이 가능

  1. 값 획득을 통해 정의한 상수/변수

  2. 존재하지 않아도

  3. 내부에서 그 값을 참조/수정이 가능

3가지로 나누어서 보자

 

//makeIncrementer 함수는 하나의 Int 매개변수를 받아서
// ()->Int 타입의 클로저를 반환한다

//incrementer 함수는 매개변수로 아무것도 받지 않고
// Int 타입을 반환한다

func makeIncrementer(amount: Int) -> (()->Int){
    var runningTotal = 0
    func incrementer() -> Int{//중첩된 함수
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}

let incrementByTwo = makeIncrementer(amount: 2)

let first = incrementByTwo() // 2
let second = incrementByTwo() // 4
let third = incrementByTwo() // 6

 

무언가 이상하다

incrementByTwo 라는 상수에 ()->Int 타입의 클로저를  반환 한 것은 알겠다.

근데 함수안에 있는 변수들은 함수가 종료될 때, 메모리에서 해제된다고 알고 있는데, 왜 값이 계속 증가해?

 

천천히 살펴보자

우선 incrementByTwo = makeIncrementer(amount: 2) 는 ()->Int 타입의 클로저라는 것을 잊지말라

1. first에서 incrementByTwo()를 호출했다

2. incrementByTwo() 내에 있는 runningTotal 변수가 생성된다.

3. incrementer 라는 함수가 생성된다.

4. incrementer 라는 함수가 반환된다.

5. incrementer() 함수가 실행된다.

6. runningTotal 값이 0에서 2로 증가한다

7. 2가 된 runningTotal이 반환된다.

 

이 순서인데.... 아니 4번에 incrementer라는 함수가 반환된 시점에서 runningTotal 변수는 죽은거아냐?

makeIncrementer하는 함수가 끝났잖아?

 

-> 맞는 말이다, 하지만 값 획득을 통해 외부의 값을 사용하는 것이다.

-> 위에서 3가지 단계로 나눠놓은 것을 빗대어 설명하자면

-> 1. 값 획득을 통해 정의한 상수/변수 : amount, runningTotal

-> 2. 존재하지 않아도 : makeIncrementer 함수가 종료되어서 메모리에서 사라져도

-> 3. 내부에서 그 값을 참조/수정이 가능 : incrementer하는 함수 내부에서 해당 값을 참조/수정이 가능

 

이러한 과정을 통해 runningTotal, amount 값을 사용할 수 있다.

 

 

클로저는 참조타입

- 이전 포스팅들에서 말했듯, 값 타입과 참조 타입이 존재한다

- 클로저는 참조타입이다.

- 만약 클로저가 값타입이라고 생각해보면 위의 incrementByTwo는 동작하면 안된다

  -> incrementByTwo 는 let 즉, 상수로 선언되었다.

  -> 상수로 선언된 값 타입 ?? struct!! 상수로 선언된 구조체 인스턴스의 내부 값을 변경할 수 없지 않았던가

  -> 근데 incrementByTwo는 상수인데 내부 값이 증가되는 것을 보면 클로저는 참조 타입이라는 것을 역으로 보일 수 있는 것이다

 

 

탈출 클로저

- 함수의 전달인자로 클로저가 전달되고, 해당 함수가 종료되고 클로저가 호출될 때, 클로저가 함수를 탈출한다고 표현한다

- 예를 들어 비동기 작업을 실행하는 함수들은 클로저를 ComplementHandler 전달인자로 받아온다.

  -> 비동기 작업으로 함수가 종료되고 난 후, 호출할 필요가 있는 클로저를 상요해야할 때 탈출 클로저가 필요하다

 

하지만 지금까지 알아본 함수들은 모두 @escaping 키워드를 찾을 수 없었다

 -> 모두 비탈출 클로저 였기 때문이다

 

typealias VoidVoidClosure: () -> Void

func withNoescapeClosure(closure: VoidVoidClosure){
	closure()
}

func withEscapingClosure(completionHandler: @escaping 
						VoidVoidClosure) -> VoidVoidClosure{
	return completionHandler
}

class SomeClass{
	var x = 10
    
    func runNoescapeClosure(){
    // 비탈출 클로저에서 self 키워드 사용은 선택
    	withNoescapeClosure{ x = 200 }
    }
    
    func runEscapingClosure -> VoidVoidClosure{
    // 탈출 클로저에서 self 키워드 사용은 필수!!
    	return withEscapingClosure{ self.x = 100 }
    }
}

let instance: SomeCLass = SomeClass()
instance.runNoescapeClosure()
print(instance.x) // 200

let returnedClosure: VoidVoidClosure = instance.runEscapingClosure()
returnedClosure()
print(instance.x) // 100


 

 

 

프로젝트가 내가 만든 프로젝트가 아니거나, 저장 공간을 옮겼을 때 나타나는 현상으로 보인다.

 

해결 방법!!

 

말 그대로 ..../경로에 Info.plist가 없어서 빌드가 되지 않는 것이다.

따라서 방법은 2가지

  1. 해당 경로에 Info.plist를 가지고 간다. -> 옮긴다
    1. 해보진 않았지만, 될 것 같다!!

1. Xcode 내의 프로젝트를 누른다

대충 이런 화면이 나온다

 

2. 좌측에 PROJECT와 TARGETS 중 TARGETS에 있는 자신의 프로젝트 이름을 클릭한다.

 

3. 뭐 Architectures, Assets, Build Locations ... 등 많은데 Packaging 을 찾는다!

 

4. 눈 뜨고 잘 보면 Info.plist라는 항목이 보인다.

 

5. 해당 항목에 있는 Value를 에러에서 나온 경로를 붙여준다.

 

 

6. 열심히 작업한다...

 

이 현상만 3번 겪었는데, 3번만에 작성하는 내가 웃기다...

이제는 그때그때마다 작성해놔야겠다

+ Recent posts