CI/CD 중, 하나인 Bitrise에서 iOS 프로젝트를 컴파일 해보다가 만난 에러를 정리했다.

첫번째 에러: xcodebuild: error: Could not resolve package dependencies:

Package.resolved file is corrupted or malformed; fix or delete the file to continue: unsupported schema version 2

 

Package.resolved 파일 때문에 일어나는 에러인데, 해결방법은 다음과 같다.

1. 프로젝트폴더/프로젝트이름.xcodeproj/project.xcworkspace/xcshareddata/swiftpm 폴더 아래에 있는 Package.resolved를 지우고 다시 시도한다.

2. .gitignore 파일에 Package.resolved 파일을 추가한 후, 다시 시도한다.

 

두번째 에러: xcodebuild: error: Failed to build workspace KeywordNews with scheme KeywordNews.

Reason: Cannot test target “KeywordNewsTests” on “iPhone 8 Plus”: iPhone 8 Plus’s iOS Simulator 15.2 doesn’t match KeywordNewsTests’s iOS Simulator 15.5 deployment target.

Cannot test target “KeywordNewsUITests” on “iPhone 8 Plus”: iPhone 8 Plus’s iOS Simulator 15.2 doesn’t match KeywordNewsUITests’s iOS Simulator 15.5 deployment target

 

뭔가 버전이 맞지 않는다고 나오는 에러인데, XCode 버전을 맞춰주면 해결된다.

1. Bitrise의 해당 앱의 Workflow로 들어간다.

2. Stack & Machines 메뉴에서 XCode의 버전을 현재 자신의 맥에 설치된 XCode 버전과 맞춰준 후, 다시 시도한다.

간단한 에러들이고, 해결방법도 간단하지만, 

혹시 몰라 정리해 두었다.

UIImageView의 코너를 조금 둥글게 만드는 방법을 알아보자.

 

UIImageView를 그냥 사용하면 요렇게 각지게 표현이 된다.

 

cell.imgView.layer.cornerRadius = 10

UIImageView에 위와 같이 설정해주면,

위와 같이 둥글둥글해지게 된다.

 

 

UICollectionView에 대해서 공부를 하다가, 

Cell을 만들고 배치를 해봤다.

 

내가 생각한 건, 좌 / 우로 배치가 되어야 하는데,

사진처럼 위 / 아래로 배치가 되버린다..

 

셀크기를 딱 CollectionView의 절반으로 했는데.. 왜 안될까..

뭐가 문제지 진짜 길이 하나하나 다 재보면서, 하루종일 알아보다가 찾아냈다.

 

원인은 UICollectionViewDelegateFlowLayout을 사용한게 원인이었는데,

해당 델리게이트의 

https://developer.apple.com/documentation/uikit/uicollectionviewdelegateflowlayout/1617696-collectionview

요 함수 때문이었다.

뭐, 아이템들의 간격을 설정해주는 함수다.

 

해당 함수의 설명을 보면, 요런게 있는데,

만약 해당 함수가 구현되어 있지 않으면, minimumInteritemSpacing 프로퍼티 값을 사용한다는 거다..

 

developer.apple.com/documentation/uikit/uicollectionviewflowlayout/1617706-minimuminteritemspacing

 

Apple Developer Documentation

 

developer.apple.com

위 링크가 minimumInteritemSpacing에 대한 설명인데,

기본값이 10.0 이다. 그래서.. 그래서.. 위/아래로 셀이 나왔던거다.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
   return 0
}

이 함수만 딱 설정해주면, 

 

요렇게 셀 두개가 딱 붙어있는 것을 볼 수 있다.

 

MyAlbum.zip
0.04MB

코드파일이다. 보통은 깃헙에 코드를 올리지만, 뭐랄까 간단한 것 같은 코드라서 그냥 파일로..

UICollectionView에 대해서 공부하던 중에,

https://theswiftdev.com/ultimate-uicollectionview-guide-with-ios-examples-written-in-swift/

 

위 포스트를 보게 되었고, 이분이 만든 CollectionView 프레임워크를 분석해보자고 마음 먹었다.

