View Controller의 생명주기(LIfe-Cycle)

|

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


iOS를 공부하면서 간단한 실습을 하다보니 view controller의 생명주기에대해 공부할 필요성을 느꼈다. 처음 접헀을때는 이게 쓸일이 있을까? 싶은 코드였는데, 꼭꼭 반드시 필요하고 제대로 알고 있어야하는 개념이었다. 실제 코드에 사용해보기도 했지만, 조금 헷갈리는 부분들이 있어 정리를 하게 되었다.

View Controller Life Cycle

앱들은 모두 View Controller 로 이루어져있다. 화면이 하나만으로 구성되는 앱도 있겠지만, 대부분의 앱은 여러개의 화면들로 구성되어있을 것이다. 이런 각각의 뷰들은 각각의 생명주기를 가지고 있고, 가져야 한다. 뷰 컨트롤러에게 있어서 생명주기란 보여지고 사라지는 주기 를 의미한다.

뷰 컨트롤러는 위 사진과 같은 주기를 가지게 된다. 기본적으로 우리가 이해하기에는 아래를 이해하면 된다.

viewDidLoad
viewWillAppear
viewDidAppear
viewwillDisappear
viewDidDisappear

그냥 보면 무슨 함수인지 모르겠지만, 함수의 이름을 잘 살펴보면 WillDid 가 보임을 알 수 있다. Will은 우리가 알고있듯 미래를 의미할 것이고, Did는 과거를 의미할 것이다. 하나씩 제대로 살펴보도록 하자!

loadView()

화면에 띄워줄 view를 만드는 메소드로 view를 만들고 메모리에 올린다.

실제로 해당 함수는 사용자가 직접 호출하여 사용하는 경우는 없다. 직접 코딩으로 만들어 사용하지 않는다면 이 함수를 override하여 사용하는 것은 좋지않다고 한다. 참고링크 바로가기

즉, 스토리보드나 nib(xib)를 사용하지 않는 경우에만 이 메소드를 직접 오버라이드 하여 뷰를 만들고 뷰 계층을 생성해준다.

viewDidLoad

뷰의 컨트롤러가 메모리에 로드된 후 호출되며 시스템에 의해 자동으로 호출된다.

익숙한 함수일 것이다. 프로젝트를 만들면 뷰 컨트롤러에 항상 꼭 보이는 함수이기 때문이다.

이 함수는 왜 항상 존재하는것이며 그래서 이 함수가 하는 일은 무엇일까?
애플 문서에 따르면

“Called after the controller’€™s view is loaded into memory”

뷰의 컨트롤러가 메모리에 로드되고 난 후 호출된다라고 의미한다. 우선 모든 뷰는 메모리에 올라가야 우리가 접근이 가능할 것이다. 그렇기 때문에 이 viewDidLoad 함수의 기능은 뷰의 로딩이 완료되었을때 시스템에 의해 자동으로 호출되어 일반적으로 리소스를 초기화하거나 초기화면을 구성하는 용도로 사용한다.

즉, 사용자에게 화면이 보여지기 전에 데이터를 뿌려주는 행위에 대한 코드를 작성하면 되고, 일반적으로 리소스를 초기화하거나 초기화면을 구성하는 용도로 자주 쓰인다. 이 메소드는 view controller 생에 단 한번만 호출이 되기 때문에 한번만 있을 행위에 대해서는 이 메소드 안에 정의해주면 된다. (화면이 처음 만들어질때 한번만 실행되기 때문에 처음 한번만 실행해야 하는 초기화 코드가 있을 경우 해당 메소드 내부에 작성하면 되는 것이다!)

viewWillAppear

뷰 컨트롤러의 화면이 올라오고 난 후 뷰가 화면에 나타나기 직전에 호출이 된다.

