반응형
반응형

 

시작하기 전

 이제야 개발 경력이 1년이 다되어 가는 주니어 중 주니어 iOS 개발자이다. 개발 블로그를 시작한 이유도 매일매일 공부하는 흔적을 남기고 공백기가 생긴다면 그때의 나에게 조금이라도 후회를 남기기 위해 시작하였다. 하지만 생각보다 기술적인 문제보다 무언가를 글로써 정리하는 게 너무 어려웠다. 매일 조금씩 조금씩 여러 가지 방향으로 개발 공부를 어떻게 하면 효율적으로 꾸준하게 할 수 있을지 고민이었다. 또한 SwiftUI로만 개발을 해왔고 UIKit를 사용해본적이 거의 없으며 UIKit로 구현되어있는 라이브러리를 사용할 때나 꼭 필요한 경우에만 해당 기능을 그때그때 확인하고 사용해본 게 전부였다. 하지만 iOS 기본적인 lifecycle이나 기초 배경지식의 필요성을 심각하게 느끼고 있어 UIKit에 대한 공부를 해야겠다는 마음을 먹었다. 그러다 우연히 빗썸으로부터 4년 차 이하의 주니어 iOS 개발자를 대상으로 진행되는 교육 프로그램을 알게 되어 지원하게 되었다. 캠프에 지원을 하고 과제를 받았을 때 SwiftUI를 사용하면 안 되고 UIKit로만 구현해야 한다는 제한사항을 보고 걱정이 조금 있었다. 하지만 어차피 UIKit를 공부하려고 했었기에 UIKit 기반(StoryBoard 기반) 공부를 병행하며 제출 과제 프로젝트를 진행하였다. 그리고 운이 좋게 해당 캠프를 들을 수 있는 기회가 생겼다.

 

세션

 현업자를 대상으로 하는 프로그램이기 때문에 세션의 시작 시간이 19시 30분부터 22시 30분까지였다. 야곰 아카데미에서 6개월짜리의 내용들을 4주 만에 끝내는 커리큘럼이기 때문에 가벼운 주제는 빠르게 핵심적인 주제로 세션이 진행되었다. 대부분 어떠한 활동학습이 주어지며 매일 다른 캠퍼 분과 해당 활동학습을 진행하는 방식이었다. 세션의 내용은 내가 혼자서 공부를 한다면 언제까지도 모르고 지나갔을 법한 내용들이 많았고 지난 시간에 대충 훑어만 보고 넘어간 내용들에 대해 개념이 확립하는 시간이 되었다.

 

협업

 해당 교육 프로그램을 들으면서 세션을 듣는 시간 이외에 따로 정해진 캠퍼분들과 프로젝트를 진행하게 된다. 1주차에서는 UIKit 기반의 작은 프로젝트였고, 2주 차는 팀원과 협의를 통해 프로젝트의 기술 스택을 자유롭게 정할 수 있었는데 SwiftUI와 TCA 기반의 프로젝트로 진행하였다.

 나는 이번에 운이 정말 좋게 너무나도 배울점이 많은 분들과 프로젝트를 진행하게 되었다. 데이터 구조부터 첫 화면까지는 페어 프로그래밍으로 다 같이 하나의 화면을 보며 여러 가지 의견을 내며 진행을 하였는데 다른 두 캠퍼분들의 모습을 보고 지금까지 나에게 너무나도 반성을 하게 되는 계기가 되었다. 

 

멘토링

 매주 수요일과 토요일에 프로젝트에 대한 멘토링이 진행되었었는데 1주차에서는 정말 사소한 부분까지 체크를 해주셨다. 사소할수록 자주 작성되는 코드들에 대해 미처 고려하지 못했던 부분들을 집어주시면서 내가 놓치고 있었던 부분들을 알게 되었다. 2주 차부터는 새로운 프로젝트를 진행하는 데 있어서 멘토링 방식이 사소한 코드 리뷰보다는 전체적인 흐름에 대한 멘토링이 진행되었다. 프로젝트를 진행하는 데 있어서 방향성에 대한 의견이나 어떠한 문제에 직면했을 때 손쉽게 프로젝트를 진행할 수 있도록 해결방안도 제시해주셔서 막힘없는 프로젝트를 진행할 수 있었다.

 