사실.. CollectionView 사용법이 나한테는 생각보다 어렵게 느껴져서,

이분이 만든 프레임워크와 예제를 분석하다 보면, 쉽게 이해될 수도 있을 것 같다는 조금의 희망을 가지고 시작한거다.

겸사겸사 swift 공부도 해볼겸..

 

그러다가, 아래와 같은 코드를 만나게 되었는데,

open 이라는 키워드를 처음 본 것 같아서, 이참에 알아봐야겠다 싶었다.

open class CollectionView: UICollectionView {

    open var source: Source? = nil {
        didSet {
            self.source?.register(itemsFor: self)

            self.dataSource = self.source
            self.delegate = self.source
        }
    }
}

 

https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID3

Swift 문서에서 이런걸 찾게 되었다.

Access Levels

Swift provides five different access levels for entities within your code. These access levels are relative to the source file in which an entity is defined, and also relative to the module that source file belongs to.

  • Open access and public access enable entities to be used within any source file from their defining module, and also in a source file from another module that imports the defining module. You typically use open or public access when specifying the public interface to a framework. The difference between open and public access is described below.
  • Internal access enables entities to be used within any source file from their defining module, but not in any source file outside of that module. You typically use internal access when defining an app’s or a framework’s internal structure.
  • File-private access restricts the use of an entity to its own defining source file. Use file-private access to hide the implementation details of a specific piece of functionality when those details are used within an entire file.
  • Private access restricts the use of an entity to the enclosing declaration, and to extensions of that declaration that are in the same file. Use private access to hide the implementation details of a specific piece of functionality when those details are used only within a single declaration.

Open access is the highest (least restrictive) access level and private access is the lowest (most restrictive) access level.

Open access applies only to classes and class members, and it differs from public access by allowing code outside the module to subclass and override, as discussed below in Subclassing. Marking a class as open explicitly indicates that you’ve considered the impact of code from other modules using that class as a superclass, and that you’ve designed your class’s code accordingly.

 

----------

 

Swift에는 5가지 접근 레벨이 있다.

5가지 레벨에는 open, public, internal access, file-private, private 가 있다.

 

위의 영어로 entities라고 있는데, 엔티티프로퍼티, 타입, 함수 등등을 이야기 한다고 합니다.

모듈(Module)은 쉽게 프레임워크라고 생각하면 좋을 것 같다.

 

open

- class에와 class member에만 사용할 수 있다.

- 모든 곳에서 sub classing이 가능하다.

 

public

- 클래스에 붙어있는 경우, 모듈 내에서만 sub classing이 가능하다.

 

Internal access

- 모듈 안에서 사용 가능

 

File-private

- 소스 파일 안에서만 사용 가능

 

private

- 소스 파일 안, 해당 선언 안에서만 사용 가능

아래 링크의, Swift : Closure 관련 글을 공부할겸 번역해 보았습니다.

아직 많이 부족한 영어실력을 가지고 있어서, 많은 부분 오역이 있을 수 있으며, 자연스럽지 못한 번역도 있습니다.

이 점 양해 부탁 드리겠습니다.

감사합니다.

https://medium.com/@abhimuralidharan/functional-swift-all-about-closures-310bc8af31dd

 


apple docs에 따르면, 클로저는 독립적인 기능의 블록이고, 그것은 당신의 코드에서 전달되거나 사용될 수 있다.

저는 클로저의 모든것을 이 글의 담으려고 노력했습니다. 그것은 조금 길어질 수도 있지만, 충분히 읽을만한 가치가 있다고 생각합니다.

저 남자가 누구인지는 모르겠지만, 사진은 좋지 않나요?

몇몇 분들은 함수와 클로저는 구문만 다른 같은 것이라고 얘기합니다. 다른말로, 함수는 특별한 종류의 함수라는 것이지요.

클로저는 1급 객체입니다. 그래서, 중첩될 수 있고, 전달될 수 있습니다.

가장 먼저, 함수가 무엇인지 알아볼까요?


함수 :


함수는 func  키워드로 선언합니다. 함수는 여러개의 파라미터를 가질 수 있고, 반환값이 없거나, 1개이거나, 여러개일 수 있습니다. (tuple을 이용하면, 우리는 여러개의 값을 반환할 수 있습니다.)