즉, 뷰가 로드된 이후 눈에 보이기 전에 컨트롤러에게 알리는 역할을 한다. 다른 뷰로 이동했다가 되돌아올때 재 호출되는 메소드로 화면이 나타날때마다 수행해야하는 작업을 정의하기 좋다. 처음 어플리케이션이 수행되고 첫 화면이 띄워질 때 호출되는 것은 viewDidLoad()와 동일하지만, 화면 전환을 통해 다시 현재의 화면으로 돌아올때는 viewDidLoad()가 아닌 viewWillAppear()가 호출된다.

즉, 뷰가 뷰 계층에 추가되기 직전에, 또 해당 뷰가 나타나기 위한 애니메이션이 설정되기 전에 호출된다. 이 메서드를 오버라이드해서 뷰가 화면에 나타나기 전에 필요한 추가적인 작업을 수행할 수도 있다.

viewDidAppear

view가 데이터와 함께 완전히 화면에 나타난 후 호출되는 메서드이다.

즉, 뷰가 나타났다는 것을 컨트롤러에 알리는 역할로 뷰가 화면에 나타난 직후에 실행되며 화면에 적용될 애니메이션을 그려준다.

viewwillDisappear

다음 view controller화면이 전환하기 전이나 view controller가 사라지기 직전에 호출되는 메서드이다.

뷰가 뷰 계층에서 제거되기 직전, 이 뷰가 사라지기 위한 애니메이션이 설정되기 전에 호출된다. 이 메서드를 오버라이드 함으로써 해당 뷰를 통해 일어난 변화는 저장하거나, 최초 반응자(first responder) 상태를 내려놓거나, 뷰가 나타났을 때 조정됐던 다른 뷰들을 원래대로 돌려놓는 등의 작업을 수행할 수 있다.

viewDidDisappear

view controller들이 화면에서 사라지고 나서 호출되는 메서드이다.

화면이 사라지고나서 필요없어지는(멈춰야하는) 작업들을 여기서 진행한다.


시스템 메모리가 부족한 상황이 되면, 시스템은 뷰 컨트롤러에 메모리가 모자라다는 메시지를 보낸다. 이것이 didReseuceMemoryWarnig()메서드로 이 메서드를 오버라이드 함으로써, 해제할 수 있는 메모리를 최대한으로 해제하여 메모리를 확보하는 작업을 수행해야한다.

이렇게 뷰 컨트롤러의 참조가 0이 되게 되면 deinit()을 통해 뷰컨트롤러가 가지고 있던 뷰ㅘ 관련 자원들을 해제함으로써 완전히 생명주기가 끝나게 된다.

Delegate를 더 쉽게 이해해보기

|

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


swift를 공부하면 꼭 알아야 하는 개념중에 하나가 delegate 개념이다.
공부를 하면서 delegate를 쓰긴 쓰는데, 정확하게 이해가 가지 않아 공부하던 찰나 좋은 글이 있어 보고 참고하여 정리해봅니다.

Delegate

delegate를 이해하기 위해서는 우선 프로토콜 개념을 이해해야한다. delegate가 프로토콜로 구현되기 때문이다.

이전에 정리했듯 프로토콜은 단순히 서로간의 약속 이라고 이해하면 쉽다. 구체적인 코드가 짜여져있는 것이 아니라 앞으로 어떤 코드를 짤것인지에 대해 정의해놓는 것이 바로 프로토콜이다. 현실에서의 예로 들자면 ‘선생님’이라는 프로토콜이 있다고 생각해보자. 선생님 프로토콜은 과목명, 담당 수업, 가르치는 행위, 숙제를 행위 등 여러가지의 특징과 기능을 가지고 있을 것이다. 각각의 선생님들은 각자가 가진 선생님 프로토콜을 준수해야한다. 중요한건 이 선생님 프로토콜 안에는 선생님으로서 해야할 행위에 대한 정의만 해놓았지 구체적인 행위의 구현을 하고 있지 않다는 것이다.

