32YB SOPT/iOS 세미나

[SOPT] iOS_2주차_과제

신_이나 2023. 4. 21. 23:58

 

 

 

Closure's Capture List

 Closure 란 이제는 많이 들어봤어도 (이름 없는 함수 느낌) , Capture List 라는 것은 처음 들어본다. 먼저 Capture 란, 클로저를 변수에 할당하거나 클로저를 호출하는 순간, 자신이 참조하는 외부의 변수를 캡쳐한다. 예를 들어, 클로저에서 stored로 선언된 변수를 사용하고자 힙에 저장되는 클로저에 stored 변수 주소를 캡처하는 것을 의미한다고 할 수 있다. 이때 값 자체를 복사해서 사용할 수 있는 캡처리스트도 존재한다. 캡쳐리스트란, 주변 환경의 범위에서 참조한 변수들을 얼마나 강하게 캡쳐해야 하는지를 명시하는 것으로, 이를 통해 메모리 누수를 일으키는 강한 참조 순환을 피할 수 있다. 강한 참조 순환으로는 Closure 의 강한 참조 순환 문제가 있는데, 클로저가 클래스와 같은 참조타입일 때 발생한다. 이때, capture List 를 통해서 옵션을 지정하여, 참조 count를 증가,감소(감소도 결정할 수 있나?) 여부를 결정할 수 있다. 

 사용하는 방법으로는 클로저 내부의 매개변수 목록 이전에 사용해야 하고, 참조 방식과 참조할 대상을 ([])로 둘러싼 목록 형식으로 작성하며. 획득 목록 뒤에는 in 키워드를 작성한다. 빨리 예시로 보자!

 

// Capture
var stored = 0

let closure = { (number: Int) -> Int in
    stored += number
    return stored
}

closure(3) // 3

closure(4)   // 7

closure(5) // 12

stored = 0

closure(5)   //  5

// Capture List
class SimpleClass {
	var value: Int = 0
}

var x = SimpleClass()
var y = SimpleClass()

let closure = { [x] in
	print(x.value, y.value)
}
x.value = 10
y.value = 10

closure() // 10 10

 

 

ARC의 개념과 존재 이유

 ARC는 자동으로 메모리를 관리하며 객체에 대한 참조 카운트를 관리하고 0이 되면 자동으로 메모리가 해제시킬 수 있다. 구체적으로는 메모리 영역 중에 힙 영역을 관리하는데, 인스턴트 클로저 등등의 참조타입은 힙에 자동으로 할당되기에 이러한 힙에 메모리를 할당하는 것을 말한다. 따라서 꼭 메모리 해제를 해줘야 한다. 하지만 이때, 하나하나 수동으로 메모리 해제를 하기가 힘들기에 ARC를 통하여 자동으로 해제시켜 준다. 해제는 적절한 공간에 자동으로 retain, release 를 삽입하는 방식으로 진행되는데, 이때 retain 과 release 란,  retain 은 retain count 증가를 통해 현재 scope 에서 객체가 유지되는 것을 말하며, release 는 retain cout 를 감소시켜 retain 이후에 필요 없을 때 언급한다. count 를 증가시킨다는 말은 인스턴스의 주소값에 변수를 할당될 때를 의미한다. 기존 인스턴스를 다른 변수에 대입할 때나, 새로운 인스턴스를 생성할 때 말이다! count 를 감소시킨다는 말은 당연히 반대로 인스턴스를 가리키던 변수가 메모리에서 해제되었을 때와 nil이라고 강제로 설정하였을 때, 변수에 다른 값을 대입한 경우, 프로퍼티의 경우 속해 있는 클래스 인스턴스가 메모리에서 해제될 때를 의미한다. 이때 참조하는 방법으로는 strong, weak., unowoned 이 있고, weak 에 대해선 세미나 복습에서 다루었다. 

아래 접은 글을 본다면 더 구체적으로 이해할 수 있다!

더보기

weak 이란, 약한 참조를 말한다. 그렇다면 당연히 반대는 strong 강한 참조다.

weak 은 해당 인스턴스의 소유권을 가지지 않고 주소값만을 가지고 있는 포인터 개념이다. 따라서 인스턴스의 count를 증가시키지 않는다. 또한 release도 발생하지 않는다. 메모리가 해제될 경우엔 자동으로 nil로 초기화 된다. 두 개의 객체 변수의 메모리가 해제되었을 때 강한 참조 일때와 달리 약한 참조를 한 객체는 ARC에서 자동으로 객체의 메모리까지 해제를 시켜준다. 따라서 weak 속성을 사용하는 객체는 optional 타입이어야 하며 (nil일 수도 있기 때문), 값이 변할 수도 있기 때문에 var 로 받아야 한다.

delegate 에서 weak 을 쓰는 이유는, 메모리가 누수되는 문제를 막기 위해 사용된다. 만약에 메모리가 해제될 경우엔 자동으로 nil로 초기화되기 때문이다. 이는 retain cycle 을 피하기 위해서인데, retain cycle 란 메모리가 해제되지 않고 유지되어 메모리 누수가 발생하는 현상이다. class - instance - property 가 서로 연결되었을 때, instance 가 nil 이 된다면, property는 연결이 끊겨 쓸모없는 데이터가 된다. 

 

++그렇다면 weak 은 여기서 property 도 모두 nil 을 만들어 주는 걸까??????