함수는 두개의 파라미터를 가지고 있고, tuple을 이용해서 두개의 반환값을 가지고 있습니다.

위의 함수는 두개의 integer 입력 값과 tuple을 이용한 두개의 integer 반환값을 가집니다.

만약 tuple을 잘 모르시겠다면, 여기서 저의 다른 글을 읽어보세요.


함수타입


하나의 Int 값을 받아서, 하나의 Int 값을 반환해주는 간단한 함수를 생각해 봅시다.

func doSometingWithInt(someInt:Int) -> Int {
return someInt * 2
}

모든 함수는 함수타입을 가지고 있고, 함수타입은 파라미터 타입과 리턴 타입으로 만들어 집니다.

위의 함수타입은 다음과 같습니다.

(Int) -> (Int)

만약 위의 함수가 두개의 파라미터를 가지고 하나의 반환값을 가진다면, 함수타입은 이렇게 변합니다.

(Int, Int) -> (Int)

함수타입은 중첩된 함수의 리턴타입이나, 파라미터로 사용될 수 있습니다.

func personInTheHouse() -> ((String) -> String) {
  func doProcess(process: String) -> (String) { // nested function
    return “The person is \(process).”
  }
  return doProcess // or return doProcess(process:)
}

let person = personInTheHouse()
print(person(“playing cricket”)) // prints “The person is playing cricket.”

 


클로저 정의


클로저 구문을 이해하는 것은 굉장히 쉽습니다. 당신은 이제 함수타입이 무엇인지 알고 있습니다.

중괄호 안에 함수타입을 넣으세요, 그리고 리턴타입 뒤에 in 키워드를 추가해주세요.

statement는  in 키워드를 따라옵니다. 이 것이 클로저 표현방식입니다.

{ 
  (params) -> returnType in
  statements
}

클로저의 표현방법들은 가벼운 구문으로 작성되어 이름이 없고, 클로저 컨텍스트 안의 값들을 읽을 수 있습니다. 클로저 표현 구문은 위의 주어진 코드와 같습니다.

  • 클로저 표현은 명확성이나 의도를 잃지 않으면서 단축 형식으로 클로저를 작성하기 위한 몇가지 구문 최적화를 제공합니다.
  • 클로저 표현 구문의 파라미터들은 in-out 파라미터가 될 수 있지만, 기본값을 가질 수는 없습니다. 가변형 파라미터는 이름을 줄 경우 사용할 수 있습니다. Tuple도 파라미터 타입과 리턴타입에 사용될 수 있습니다.

두개의 Int값을 받아서, 합을 구하는 함수를 만들어 봅시다.

func addTwoNumbers(number1:Int, number2:Int) -> {
return number1 + number2
}
addTwoNumbers(number1: 8, number2: 2) // result is 10

이제, 같은 동작을 하는 클로저를 만들어 봅시다.

let closure: (Int, Int) -> Int = { (number1, number2) in
return number1 + number2
}
closure(8,2) // the result is 10

위의 함수와 클로저는 줄수나 가독성이 비슷합니다.

클로저를 조금 더 간단하게 만들어 봅시다.


아큐먼트 이름 속기법


클로저의 아큐먼트는 이름대신 위치로 참조될 수 있습니다. ($0, $1, $2, ...)

var shortHandClosure:(Int,Int)->Int = {
  return $0 + $1
}
shortHandClosure(8,2) // result is 10

또한, 위의 클로저는 내부에 단 하나의 표현식만 가지고 있기 때문에, return 구문을 생략할 수 있습니다. 만약, 클로저 내부의 여러 줄의 코드가 있다면, return  구문을 생략할 수 없습니다.

var superShortClosure:(Int,Int)->Int = {$0 + $1}

이제, 우리가 처음에 만들었던 함수와는 매우 다른 모습이 되었고, 이 표현은 좀 더 간단하고 이해하기 쉽습니다.

NOTE : 만약, 우리가 return 구문과 함께 print 구문을 쓴다면, 우리는 return 구문을 생략할 수 없습니다.

 