애플측에서는 모든 경우의 수를 생각해 구현해내기 힘드니 개발자는 함수 프로토타입을 가지고 자신이 원하는 방향으로 구현만 하면 된다. 즉, 선생님이라는 프로토콜을 채택한 곳에서 구체적인 코드를 구현해내면 되는 것이다. 즉, 프로토콜은 단순히 서로간의 지켜야할 규약 으로 이해하면 쉽다.

Delegate: [명사] 대표(자), 사절, 위임, 대리(자)
[동사] (권한, 업무 등을) 위임하다, (대표를) 선정하다

이전 정리한 글에서도 썼듯 delegate의 사전적의미는 위와 같다.

즉, 내가 해야할 일을 swift가 해서 준다! 정도로 이해하면 조금 쉬워질 것 같은데 swift에서 버튼이나 텍스트필드, 레이블 등 각각의 객체들은 고유의 특징을 가지고 있다. 버튼을 누르면 동작을 한다든지, 글자를 입력할 수 있도록 해주는 것과 같은 특징말이다. 이 delegate pattern은 쉽게 말해 객체 지향 프로그래밍에서 하나의 객체가 모든일을 처리하는 것이 아니라 처리해야할 일 중 일부를 다른 객체에 넘기는 것 을 뜻한다.

더 어렵게 느껴지는 것 같다.

아래는 간단한 UITextFieldDelegate 예제이다.
TextField안에 글자를 쓰고 버튼을 누르면 라벨에 사용자가 쓴 글이 옮겨진다.

  1. Main.Storyboard에 text field, button, label을 하나씩 만들어준다.
  2. 버튼에 대한 액션을 지정해준다.
class ViewController: UIViewController {
  @IBOutlet weak var enteredLabel: UILabel!
  @IBOutlet weak var textField: UITextField!

  @IBAction func buttonClicked(_ sender: Any) {
    enteredLabel.text = textField.text;
  }
}

위 액션의 의미는 말그래도 레이블의 텍스트를 내가 현재 쓴 textField 안의 값으로 써준다라는 의미이다.
textField에 원하는 글을 작성하고 버튼을 누르면 내가 작성한 글 그대로 레이블에 들어가게 될 것이다.

이제 delegate를 써보자. 가장 먼저 해야할 일은 채택작업 이다.

class ViewController: UIViewController, UITextFieldDelegate {
  @IBOutlet weak var enteredLabel: UILabel!
  @IBOutlet weak var textField: UITextField!
}
  1. ViewController에 UITextFieldDelegate를 채택함으로써 해당 프로토콜을 채택해준다.
  2. 이전에 만들었던 IBAction을 지우고 위임자가 누군지 선언해준다.
class ViewController: UIViewController, UITextFieldDelegate {
  @IBOutlet weak var enteredLabel: UILabel!
  @IBOutlet weak var textField: UITextField!

  override func viewDidLoad() {
    super.viewDidLoad()
    textField.delegate = self
  }
}

textField.delegate = self는 결국 윔지가 누군지 알려주는 과정이며 textField의 위임자는 self 즉, ViewController임을 의미한다. 즉 ViewController의 입장에서 TextField의 변화가 감지되면(이벤트가 발생하면) 해당 이벤트를 프로토콜에 따라 응답을 주겠다는 것을 뜻한다.

  1. 그리고 추가적으로 @IBAction를 대신할 함수를 만들어 준다.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  enteredLabel.text = textField.text
  return true
}

textFieldShouldReturn 함수는 UITextFieldDelegate안에 정의되어있는 함수이다. 해당 함수를 UITextFieldDelegate에서 불러와 하고싶은 일만 구현 하면 된다. 함수의 이름으로도 유추할 수 있듯 TextField에 사용자가 어떤 행위를 하고 그 값이 리턴될 것이다 라는 의미이다. 실제 실행해보면 같은 결과가 나타날 것이다.

Gesture Recognizer

|

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


Gesture Recognizer