파트장님 답변참고) var 자체가 strong 이기 때문에 사실상 strong 이라는 말을 따로 쓰지 않는다. 따라서 var만 선언한다면 모달을 내려도 (창(?)을 닫아도) 메모리가 계속 남아있다. 하지만 weak var 를 선언해주면 약한참조로 선언되는데, 이때는 모달을 내림과 동시에 메모리도 없어진다. 따라서 내가 질문한 모두 nil 을 만들어 주는 뭐 그런 것보단 ... weak 으로 선언된 것들의 메모리가 해제된다는 의미로 이해하면 될 것 같다. 지금은 간단한 데이터 전송이라 괜찮지만 나중가서 앱개발을 본격적으로 하게 되면 이 메모리 선언이 굉장히 중요하다!

 

- [SOPT] iOS_2주차_정규세미나 (데이터 전달 심화, SnapKit을 통한 AutoLayout, InjectIII, Then) https://ena-is.me/47

 

 

Delegate패턴과 클로저를 사용한 데이터 전달

 Delegation는 코드자체로 전환하는 것을 말한다. 따라서 대리자인 Delegation 을 선언하여 사용할 수 있다. 여기서 중요한 것이, 모든 일을 처리하는 것이 아니라 일부를 대리자에게 넘기는 것이다.(객체지향프로그램!) 기능을 위임할 수 있는 대리자, 즉 객체가 있다는 것은 직접 구현해야 하는 부분이 적기 때문에 효율적이다.

 

더보기

데이터 저장소 Ena Company가 있을 때, 여기서 데이터를 가지고 가고 싶은 소비자가 있다고 치자. 이때 성장한 Ena Company는 많은 소비자 각각에게 해당하는 데이터를 매번 가져다주기 어렵기 때문에 Protocol 이라는 안내서를 제공하여 소비자가 직접 자기 데이터를 가져가도록 가이드라인을 제공하는 것이다. 착한 소비자들은 가이드라인에 맞춰 내가 뭘 가져가고 싶은지 체크하고 답변하면서 Ena Company의 데이터를 거쳐가는 통로를 마련하는 것이다.

 

 Closure 란 코드 안에서 사용될 수도 있고, 매개 변수로 전달될 수도 있다. 따라서 클로저를 사용한다면 쓰기 간결해진다 (두둥,, 매우 중요한 장점이군) 프로토콜이나 함수의 사용 없이 지역 변수 Scope 내에서 처리가 가능하다. 전달하고자 하는 VC에 클로저(1)를 만들어 주고 이미 만들어 둔 클로저(0)에 전달할 데이터를 넣은 뒤, 똑같이 설정한 VC에 있는 클로저(1)를 호출에 데이터를 받아 텍스트 라벨을 설정하는 것이다.  B에 클로저 타입의 프로퍼티를 선언하뒤 B 위에서 선언해둔 클로저에 데이터를 넣는다. 그 뒤에 completionHandler 를 정의하여 데이터를 전달할 수 있다. 하지만 closure 를 사용하는 데이터 전달은 VC 사이에서 일어나는 데이터 전달일 때만 사용하는 것이 바람직하다.

var completionHandler: ((String) -> (String))?

@IBAction func touchButton(_ sender: Any) {
  _ = completionHandler?(self.textField.text ?? "")
  
  self.navigationControlelr?.popViewController(animated: true)
}

guard let vc = storyboard?.instantiateViewController(...)

vc.completionHandler = {
  text in
  self.myLabel.text = text
  return text
}

self.naviagtionController!.pushViewController(vc, animated: true)

 

 

 

Any, AnyObject

 

Any 는 함수타입을 포함하여 모든 타입의 인스턴스를 나타낼 수 있고, AnyObject는, 모든 클래스 타입의 인스턴스를 나타낼 수 있다. 또한 위 사진에서 말하는 바처럼, AnyObject 는 프로토콜이다. 따라서 아래 예시 2번은 AnyObject를 참조하고 있음이 나타난다.

//AnyObject

//Ex ) 1
let s: AnyObject = "This is a bridged string." as NSString
print(s is NSString)
// Prints "true"

let v: AnyObject = 100 as NSNumber
print(type(of: v))
// Prints "__NSCFNumber"

//Ex ) 2
protocol sendDataDelegate: AnyObject {
	func sendData(data: String)
}

 

 

inset, offset 

  layout의 좌표를 설정하는 방법에는 수동 방식인 Frame-Based Layout 과 자동으로 설정한 AutoLayout 으로 나눌 수 있다. inset 과 offset 은 그 중 AutoLayout 에 해당한다. offset은 element 와의 간격에 사용하며, inset은 superView와의 간격에 사용한다. offset의 경우 현재뷰의 constraint = 슈퍼뷰의 constraint +offset 값 으로 식을 작성하며 bottom 과 right 는 마이너스 부호를 갖게 된다. Ex) $0.top.left.equalToSuperview().offset(50) or $0.bottom.right.equalToSuperview().offset(-50) inset의 경우엔 좌표값이 양수 값이다. 따라서 Ex. $0.top.left.bottom.right.equalToSuperview() .inset(UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50)) 으로 잡아줄 수 있으며 모든 값이 동일 할 때는 $0.edges.equalToSuperview().inset(50) 으로 간단하게 표기 가능하다!

 

 

 

SafeArea

 SafeArea 는 말그대로 안전한 부분을 나타낸다. 예를 들어, 상태바, 내비게이션바. 탭바 등이 있다. 쉽게 말해, view 를 가리지 않기 위해 제공되던 시스템 마진을 말한다. top, bottom 뿐만 아니라 leading, trailing 에 대한 시스템 마진의 필요성도 생겨 났는데 여기서 safeArea 는 앞서 언급한 top, bottom, leading, trailing 의 시스템 마진을 모두 가진다.