우리의 목표는 좀 더 적은 코드를 작성하는 것입니다.


클로저 타입 추론


다음 코드에서 클로저 타입은 (Int,Int)->Int  으로 추론 됩니다.

let inferredClosure = {(x:Int,y:Int)->Int in x + y }
inferredClosure(1,99) // result is 100

리턴 타입도 추론될 수 있습니다.

let inferredReturnTypeClosure = {(number:Int) in number*number }

여러분이 예상하기에 위의 클로저 타입은 무엇이 될까요? 하나의 Int 값을 리턴합니다.

그러므로, 위의 클로저 타입은 (Int) -> Int  입니다.


아무것도 받지 않고, String을 반환하는 클로저


클로저는 () -> (SomeType)  타입이 될 수 있습니다.

다음의 클로저는 파라미터가 없고, String을 반환하고 있습니다.

let callStringWtihClosure: () -> String = { () in 
return “hello”
}
//____________
print(callStringWtihClosure()) // prints “hello”

그러므로, 우리는 이곳에서 입력 파라미터를 신경 쓰지 않아도 되고, 클로저 내부의 () in 을 생략할 수 있습니다.

아래의 클로저 타입은 () -> String 이 됩니다.

let callStringWtihClosure: () -> String = {return “hello”}

또한, 위의 클로저는 String을 반환하고, 아무런 파라미터도 받지 않으므로, 우리는 타입도 생략할 수 있습니다.

아래 클로저 타입은  () -> String  이 됩니다.

let callStringWithClosureWithoutType = { “hi, I’m a closure too” }


Swift에서의 함수와 클로저는 1급 타입:


Swift에서의 함수와 클로저는 1등급 시민 입니다. 왜냐하면, 보통의 값으로 다룰 수 있기 때문입니다. 예를들어,

  • 함수와 클로저를 로컬 변수에 할당할 수 있다.
  • 함수와 클로저를 아규먼트로 전달할 수 있다.
  • 함수와 클로저를 반환할 수 있다.


클로저를 이용한 메소드와 완료 콜백


여기에 클로저를 이용한 메소드와 완료 콜백에 대한 제 글이 있습니다. 읽어보세요.

아래의 코드는 세개의 파라미터를 가지고 있는 함수를 가지고 있습니다.

하나는 딕셔너리 타입이고, 나머지 두개는 처리 후 동작하는 클로저(콜백 함수) 입니다.

//: Playground - noun: a place where people can play
import UIKit

var shoppingList = ["key":"value"]

// Initialize the dictionary
 func callSomeMethodWithParams(_ params: [AnyHashable: Any], onSuccess success: @escaping (_ JSON: Any) -> Void, onFailure failure: @escaping (_ error: Error?, _ params: [AnyHashable: Any]) -> Void) {
    
    print("\n" + String(describing: params))
    
    let error: Error? = NSError(domain:"", code:1, userInfo:nil)

    var responseArray: [Any]?
    responseArray = [1,2,3,4,5]
    
    if let responseArr = responseArray {
        success(responseArr)
    }
    if let err = error {
        failure(err, params)
    }

}

callSomeMethodWithParams(shoppingList, onSuccess: { (JSON) in
    print("\nSuccess. Response received...: " + String(describing: JSON))
}) { (error, params) in
    if let err = error {
        print("\nError: " + err.localizedDescription)
    }
    print("\nParameters passed are: " + String(describing:params))
}

@escaping  키워드는 아래에서 설명하겠습니다.


함수에서 클로저 반환


우리는 함수에서 클로저를 반환할 수 있습니다. 아래 코드를 확인해 보세요.

// return a closure from a function
var addClosure:(Int,Int)->Int = { $0 + $1 }
func returnClosure() -> (Int,Int)->Int {
return addClosure
}
//____________________________________
returnClosure()(10,20) // returns 30
var returnedClosure = returnClosure() // returns a closure of type (Int,Int)->Int
returnedClosure(20,10) // returns 30


클로저를 사용한 객체 초기화와 변경