제스처 인식기는 여러 제스처 관련 이벤트를 인식할 수 있다.
특정 제스처 이벤트가 일어날 때 마다 각 타깃에 맞는 액션 메시지를 보내어 제스처 관련 이벤트를 처리할 수 있다.

UIGestureRecognizer class

UIGestureRecognizer 클래스는 특정 제스처 인식기에 대한 동작을 정의하며 델리게이트 객체를 활용하여 일부 동작을 더욱 세밀하게 사용자화 할 수 있다.

UIGestureRecognizer의 하위 클래스

아래의 7가지의 UIGestureRecognizer 하위 클래스를 통해 여러 제스처를 인식할 수 있다.

  • UITapGestureRecognizer : 싱글탭 또는 멀티탭 제스처
  • UIPinchGestureRecognizer : 핀치(Pinch) 제스처
  • UIRotationGestureRecognizer : 회전 제스처
  • UISwipeGestureRecognizer : 스와이프(swipe) 제스처
  • UIPanGestureRecognizer : 드래그(drag) 제스처
  • UIScreenEdgePanGestureRecognizer : 화면 가장자리 드래그 제스처
  • UILongPressGestureRecognizer : 롱프레스(long-press) 제스처

제스처 인식기를 사용하기 위해서 타깃-액션 연결을 설정한 후 UIView의 메서드인 addGestureRecognizer(_:) 메서드를 통해 뷰에 연결한다. 제스처가 인식되면 해당 제스처 이벤트에 연결된 타깃에 액션 메시지가 전달되고 호출되는 액션메서드는 아래의 메서드 구현 형식 중 하나와 같아야 한다.

@IBAction func myActionMethod()
@IBAction func myActionMethod(_ sender: UIGestureRecognizer)

윈도우는 뷰에 터치 이벤트를 전달하기 전에 뷰에 추가된 제스처 인식기에 터치 이벤트를 전달한다. 제스처 인식기가 터치 이벤트를 인식했을 경우 뷰는 터치 이벤트를 받지 못하고, 제스처 인식기가 터치 이벤트를 인식하지 못했을 경우 터치 이벤트를 뷰가 받게 되며 일반적인 제스처 인식기의 동작의 흐름은 cancelsTouchesInView, delaysTouchesBegan, delaysTouchesEnded 프로퍼티의 값에 영향 을 받는다.

UIGestureRecognizer의 주요 메서드

  • init(target: Any?, action: Selector?) : 제스처 인식기를 타깃-액션의 연결을 통해 초기화
  • func location(in: UIView?) -> CGPoint : 제스처가 발생한 좌표를 반환
  • func addTarget(Any, action: Selector) : 제스처 인식기 객체에 타깃과 액션을 추가
  • func removeTarget(Any?, action: Selector?) : 제스처 인식기 객체로부터 타깃과 액션을 제거
  • func require(toFail: UIGestureRecognizer) : 여러 개의 제스처 인식기를 가지고 있을 때, 제스처 인식기 사이의 의존성을 설정

UIGestureRecognizer의 주요 프로퍼티

  • var state: UIGestureRecognizerState : 현재 제스처 인식기의 상태를 나타냄
  • var view: UIView? : 제스처 인식기가 연결된 뷰
  • var isEnabled: Bool : 제스처 인식기가 사용 가능한 상태인지를 나타냄
  • var cancelsTouchInView : 제스처가 인식되었을 때 터치 이벤트가 뷰로 전달되는 여부에 영향을 미침