무엇을 배우고 느꼈는가?

 평일 매일 진행되는 세션을 통해 iOS개발자로써 꼭 알아야 하는 기초지식과 개념이 정리가 되었고 현재 내가 어떤 부분의 개념이 부족한지 깨닫는 시간이 되었다. 항상 개발하면서 고민하고 시간을 많이 끌었던 적이 데이터 구조였다. 많은 예제 프로젝트들을 보면서 공부를 꾸준하게 해왔지만 이번 협업을 통해 어떠한 이유로 어떤 구조와 설계를 하게 되는지 여러 한 의견을 내고 들으며 많은 것을 배우게 되었다. 너무나도 오래 묵혀있던 게 풀린 게 너무 시원해서 팀원분들께 감사했다. 실제 업무를 SwiftUI를 사용하고 있기 때문에 TCA에 대하여 흥미를 가졌던 적이 있었지만 아직까지 SwiftUI에 대한 레퍼런스도 많이 없을뿐더러 TCA는 더욱 없었기 때문에 그 당시 대략적인 부분만 이해하고 넘어갔던 적이 있었는데 이번 프로젝트를 진행하면서 멘토 분과 팀원분들 덕분에 많은 문제들을 해결하며 많은 공부가 되었다.

 나는 이번 캠프를 진행하면서 세션, 멘토분 그리고 같이 프로젝트를 진행하였던 캠퍼분들을 통해 내가 가장 크게 배우고 느낀 것은 아무래도 나의 개발 공부에 대한 방향성이다. 내가 현재 부족한게 무엇인지 확실하게 깨달았고 앞으로 나에게 가장 필요한 공부가 무엇인지에 대해 우선순위가 정해졌다. 또한 공부를 할 때 어떻게 접근하는지, 어디까지 깊게 공부를 해야 하는지에 대한 개념이 잡혔다. 만약 이번 캠프를 참가하지 못했다면 나의 개발 인생에 많은 시간이 낭비되었을 것 같다는 생각이 든다. 4주 동안 회사를 다니며 주말도 쉬지 않고 세션, 프로젝트를 진행하면서 일정이 빡빡했지만 너무나도 보람 있고 알찬 시간이었다.

반응형

SwiftUI에서 UIkit 사용하기


SwiftUI에서 UIkit를 사용하기 위해서는 UIViewRepresentable을 채택하는 struct를 구현하면 됩니다.

UIViewRepresentable을 채택하게 되면 필수로 구현해야 하는 makeUIView와 updateUIView가 있고 UIViewType을 원하는 UIkit의 View로 변경하면 됩니다.

UIViewRepresentable

// 구현
struct SwiftUIView: UIViewRepresentable {
	func makeUIView(context: Context) -> UIViewType {
    	let view = UIViewType()
        return view
    }
    
    func updateUIView(_ view: UIViewType, context: Context) {
    
    }
}


// 사용
struct MyView: View {
	var body: some View {
    	VStack { 
        	SwiftUIView()
        }
    }
}

UIViewControllerRepresentable

만약 Custom View가 아닌 하나의 Controller일때는 UIViewControllerRepresentable이라는 것을 따로 제공합니다. 

makeUIViewController에 해당하는 ViewController를 리턴하도록 해주면 됩니다.

struct MyViewControllerRepresentation: UIViewControllerRepresentable { 
	func makeUIViewController(context: Context) -> ViewController {
		
    }

	func updateUIViewController(_ uiViewController: ViewController, context: Context) { 
    
    } 
}

 

 

SwiftUI에서 Storyboard 사용하기


코드로 구현된 UIViewController가 아닌 Storyboard(.xib)로 구현되어있다면 어떻게 해야할까요?

UIStoryboard

struct MyViewControllerRepresentation: UIViewControllerRepresentable { 
	func makeUIViewController(context: Context) -> ViewController {
		UIStoryboard(name: "MyStoryBoardView", bundle: nil)
        	.instantiateViewController(withIdentifier: "ViewController") as! ViewController
    }