함수는 보통 객체를 구성하거나 조작하기 위해서 사용됩니다.  하지만, 클로저 코드가 간결하고 이해할 수 있게 보입니다.

UIView()  를 만들고,  그것을 조작하고 반환하는 () -> UIView() 타입의 함수를 만듭니다.

func setupView() -> UIView {
let view = UIView()
view.backgroundColor = .red
return view
}
let someView = setupView() // returns a red view

아래의 있는 setupViewUsingClosure는 위의 코드와 같은 일을 하는 () -> UIView()  타입의 클로저 입니다.

let setupViewUsingClosure = { () -> UIView in
let view = UIView()
view.backgroundColor = .green
return view
}
let someOtherView = setupViewUsingClosure() // returns a green view

위의 메소드는 클로저를 이용해서 직접 객체를 초기화하면 좀 더 간단해질 수 있습니다.

let setupViewUsingClosure: UIView = {
let view = UIView()
view.backgroundColor = .green
return view
}() //IMPORTANT!!! I have added () at the end.

위의 setupViewUsingClosure 는 UIView 객체 입니다.

RHS(Right Hand Side)의 표현식은 사실 타입이  () -> UIView 인 클로저 입니다.

좀 더, 명확하게 만들어 봅시다.

let aClosure= { “hello, I’m a closure too.” }
let aString= { “hello, I’m a closure too.” }() // added () at the end.

첫 번째 구문은 타입이  () -> String 인 클로저 입니다.

두 번째 구문에 우리는  () 를 끝에 추가했고, 그것은 클로저를 호출하게 됩니다.

그래서, 그것은 클로저를 실행하고  String 을 반환하게 됩니다. 만약, 이 부분이 명확하게 이해되지 않으시면, 위쪽의 클로저 타입 추론 섹션 부분을 참조 하시면 됩니다.

게으른 할당:

다음의 게으른 할당에 대해서 읽어주시길 바랍니다. 이 부분은 Swift에서 정말 중요한 부분입니다.

꼬리 클로저

만약 당신이 함수의 아규먼트로 클로저를 전달해야 하고, 그 클로저의 길이가 길다면 꼬리 클로저를 사용하는 것은 매우 유용할 것입니다.

꼬리 클로저는 그것이 함수의 아규먼트라도 함수콜 괄호 다음에 씁니다.

함수 호출할 때에, 꼬리 클로저를 사용하게 되면, 클로저에 대한 라벨은 입력하지 않습니다.

func doSomething(number:Int,onSuccess closure:(Int)->Void) {
closure(number * number * number)
}
doSomething(number: 100) { (numberCube) in
print(numberCube) // prints  1000000
}

클로저의 아규먼트 라벨인  onSuccess 는 함수 호출에는 없다. 클로저가 함수 파라미터에 속해 있더라도, Swift는 그것을 꺼내 좀 더 가독성 높은 코드로 만듭니다.


값 캡쳐링


클로저는 정의된 컨텍스트에서 모든 상수 및 변수에 대한 참조를 캡쳐하거나 저장할 수 있습니다. 이것은 상수 및 변수에 대한 폐쇄라고 알려져 있습니다. (이 부분은 이 글의 뒷 부분에서 설명됩니다.) Swift는 당신을 위해서, 모든 캡처링의 메모리 관리를 다룹니다.

아래 코드를 생각해 봅시다.

// capturing values
var i = 0
var closureArray = [()->()]()

for _ in 1…5 {
  closureArray.append {
    print(i)
  }
  i += 1
}

// here i will be 5
closureArray[0]() // prints 5
closureArray[1]() // prints 5
closureArray[2]() // prints 5
closureArray[3]() // prints 5
closureArray[4]() // prints 5

for loop의 모든 반복에서 우리는  ()->() 타입의 비어있는 클로저를 생성하고, 그것을  closureArray 라고 부르는 배열에 추가합니다. 클로저는  i 를 출력하는 하나의 구문만 포함하고 있습니다. 클로저는 현재  i 의 주소를 캡쳐하고 매번 우리는  i 에 접근하고,  i 는 현재 값을 반환합니다.