이 프로퍼티가 true(기본값) 이고 제스처 인식기가 제스처를 인식했다면, 해당 제스처의 터치는 뷰로 전달되지 않는다. 이전에 전달된 터치들은 touchesCancelled(_:with:) 메시지를 통해 취소되고 제스처 인식기가 제스처를 인식 못하거나 이 프로퍼티의 값이 false 라면 뷰가 모든 터치를 전달받게 됩니다.

  • var delaysTouchesBegan : began 단계에서 제스처 인식기가 추가된 뷰에 터치의 전달 지연 여부를 결정
  • var delaysTouchesEnded : end 단계에서 제스처 인식기가 추가된 뷰에 터치의 전달 지연 여부를 결정

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBAction func tapView(_ sender: UITapGestureRecognizer) {
        self.view.endEditing(true)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapView(_:)))

        self.view.addGestureRecognizer(tapGesture)
    }
}

혹은


import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer()
        tapGesture.delegate = self
        self.view.addGestureRecognizer(tapGesture)
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        self.view.endEditing(true)
        return true
    }
}

Target-Action Pattern

|

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


Target-Action 디자인 패턴

Target-Action 디자인 패턴은 iOS 환경에서 많이 사용하는 디자인 패턴 중 하나로 Target-Action 디자인 패턴에서 객체는 이벤트가 발생할 때 다른 객체에 메시지를 보내는 데 필요한 정보를 포함 한다.

  • 액션은 특정 이벤트가 발생했을 때 호출할 메서드를 의미
  • 타겟은 액션이 호출될 객체를 의미

이벤트 발생 시 전송된 메시지를 액션 메시지라고 하고, 타겟은 프레임워크 객체를 포함한 모든 객체가 될 수 있으나, 보통 컨트롤러가 되는 경우가 일반적이다.

  1. 만약 특정 이벤트가 발생했을 때 abc라는 이름의 메서드를 호출해야 하는 상황이라고 생각해 보자.
  2. 이 abc라는 (액션)메서드는 A라는 클래스에도 정의되어 있고, B라는 클래스에도 정의되어 있는 경우가 있다.
  3. 이렇게 같은 메서드가 여러 클래스에 정의되어 있는 경우도 있고, 그런 클래스의 인스턴스가 여러개인 상황도 있다.
  4. 이런 여러 가지 상황에서 우리가 원하는 객체를 Target으로 지정하면 abc라는 액션을 실행할 객체를 상황에 따라서 선택할 수 있다.

액션 메서드

액션 메서드는 특정한 양식이 필요하다.

IBAction은 인터페이스 빌더가 메서드를 인지할 수 있도록 해준다.
스위프트 언어를 활용한 프로그래밍 방식에서 @objc는 Swift 클래스를 사용하는 Objective-C 코드가 있거나 Objective-C유형의 메서드를 사용하는 경우 필요하다.

// 프로그래밍 방식
@objc func doSomething(_ sender: Any) {

}
// 인터페이스 빌더
@IBAction func doSomething(_ sender: Any) {

}

아직까지 애플의 프레임워크는 Objective-C 언어로 작성된 코드가 많기 때문에 스위프트 언어로 작성한 코드에서는 Objective-C 코드와 호환하기 위해서 @objc라고 표시해주어야 한다. 스위프트 언어 4버전 이전의 컴파일러는 @objc를 자동으로 만들어 주었다. 하지만 이러한 방식은 자원 비용이 많이 들어 스위프트 4에서는 명시적으로 작성해야 한다.

컨트롤 이벤트

컨트롤 이벤트와 액션과의 관계

UIKit에는 UIButton, UISwitch, UIStepperUIControl을 상속받은 다양한 컨트롤 클래스가 있다. 그런 컨트롤 객체에 발생한 다양한 이벤트 종류를 특정 액션 메서드에 연결할 수 있는데 즉, 컨트롤 객체에서 특정 이벤트가 발생하면, 미리 지정해 둔 타겟의 액션을 호출하게 된다.

컨트롤 이벤트의 종류

