야곰닷넷 질문모음 – 9

1 답변 글타래를 보이고 있습니다
  • 글쓴이
    • TTOzzi
      참가자
      • 글작성 : 10
      • 답글작성 : 13

      iOS 커뮤니티들의 기존 질문, 답변에 제가 찾은 정보와 찾으면서 참고했던 비슷한 질문들을 같이 정리해보았습니다.
      잘못된 정보 수정이나 추가하고 싶은 답변이 있으시다면 댓글을 달아주시거나 Question-Archive 에 issue 나 PR 로 알려주시면 수정하겠습니다!

      Q.

      @objc 의 의미가 무엇인가요?

      @objc 키워드의 의미가 무엇인가요?

      질문 바로가기

      A.

      • @objc 는 Swift 로 작성된 코드에서 Objective-C 의 API 와의 호환을 위해 사용합니다. target-action 패턴(Selector)을 활용하는 Timer, NotificationCenter 나, keyPath 를 활용하는 KVO, KVC 와 같은 Objc 기반 API 는 Objc 런타임을 통해 제공됩니다. Swift 4 이전엔 컴파일러가 Objc 런타임에 노출할 프로퍼티와 메소드를 자동으로 추론해줬지만, 추론하는 시기의 불분명함, 같은 이름(오버로딩)으로 인한 의도치 않은 충돌, 바이너리 크기 증가와 성능 저하 등의 이유(SE-0160: Limiting @objc inferece)로 Swift 4 부터는 @objc 를 명시하여 Objc 런타임에 노출할 프로퍼티와 메소드를 컴파일러에게 알려줘야 합니다.
      • target-action 패턴의 #selector

        class ViewController: UIViewController {
          @IBOutlet weak var button: UIButton!
        
          override func viewDidLoad() {
              super.viewDidLoad()
              button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)
          }
        
          @objc func tappedButton(_ sender: UIButton?) {
              print("tapped button")
          }
        }
        

        selector 에 전달하는 함수가 @objc 로 선언되어있지 않으면 컴파일 에러가 발생합니다.

      • keyPath

        class Person: NSObject {
          @objc var name: String
          @objc var friends: [Person] = []
          @objc var bestFriend: Person? = nil
        
          init(name: String) {
              self.name = name
          }
        }
        
        let gabrielle = Person(name: "Gabrielle")
        let jim = Person(name: "Jim")
        let yuanyuan = Person(name: "Yuanyuan")
        gabrielle.friends = [jim, yuanyuan]
        gabrielle.bestFriend = yuanyuan
        
        #keyPath(Person.name)
        // "name"
        gabrielle.value(forKey: #keyPath(Person.name))
        // "Gabrielle"
        #keyPath(Person.bestFriend.name)
        // "bestFriend.name"
        gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
        // "Yuanyuan"
        #keyPath(Person.friends.name)
        // "friends.name"
        gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
        // ["Yuanyuan", "Jim"]
        

        keyPath 로 접근하는 프로퍼티를 @objc 로 선언해주었습니다.

        @objcMembers class Person: NSObject {
          var name: String
          var friends: [Person] = []
          var bestFriend: Person? = nil
        
          init(name: String) {
              self.name = name
          }
        }
        

        클래스 전체를 Objc 런타임에 노출되어야 할 때 클래스를 @objcMembers 로 선언하여 내부의 모든 프로퍼티와 메소드를 @objc 로 선언한 것과 같은 효과를 줄 수도 있습니다. 하지만 클래스 전체를 포함하다 보니 Objc 런타임에 필요하지 않은 프로퍼티나 메소드까지 포함할 수 있어 바이너리 크기가 증가하고 성능이 저하될 수 있습니다. Objc 런타임의 기능을 많이 사용하는 라이브러리 등을 제외한 대부분 코드에서는 @objc 를 사용하는 것을 권장합니다.

      • @IBAction, @IBOutlet 등의 Interface Builder 와 관련된 접두사들이나 @NSManaged, @GKInspectable 등의 접두사들은 내부적으로 @objc 로 구현되어 있어 따로 @objc 를 명시해주지 않아도 관련 기능들을 사용할 수 있습니다.

        class ViewController: UIViewController {
          @IBOutlet weak var button: UIButton!
        
          override func viewDidLoad() {
              super.viewDidLoad()
              button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)
          }
        
          @IBAction func tappedButton(_ sender: UIButton?) {
              print("tapped button")
          }
        }
        

        @IBAction 으로 선언된 메소드도 #selector 에 전달할 수 있는것을 확인할 수 있습니다.

      참고할 만한 비슷한 질문, 자료


      Q.

      자기 자신의 프로퍼티나 메소드에 접근할 때 항상 self 를 붙여줘야 하나요?

      클래스, 구조체, 열거형의 내부에서 자기 자신의 프로퍼티나 메소드에 접근할 때 self 를 명시해줄 수도, 생략할 수도 있는데요. self 를 계속 명시해주는 것과 self 를 꼭 필요할 때만 쓰는 것 중 어떤 방법이 더 나은가요?

      질문 바로가기

      A.

      • self 를 항상 명시한다면, 생략하는 것보다 명확하게 자기 자신의 프로퍼티나 메소드를 사용한다는 의도를 표현할 수 있습니다. 반대로, self 를 필요할 때만 명시하는 경우 코드가 간결해지고, 컴파일러에서 클로저 내부에서의 self 접근에 self 의 명시를 강제하기 때문에, 클로저 캡처로 인한 순환 참조가 발생할 가능성이 있는 부분이 눈에 띈다는 장점이 있습니다.
      • 다음은 깃헙에서 star 가 많은 순서대로 나열한 회사들의 스타일 가이드입니다.

        이 중 raywenderlich, github, linkedin, airbnb 에서는 컴파일러에서 필요로 할 때만 self 를 명시하고 eure, StyleShare 에서는 self 를 항상 명시한다고 합니다.

      • 자료를 찾아보면서 self 를 명시해야 하느냐에 대한 논쟁이 굉장히 많았습니다. 실제로 (거절당했지만)SE-0009: Require self for accessig istance members 와 같은 제안도 있었습니다. self 명시는 정해진 정답이 없습니다. 팀에 속해있다면 팀 내에서 컨벤션을 정하여 맞추면 되고, 혼자라면 스스로 기준을 두고 결정하여 통일성만 유지한다면 어떤 방법으로 작성하든 상관없다고 생각합니다.

      • Swift 5.3 부터는 클로저 내부에서 [self], [unowned self] 와 같이 캡처 의도를 명확하게 선언하거나([weak self] 제외), 순환 참조가 발생하지 않는 값 유형의 캡처에 대해선 self 의 생략이 가능합니다. 순환 참조가 발생할 가능성이 있는 경우에만 self 를 붙여야 한다는 컴파일 에러가 발생합니다. SE-0269: Increase availability of implicit self in @escaping closures when reference cycles are unlikely to occur 를 참고하세요. 개인적인 생각이지만, Swift 의 업데이트 방향을 봤을 때 애플에서는 self 를 생략하는 것을 권장하는 것 같습니다.

      참고할 만한 비슷한 질문, 자료


      Q.

      코드로 오토레이아웃을 설정하고 싶어요.

      코드로 오토레이아웃을 어떻게 설정하나요?

      질문 바로가기

      A.

      • 코드로 오토레이아웃을 설정하는 방법에는 여러 가지가 있습니다. 아래 사진처럼 현재 화면에 보이는 뷰 중앙에 가로, 세로 길이가 100인 뷰를 추가하는 코드를 예시로 들어보겠습니다. 다음 예시들은 모두 같은 레이아웃을 설정하는 코드입니다.

        스크린샷 2020-09-13 오후 11 42 09

      • 코드로 설정하는 레이아웃은 크게 제약조건을 생성하는 부분과 제약조건을 활성화하는 부분으로 나뉩니다. 제약조건을 먼저 생성하고 생성한 제약조건을 활성화하는 방식으로 레이아웃을 설정합니다.

        override func viewDidLoad() {
              super.viewDidLoad()
              let squareView = UIView()
              squareView.backgroundColor = .systemIndigo
              view.addSubview(squareView)
        
              // autoResizingMask 를 제약조건으로 변환하지 않겠다는 것을 명시
              squareView.translatesAutoresizingMaskIntoConstraints = false
        
              // 레이아웃 제약조건 생성 코드 작성
              ...
        
              // 레이아웃 제약조건 활성화 코드 작성
              ...
          }
        

        코드로 오토레이아웃을 설정해 줄 땐 반드시 설정해줄 뷰의 translatesAutoresizingMaskIntoConstraints 를 false 로 설정해주어야 합니다. 기본적으로 코드로 생성한 뷰는 true 를 기본값으로 가지며 인터페이스 빌더에 뷰를 추가하면 자동으로 false 로 설정되지만, 우리는 코드로 오토레이아웃을 설정해줄 것이기 때문에 직접 false 로 설정해주어야 합니다.

      • 제약조건 생성

        let horizontalConstraint = NSLayoutConstraint(item: squareView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
        let verticalConstraint = NSLayoutConstraint(item: squareView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
        let widthConstraint = NSLayoutConstraint(item: squareView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100)
        let heightConstraint = NSLayoutConstraint(item: squareView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100)
        

        NSLayoutConstraint 의 이니셜라이저는 제약조건 방정식을 그대로 코드로 표현한 형태입니다. 각각의 인자를 식으로 변환하면 view1.attr1 <relation> multiplier × view2.attr2 + c 와 같습니다.

        let horizontalConstraint = squareView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        let verticalConstraint = squareView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        let widthConstraint = squareView.widthAnchor.constraint(equalToConstant: 100)
        let heightConstraint = squareView.heightAnchor.constraint(equalToConstant: 100)
        

        NSLayoutConstraint 보다 가독성이 좋고 코드가 간결합니다. 공식문서에서는 코드로 오토레이아웃을 설정할 때 NSLayoutConstraint 의 이니셜라이저보단 NSLayoutAnchor 를 권장합니다. 이 둘의 가장 큰 차이점은 잘못된 제약조건을 설정하였을 때 나타납니다.

        let horizontalConstraint = NSLayoutConstraint(item: squareView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
        

        squareView 의 centerX 를 view 의 centerY 에 맞추라는 제약조건입니다. NSLayoutConstraint 를 사용했을 땐 위와 같이 잘못된 제약조건을 설정하면 런타임에 예외를 발생시킵니다.

        // Cannot convert value of type 'NSLayoutAnchor<NSLayoutYAxisAnchor>' to expected argument type 'NSLayoutAnchor<NSLayoutXAxisAnchor>' 컴파일 에러 발생
        let horizontalConstraint = squareView.centerXAnchor.constraint(equalTo: view.centerYAnchor)
        

        반면에, NSLayoutAnchor 를 사용했을 땐 제약조건의 대상이 되는 NSLayoutAnchor 의 타입체크가 이루어져 컴파일 에러를 발생시켜 개발자에게 잘못된 제약조건을 설정한 것을 인지시켜줍니다(하지만 완벽하게 방지해주진 않습니다. 런타임에 충돌이 발생할 수 있습니다.).

      • 제약조건 활성화

        view.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
        

        공식문서에서는 iOS 8 이상의 환경에선 addCostraints(_:) 를 직접 호출하는 것보다 isActive 를 활용하라고 합니다.

        horizontalConstraint.isActive = true
        verticalConstraint.isActive = true
        widthConstraint.isActive = true
        heightConstraint.isActive = true
        

        Bool 값을 할당하여 제약조건을 활성화, 비활성화할 수 있습니다.

        NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
        

        한 번의 호출로 여러 개의 제약조건을 활성화할 수 있습니다. 각 제약조건의 isActive 를 true 로 설정하는 것과 같습니다.

      • Visual format 언어를 활용한 방법(constraints(withVisualFormat:options:metrics:views:))

        class ViewController: UIViewController {
        
          override func viewDidLoad() {
              super.viewDidLoad()
              let squareView = UIView()
              squareView.backgroundColor = .systemIndigo
              view.addSubview(squareView)
        
              squareView.translatesAutoresizingMaskIntoConstraints = false
              // 레이아웃 제약조건 생성 코드
              let views = ["view": view!, "squareView": squareView]
              let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:[view]-(<=0)-[squareView(100)]", options: .alignAllCenterY, metrics: nil, views: views)
              let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[view]-(<=0)-[squareView(100)]", options: .alignAllCenterX, metrics: nil, views: views)
              // 레이아웃 활성화 코드
              // UIView 의 addConstrains(_:) 를 활용한 방법
              view.addConstraints(horizontalConstraints)
              view.addConstraints(verticalConstraints)
              // NSLayoutConstraint 의 active(_:) 를 활용한 방법
              NSLayoutConstraint.activate(horizontalConstraints)
              NSLayoutConstraint.activate(verticalConstraints)
          }    
        }
        
        

        애플에서 제공하는 Visual Format 언어를 활용해 문자열 형식으로 레이아웃을 표현합니다. 간단한 표현식만으로 한번에 여러 개의 제약조건을 생성할 수 있다는 장점이 있습니다. 하지만 표현의 완전성보다 시각화에 초점을 둔 기능으로 가로, 세로 비율 설정과 같이 설정하지 못하는 제약조건이 있다는 한계가 있고, 컴파일러가 Visual Format 언어의 유효성을 검사하지 않아 잘못된 제약조건을 설정하면 런타임에 예외가 발생합니다. 한 문장으로 여러 개의 제약조건을 표현할 수 있어 반환 값이 위의 예시들과는 달리 배열입니다. 그래서 활성화하는 코드도 조금 차이가 있습니다. 자세한 문법은 Auto Layout Guide: Visual Format Language 에서 확인하실 수 있습니다.

      참고할 만한 비슷한 질문, 자료

      • 이 게시글은 TTOzzi에 의해 3 years, 7 months 전에 수정됐습니다.
      • 이 게시글은 TTOzzi에 의해 3 years, 7 months 전에 수정됐습니다. 이유: self 명시 관련 Swift 5.3 업데이트 내용 추가
      • 이 게시글은 TTOzzi에 의해 3 years, 7 months 전에 수정됐습니다.
      • 이 게시글은 TTOzzi에 의해 3 years, 7 months 전에 수정됐습니다.
      • 이 게시글은 TTOzzi에 의해 3 years, 7 months 전에 수정됐습니다. 이유: Swift 5.3 업데이트 클로저 self 캡처 내용 보충
      • 이 게시글은 TTOzzi에 의해 3 years, 7 months 전에 수정됐습니다.
      • 이 게시글은 TTOzzi에 의해 3 years, 7 months 전에 수정됐습니다.
      • 이 게시글은 야곰에 의해 3 years, 7 months 전에 수정됐습니다.
      • 이 게시글은 야곰에 의해 3 years, 7 months 전에 수정됐습니다.
      • 이 게시글은 야곰에 의해 3 years, 7 months 전에 수정됐습니다.
    • GangWoon
      참가자
      • 글작성 : 5
      • 답글작성 : 7

      이번 주도 올려주셔서 감사합니다!

1 답변 글타래를 보이고 있습니다
  • 답변은 로그인 후 가능합니다.

logo landscape small

사업자번호 : 743-81-02195
통신판매업 신고번호 : 제 2022-충북청주-1278 호
고객센터 : 카카오톡채널 @yagom