위에서 언급한대로, 메모리 이슈는 swift가 다루고 있는 이 캡쳐링과 관련이 있습니다. 우리는 걱정할 필요가 없습니다.

중요한 점은 다음과 같습니다.

  • 클로저는 상수와 변수를 그것이 선언된 컨텍스트 안에서 캡쳐할 수 있습니다.
  • 그런다음, 클로저는 상수와 변수를 그것의 몸체(컨텍스트) 안에서 참조하고 수정할 수 있습니다. 심지어 상수와 변수가 선언되는 원래의 범위가 더 이상 존재하지 않더라도.
  •  또한, Swift는 더 이상 필요하지 않는 변수를 제거하는 것을 포함한 모든 메모리 관리를 다룹니다.
  • 만약 당신이 클로저를 클래스 인스턴스 프로퍼티로 할당하고, 참조된 인스터스나 멤버들의 인스턴스를 그 클로저가 캡쳐하면, 당신은 클로저와 객체 사이에 강력한 순환 참조를 만들게 됩니다. Swift 캡쳐 리스트를 사용해서 이러한 강력한 순환 참조를 제거합니다.


캡쳐 리스트 생성


만약, 우리가 이 행동 (값을 캡쳐링 하는 것)을 막고,  i 의 값을 출력하고 싶다면 (클로저 안에서 값이 캡쳐링 된 후에 변하더라도), 우리는 명시적으로 값을 캡쳐할 수 있습니다. 다음과 같이 캡쳐 리스트를 이용해서

var closureArray2 = [()->()]()
var j = 0

for _ in 1…5 {
  closureArray2.append { [j] in
    print(j)
  }
  j += 1
}

// here i will be 5
closureArray2[0]() // prints 0
closureArray2[1]() // prints 1
closureArray2[2]() // prints 2
closureArray2[3]() // prints 3
closureArray2[4]() // prints 4

이 방법으로, 우리는 j 변수의 변하지 않는 복사본을 유지할 수 있습니다. 이 복사본 덕분에 클로저 바깥에서의 j 변수에 대한 변경은 클로저에 영향을 끼치지 않습니다. j는 여기서 let 상수이고, 변하지 않습니다.

우리는 여러개의 변수를 캡쳐리스트에 추가할 수 있습니다.

closure.append { [j,k,l] in
  print("\(j) \(k) \(l)")
}

also, you can have alias names for the values captured.

또한, 캡쳐된 값의 별명을 지어줄 수도 있습니다.

closure.append { [a = j, b = k, c = l] in
  print("\(j) \(k) \(l)")
}


Escaping 클로저 vs non-escaping 클로저


두 종류의 클로저가 있습니다.

  • escaping 클로저는 함수 이후에 호출되는 클로저 입니다. 함수에서 return 구문이 실행 된 후 실행됩니다. 다른말로, 클로저가 전달된 함수보다 더 오래 존재합니다.
  • non-escaping 클로저는 함수 안에서 호출됩니다. 함수의 return 구문전에 실행됩니다.

@noescape 는 Swift2에서의 속성 이었고, Swift3에서는 사라졌습니다.  @noescape 는 Swift3에서 기본으로 적용되었습니다. 왜냐하면  Swift3 에서의 클로저는 기본적으로 non-escaping 클로저이기 때문입니다. escaping 클로저는 따로 표시되어야 하고,  @escaping 속성이 그일을 합니다.

클로저는 함수의 아규먼트로 전달될 때, 함수를 escape 한다고 합니다. 하지만, 함수가 종료된 후에 호출됩니다. 클로저를 파라미터의 하나로 받는 함수를 선언할 때, 파라미터 타입전에 @escaping 속성을 해당 클로저가 escape 되는것을 허락받았다는 의미로 사용할 수 있습니다.

아래의 completion handler는 escaping 클로저의 좋은 예제 입니다. 그 클로저는 길고 긴 작업이 완료되었을 때, 실행됩니다. 그래서, 그 클로저는 그 클로저를 생성한 함수보다 오래 생존합니다.

또 다른 예저는 비동기 프로그래밍 입니다. 비동기로 실행되는 클로저는 언제나 원본 컨텍스트를 escape 합니다.