컨트롤 이벤트는 UIControlEvents라는 타입으로 정의되어 있다.

  • touchDown: 컨트롤을 터치했을 때 발생하는 이벤트 > UIControlEvents.touchDown
  • touchDownRepeat: 컨트롤을 연속 터치 할 때 발생하는 이벤트 > UIControlEvents.touchDownRepeat
  • touchDragInside: 컨트롤 범위 내에서 터치한 영역을 드래그 할 때 발생하는 이벤트 > UIControlEvents.touchDragInside
  • touchDragOutside: 터치 영역이 컨트롤의 바깥쪽에서 드래그 할 때 발생하는 이벤트 > UIControlEvents.touchDragOutside
  • touchDragEnter: 터치 영역이 컨트롤의 일정 영역 바깥쪽으로 나갔다가 다시 들어왔을 때 발생하는 이벤트 > UIControlEvents.touchDragEnter
  • touchDragExit: 터치 영역이 컨트롤의 일정 영역 바깥쪽으로 나갔을 때 발생하는 이벤트 > UIControlEvents.touchDragExit
  • touchUpInside: 컨트롤 영역 안쪽에서 터치 후 뗐을때 발생하는 이벤트 > UIControlEvents.touchUpInside
  • touchUpOutside: 컨트롤 영역 안쪽에서 터치 후 컨트롤 밖에서 뗐을때 이벤트 > UIControlEvents.touchUpOutside
  • touchCancel: 터치를 취소하는 이벤트 (touchUp 이벤트가 발생되지 않음) > UIControlEvents.touchCancel
  • valueChanged: 터치를 드래그 및 다른 방법으로 조작하여 값이 변경되었을때 발생하는 이벤트 > UIControlEvents.valueChanged
  • primaryActionTriggered: 버튼이 눌릴때 발생하는 이벤트 (iOS보다는 tvOS에서 사용) > UIControlEvents.primaryActionTriggered
  • editingDidBegin: UITextField에서 편집이 시작될 때 호출되는 이벤트 > UIControlEvents.editingDidBegin
  • editingChanged: UITextField에서 값이 바뀔 때마다 호출되는 이벤트 > UIControlEvents.editingChanged
  • editingDidEnd: UITextField에서 외부객체와의 상호작용으로 인해 편집이 종료되었을 때 발생하는 이벤트 > UIControlEvents.editingDidEnd
  • editingDidEndOnExit: UITextField의 편집상태에서 키보드의 return 키를 터치했을 때 발생하는 이벤트 > UIControlEvents.editingDidEndOnExit
  • allTouchEvents: 모든 터치 이벤트 > UIControlEvents.allTouchEvents
  • allEditingEvents: UITextField에서 편집작업의 이벤트 > UIControlEvents.allEditingEvents
  • applicationReserved: 각각의 애플리케이션에서 프로그래머가 임의로 지정할 수 있는 이벤트 값의 범위 > UIControlEvents.applicationReserved
  • systemReserved: 프레임워크 내에서 사용하는 예약된 이벤트 값의 범위 > UIControlEvents.systemReserved
  • allEvents: 시스템 이벤트를 포함한 모든 이벤트 > UIControlEvents.allEvents

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var datePicker: UIDatePicker!
    @IBOutlet weak var dateLabel: UILabel!
    let dateFormatter: DateFormatter = {
        let formatter: DateFormatter = DateFormatter()
//        formatter.dateStyle = .medium
//        formatter.timeStyle = .medium
        formatter.dateFormat = "yyyy/MM/dd hh:mm:ss"
        return formatter
    }()

    @IBAction func didDatePickerValueChanged(_ sender: UIDatePicker) {
        print("value changed!")

        // 바꼇을때 date에 label을 표시(sender: 화면위에 올려놓은 datePicker)
        let date: Date = self.datePicker.date  // let date: Date = sender.date
        let dateString: String = self.dateFormatter.string(from: date)

        self.dateLabel.text = dateString
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        self.datePicker.addTarget(self, action: #selector(self.didDatePickerValueChanged(_:)), for: UIControl.Event.valueChanged)
    }
}

Poetry를 통한 파이썬 패키지 관리

|