	func updateUIViewController(_ uiViewController: ViewController, context: Context) { 
    
    } 
}



반응형

Publisher(게시자)

  • 요청 시 데이터를 제공해줌
  • 구독이 없는 경우 Publisher는 데이터를 제공하지 않음
  • 두 가지 유형을 제공함(Outpt Type, Failure Type)

publisher

Subscriber(구독자)

  • 구독자는 데이터를 요청하고 Publisher가 제공한 데이터(및 오류)를 처리해야 함
  • 입력에 대한 유형과 실패에 대한 유형 두 가지 연관된 유형
  • 구독자는 데이터를 요청을 시작하고 수신하는 데이터의 양을 제어함
  • Subscriber가 없으면 게시가 아예 되지 않기 때문에 Subscriber는 작업을 주도하는 것으로 생각할 수 있습니다.
    subscriber

Operator(연산자)

  • 연산자는 Publisher Protocol과 Subscriber Protocol을 모두 채택하는 클래스
  • 게시자 구독 및 모든 구독자에게 결과 보내기를 지원
  • 게시자가 데이터를 제공하고 구독자가 요청한 데이터를 처리, 반응 및 변환하기 위해 체인을 만들 수 있음
    operator

연산자는 값 또는 유형을 변환하는데 사용되며 스트림을 분할 또는 복제하거나 스트림을 병합할 수도 있습니다. 그리고 항상 (출력/실패) 유형의 조합으로 정렬되어야 합니다. 만약 유형이 올바르지 않으면 컴파일러에서 오류가 발생합니다!


아래는 간단한 Combine 파이프라인 입니다.

let _ = Just(5)                     // 1
    .map { value -> String in     // 2
        return "a string"
    }
    .sink { receivedValue in      // 3
        print("The end result was \(receivedValue)")
    }

1) Just 파이프라인은 정의되어있는 값으로 응답하는 게시자입니다. 이미 정의되어있는 값이기 때문에 실패 유형이 없죠? 그래서 Error 유형은 NEVER가 됩니다.
2) map을 통해 기존의 값을 기반으로 한 새로운 유형의 값을 반환할 수 있습니다. 예제에서는 기존 값을 무시하고 새로운 String 값을 반환합니다.
3) sink 구독자로 인해 파이프라인이 종료됩니다.

파이프라인은 출력 유형과 실패 유형 두 가지에 의해 연결된 일련의 작업입니다. 파이프라인을 생성할 때 최종 목표를 달성하기 위해 데이터, 유형(스트림의 성공 여부 등)을 변환하는 데 연산자를 사용하게 됩니다. 여기서 말하는 최종 목표는 대부분 사용자 인터페이스 요소를 변화시키는 작업이겠죠? 그렇기에 이러한 변환을 돕기 위한 게 바로 Combine의 연산자입니다.

Combine의 대표 연산자 tryMap

Combine의 연산자를 보다 보면 try###라는 이름의 연산자들이 있습니다. 모두 기존의 함수와 비슷한 동작을 하지만 실패 유형을 가지고 있어 실패 유형에 대한 처리를 도와줍니다. map은 위의 예제에서처럼 String만을 반환하지만 tryMap의 경우 유형을 제공하는 게시자를 <String, Never>을 사용하는 구독자로 끝납니다. 간단하게 throw를 해야 한다면 try가 붙은 연산자를 사용하면 됩니다.

let _ = Just(5) 
    .map { value -> String in 
        switch value {
        case _ where value < 1:
            return "none"
        case _ where value == 1:
            return "one"
        case _ where value == 2:
            return "couple"
        case _ where value == 3:
            return "few"
        case _ where value > 8:
            return "many"
        default:
            return "some"
        }
    }
    .sink { receivedValue in 
        print("The end result was \(receivedValue)")
    }
  • Just<Int, Never> 유형의 조합을 만들었고 정의된 단일 값을 제공합니다.
  • .map() 함수를 통해 Int를 받아 String으로 반환합니다. 실패 유형인 Never는 변경되지 않으므로 통과됩니다.
  • sink를 통해 <String, Never> 조합을 수신합니다.
반응형

+ Recent posts