func someFunctionWithNonescapingClosure(closure: () -> Void) {
  closure()
}

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
  completionHandler()
}

class SomeClass {
  var x = 10
  func doSomething() {
    someFunctionWithEscapingClosure { self.x = 100 }
    someFunctionWithNonescapingClosure { x = 200 }
  }
}

You may have noticed that the @escaping attribute precedes the type of the parameter, not the name. This too is new in Swift 3.

당신은 아마  @escaping 속성이 파라미터 타입앞에 오는 것을 알 수 있을 것입니다. 이것은 Swift3에서 처음 나오는 것 입니다.

@escaping 와 함께 클로저를 만드는 것은, 클로저 안에서 명시적으로  self 를 사용해야 한다는 것을 뜻합니다. 위의 코드를 예로 들어보면,  someFunctionWithEscapingClosure(_:) 의 전달되는 클로저는 escaping 클로저 입니다. 즉, 그것은  self 를 명시적으로 사용해야 한다는 것을 뜻합니다. 그와는 반대로,  someFunctionWithNonescapingClosure(_:) 로 전달되는 클로저는 non-escaping 클로저 입니다. 그것은  self 를 묵시적으로 사용할 수 있다는 것을 뜻합니다.

당신은  self 를 non-escaping 클로저 안에서도 아무런 문제 없이 사용할 수 있습니다. 왜냐하면 클로저가 함수의 return 전에 적용되기 때문입니다. 클로저에서는  self 의 약한 참조(weak references)를 사용할 필요가 없습니다. 이것은 꽤 좋은 점입니다.


메모리 관리


위의 "캡쳐 리스트 생성" 을 읽고, 캡쳐링과 캡쳐 리스트 생성에 대해 이해해야 합니다.

강한 참조 순환(strong reference cycle)은 두개의 객체가 서로에게 강한 참조를 유지하고 있을 때, 발생합니다. 이 순환(cycle)의 이유는, 두개의 객체의 할당이 해제되지 않기 때문에, 그 객체들의 참조 카운트(reference count)가 0으로 떨어지지 않습니다. 기본적으로 클로저는 캡쳐된 값의 강한 참조를 유지 합니다.

다음에 코드를 생각해 봅시다.

class InterviewTest {
  var name: String = "Abhilash"
  lazy var greeting : String = {
    return "Hello \(self.name)"
  }()
}
//-------------------------
let testObj = InterviewTest()
testObj.greeting // result is Hello Abhilash

lazy 변수인  greeting 은  InterviewTest 클래스에 로컬 변수인 name 에 접근하여, 문자열을 반환 합니다. 우리는 직접적으로 그 값에 접근할 수 없습니다. 오로지,  self 키워드를 사용해야만 합니다. 하지만 설명했듯이, 기본적으로 클로저는 캡쳐된 값에 대해 강한 참조를 유지 합니다. 그것은 아마 순환 참조를 야기할 수 있습니다.

우리는 캡쳐 리스트의  weak 나 unowned 참조의  self 를 통해서 강한 순환 참조(strong reference cycle)을 깰 수 있습니다.

Weak

weak 참조는 그것이 참조하는 인스턴스를 강력하게 유지하지 않는 참조 입니다. 그래서 ARC가 참조된 인스턴스를 정리하는 것을 멈추지 않습니다. 이러한 행동은 참조가 강한 순환 참조가 되는 것을 방지 합니다.

weak 참조는 nil 일 수 있기 때문에, 캡쳐된 값은 optional이 됩니다. 그르므로, 우리는 guard 를 사용해서 안전하게 언랩핑 해야 합니다.

lazy var greeting : String = { [weak self] in
  guard let strongSelf = self else { return “” }
  return “Hello \(strongSelf.name)”
}()


Unowned (소유자가 없는)


weak 참조와 같이, unowned 참조는 그것이 참조하는 인스턴스를 강력하게 유지하지 않는 참조 입니다. 그러나, weak 참조와는 다르게, unowned 참조는 다른 인스턴스가 같거나 더 긴 수명을 가지고 있을 때 사용 됩니다.