이 글은 macOS 10.15.2(Catalina) 환경을 기준으로 쓰여졌습니다.
윈도우 및 리눅스 환경에서는 사용법이 다를 수 있습니다.


파이썬의 기본 패키지 관리자인 pip는 패키지간 의존성 및 버전 호환 관리가 힘들며, 의존성 패키지를 제외한 실제 필요 패키지의 구분이 어렵다. 이를 해결하기 위해 파이썬 패키지를 관리할 수 있도록 도와주는 pipenv, pipx와 같은 여러 라이브러리들이 있지만, 항상 약간씩 기능에 아쉬움을 느껴 지금까지는 직접 requirements.txt를 버전별로 관리하는 방법을 사용했다.

그러던 중 최근에 Poetry라는 관리도구를 사용해보았는데, 다른 도구들에서 불편하던 점들이 많이 개선되어 사용법을 남겨본다.

Poetry가 자동으로 제공하는 가상환경을 사용하지 않고, Pyenv와 Pyenv-virtualenv로 직접 생성한 가상환경을 사용합니다.

Poetry 설치

현재 brew를 통한 설치가 가능한 것 같지만, brew를 통해 설치해보니 약간의 아쉬움이 있었다. (brew에서 제공한지 얼마 안되어 그런것 같다)
그러니 우선 스크립트를 통한 설치를 진행해보자

스크립트를 통한 설치

Poetry는 시스템 전역에 설치한다. pip를 사용한 설치는 권장되지 않으며, 시스템 전역에서 사용 시 몇 가지 필요 설정이 있다.

curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python

(Pyenv 사용시) PATH설정

Pyenv를 사용하는 경우, Pyenv-virtualenv로 생성한 가상환경 중 단 하나에라도 존재하는 실행파일은 $HOME/.pyenv/shims/에서 찾게된다.
brew를 사용해 설치한 패키지나, 그 외에도 Pyenv로 관리되는 가상환경보다 우선하게 실행하고자 하는 명령어는 적절히 PATH의 순서를 조정해야 한다.

따라서zsh을 쓴다면 ~/.zshrc에 내용을 추가하며, bash나 다른 셸을 사용하는 경우, 해당 셸의 설정 파일을 수정해야 한다.

# ~/.zshrc
# pyenv의 PATH
export PYENV_PATH=$HOME/.pyenv
if which pyenv > /dev/null; then eval "$(pyenv init -)"; fi
if which pyenv-virtualenv-init > /dev/null; then eval "$(pyenv virtualenv-init -)"; fi

# poetry실행파일의 PATH가 pyenv의 PATH보다 우선되도록 설정
export PATH=$HOME/.poetry/bin:$PATH

사용법

프로젝트의 패키지를 Poetry가 관리하도록 초기화

npm init과 비슷하게, Poetry로 해당 폴더의 패키지 관리를 시작하는 명령어를 사용

# pyenv, pyenv-virtualenv를 사용하는 경우, 아래 명령어로 가상환경 생성&적용 후 실행
# pyenv virtualenv <버전> <env명>
# pyenv local <env명>

# 가상환경이 적용되지 않은 상태에서 초기화 시, Poetry가 임의의 가상환경을 생성
poetry init

이 명령어는 pyproject.toml 파일을 만들어준다.

패키지 추가

버전을 지정해주면 나머지는 그 버전에 맞게 호환될수 있는 버전으로 추가/설치된다.

poetry add <패키지명>

패키지 및 의존성패키지를 함께 삭제

poetry remove <패키지명>

설치된 패키지 목록 확인

내가 관리하는 패키지들과, 해당 패키지들에 의존성으로 설치된 패키지들을 분리해서 볼 수 있다.