lazy var greeting : String = { [unowned self] in
  return “Hello \(self.name)”
}()

우리가  unowned 를 사용하는 것의 의미는  unowned 레퍼런스는 클로저 안에서 절대로  nil 이 되지 않는다는 것을 뜻합니다. 그렇지 않으면 앱은 Crash 나게 됩니다.

우리는  weak 와 unowned 를 캡쳐 리스트의 어느 값에나 사용할 수 있고, 또한 별명들과도 결합할 수 있습니다.

lazy var greeting : String = { [unowned unownedSelf = self] in
  return “Hello \(unownedSelf.name)”
}()

 


어떻게 클로저의 파라미터로 self를 캡쳐할 수 있을까요?


당신은 내부 클로저 파라미터 앞이나 내부 파라미터 괄호 전에 self 를 캡쳐해야 합니다. 아래 코드를 살펴보세요. 아래 코드는 어떻게 파라미터 하나로  self 를 캡쳐할 수 있는지 설명합니다. 만약 당신이 여러개의 파라미터를 사용하고 있다면, 당신은 괄호를 사용해야 합니다.

이 곳에서 클로저에 관한 것을 더 읽어보세요.

 


자동 클로저


오토클로저는 함수의 아규먼트로 전달되는 표현을 감싸기 위해 자동으로 생성되는 클로저 입니다. 그것은 어떤 아규먼트도 받지 않고, 그것이 호출될 때, 그것은 안쪽에 감싸진 표현의 값을 리턴합니다. 이러한 구문상의 편의로 인해서 명시적 클로전 대신 일반 표현식을 사용하여, 함수 매개 변수 주위의 중괄호를 생략할 수 있습니다.

오토클로저를 자주 사용하는 것은 코드를 읽기 어렵게 만듭니다.

var customersInLine = [“Chris”, “Alex”, “Ewa”, “Barry”, “Daniella”]
print(customersInLine.count)
// Prints “5”
let customerProvider = { customersInLine.remove(at: 0) } // this is of type ()->String
print(customerProvider()) // prints Chris.. the remove(at:) returns a String.

위의 함수의 아규먼트로 전달되는 함수를 생각해 보세요.

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: () -> String) {
  print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } ) // we cannot omit {}
// Prints "Now serving Alex!"

위의 serve(customer:) 함수는 고객의 이름을 반환하는 명시적인 클로저를 가집니다. 아래의  serve(customer:) 함수는 같은 연산을 수행하지만, 명시적인 클로저를 가지는 대신, @autoclosure 속성으로 마킹된 파라미터를 가집니다. 이제 클로저 대신 String 아규먼트로 함수를 호출할 수 있습니다. 해당 아규먼트는 자동으로 클로저로 변환됩니다. 왜냐하면,  serve(customer:)  파라미터의 타입은  @autoclosure 속성으로 마크되어 있기 때문입니다.

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
  print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"

만약 오토클로저의 이스케이프를 허용하고 싶다면,  @autoclosure 와  @escape 속성을 둘다 사용하면 됩니다.

그래서, 짧은 표현식은

serve(customer: { customersInLine.remove(at: 0) }) // need {}

아래가 됩니다.

serve(customer: customersInLine.remove(at: 0)) // omit {}

 


오토클로저 예제 하나 더!


우리는 다음과 같이 UIView 의 extension을 추가할 수 있습니다.

extension UIView {

class func animate(withDuration duration: TimeInterval, _ animations: @escaping @autoclosure () -> Void) {
        UIView.animate(withDuration: duration, animations: animations)
    }

}

그래서,

UIView.animate(withDuration: 2.5) { 
    self.view.backgroundColor = .orange
}

아래가 됩니다.

UIView.animate(withDuration: 2.5, self.view.backgroundColor = .orange)

 


마지막!! 하지만 중요한!


함수와 클로저는 레퍼런스 타입입니다. 아래 코드를 보시죠,  addClosure2 와  addClosure 는 메모리의 같은 클로저를 참조합니다.

var addClosure:(Int,Int)->Int = { $0 + $1 }
let addClosure2 = addClosure

 

+ Recent posts