poetry show --no-dev --tree
# 아래와 같이 설치된 패키지와 의존성 패키지 정보를 표시
boto3 1.11.9 The AWS SDK for Python
├── botocore >=1.14.9,<1.15.0
│   ├── docutils >=0.10,<0.16
│   ├── jmespath >=0.7.1,<1.0.0
│   ├── python-dateutil >=2.1,<3.0.0
│   │   └── six >=1.5
│   └── urllib3 >=1.20,<1.26
├── jmespath >=0.7.1,<1.0.0
└── s3transfer >=0.3.0,<0.4.0
    └── botocore >=1.12.36,<2.0.0
        ├── docutils >=0.10,<0.16
        ├── jmespath >=0.7.1,<1.0.0
        ├── python-dateutil >=2.1,<3.0.0
        │   └── six >=1.5
        └── urllib3 >=1.20,<1.26
django 2.2.9 A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
├── pytz *
└── sqlparse *
django-extensions 2.2.6 Extensions for Django
└── six >=1.2
django-secrets-manager 0.1.7 Django SecretsManager is custom secret managers for Django
├── boto3 *
│   ├── botocore >=1.14.9,<1.15.0
│   │   ├── docutils >=0.10,<0.16
│   │   ├── jmespath >=0.7.1,<1.0.0
│   │   ├── python-dateutil >=2.1,<3.0.0
│   │   │   └── six >=1.5
│   │   └── urllib3 >=1.20,<1.26
│   ├── jmespath >=0.7.1,<1.0.0 (circular dependency aborted here)
│   └── s3transfer >=0.3.0,<0.4.0
│       └── botocore >=1.12.36,<2.0.0 (circular dependency aborted here)
└── django *
    ├── pytz *
    └── sqlparse *
django-storages 1.8 Support for many storage backends in Django
└── django >=1.11
    ├── pytz *
    └── sqlparse *
gunicorn 20.0.4 WSGI HTTP Server for UNIX
└── setuptools >=3.0
pillow 7.0.0 Python Imaging Library (Fork)
requests 2.22.0 Python HTTP for Humans.
├── certifi >=2017.4.17
├── chardet >=3.0.2,<3.1.0
├── idna >=2.5,<2.9
└── urllib3 >=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26

poetry.lock파일로부터 requirements.txt생성

Poetry패키지 자체는 필요하지 않은 작업(ex: Docker Image생성)시에는 현재 설치된 패키지에 대한 requirements만 필요하다. 이 때, export명령어를 사용

poetry export -f requirements.txt > requirements.txt
# 아래와 같은 requirements.txt생성
amqp==2.5.2 \
    --hash=sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8 \
    --hash=sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d
billiard==3.6.1.0 \
    --hash=sha256:01afcb4e7c4fd6480940cfbd4d9edc19d7a7509d6ada533984d0d0f49901ec82 \
    --hash=sha256:b8809c74f648dfe69b973c8e660bcec00603758c9db8ba89d7719f88d5f01f26
boto3==1.10.14 \
    --hash=sha256:cb8f2531c22a4cf7847f62a9e23c2611300b6fdbcad577f67a9b56b686f78dd5 \
    --hash=sha256:aa40df7958bb274fad45ce6cc9ae1c77aac3b4cf33c34684646b42583a52d7e0
botocore==1.13.14 \
    --hash=sha256:48ad5efd98e0570df13a73e6463da2a9d9422f47a943b6ef74f950695c23dbb0 \
    --hash=sha256:d789f30a5def264b9d21a917aeadc4e5fc2b6a03a61f222befcfaf80eaba86e5
celery==4.3.0 \
    --hash=sha256:528e56767ae7e43a16cfef24ee1062491f5754368d38fcfffa861cdb9ef219be \
    --hash=sha256:4c4532aa683f170f40bd76f928b70bc06ff171a959e06e71bf35f2f9d6031ef9
django==2.2.7 \
    --hash=sha256:89c2007ca4fa5b351a51a279eccff298520783b713bf28efb89dfb81c80ea49b \
    --hash=sha256:16040e1288c6c9f68c6da2fe75ebde83c0a158f6f5d54f4c5177b0c1478c5b86