[iOS] 의존성 주입

RudinP·2024년 6월 19일
0

Study

목록 보기
229/258

의존성 주입

== Dependency Injection, DI

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let cell = sender as? UICollectionViewCell, let indexPath = planetCollectionView.indexPath(for: cell){
            let selected = solarSystemPlanets[indexPath.item]
            
            if let vc = segue.destination as? PlanetDetailViewController{
                //의존성 주입
                vc.planet = selected
            }
        }
    }
  • 객체지향에서 사용하는 단어.
  • 한 객체가 다른 객체 없이 동작 불가능한 개념
  • 예시를 들자면, 목록 뷰에서 선택을 하여 상세 화면을 표시하였을 경우, 데이터를 무엇을 표현하는지는 목록 뷰에서 상세 화면으로 넘겨주어야 한다. 이런 경우 상세 화면은 목록 화면에서 선택한 데이터에 의존하고 있는 것이다.
  • 주입: 의존 화면이 동작 가능하도록 필요한 객체를 외부에서 전달해 주는 것

주입의 방법

1. Property Injection

== Setter Injection

  • 속성 주입, 즉 객체를 대입 연산자로 제공해주는 방법이다.
  • 상단 코드에서 예시를 들자면 prepareSegue에서 목적지뷰에 객체를 설정하는 것이 Property Injection이다.
  • 해당 속성은 optional 로 선언되거나 빈 값으로 초기화되어야 한다.
  • Property Injection은 생성 후 주입되기 때문에 외부에서 접근할 수 있도록 처리해야 한다.
  • 따라서, 객체지향의 원칙 중 은닉성에 위배된다.
  • 속성을 사용할 때에도 optional Binding이 추가로 필요하다.
class NoirFilter{
    func apply(to image: UIImage) -> UIImage{
        print("느와르 필터 적용중...")
        return image
    }
}

class SkyFilter{
    func apply(to image: UIImage) -> UIImage{
        print("스카이 필터 적용중...")
        return image
    }
}

class OceanFilter{
    func apply(to image: UIImage) -> UIImage{
        print("오션 필터 적용중...")
        return image
    }
}

이러한 예시 클래스가 있다고 가정해보자.

class PhotoExporter{
    var filter: NoirFilter?
    
    func export(image: UIImage){
        guard let filter else{
            fatalError("필터가 필요합니다.")
        }
        filter.apply(to: image)
        
        print("사진을 익스포트 합니다.")
    }
}
  • export 메소드를 정상적으로 실행하려면 필터가 반드시 존재해야 한다.
  • 즉, export메소드가 filter속성에 의존하게 되는 것이다.
  • filter가 없으므로 에러 발생.
let img = UIImage(systemName: "star")!
let exporter = PhotoExporter()
exporter.filter = NoirFilter() // 속성 주입, Property Injection
exporter.export(image: img)

2. Initializer Injection

  • Initializer Injection은 만들어진 시점에 의존성 주입이 된다.
  • 속성을 private으로 선언 가능하기 때문에 은닉성을 높일 수 있다.
  • optional Binding을 사용하지 않아도 된다.
  • var를 let으로 바꿔도 된다.
  • 즉, property Injection에 비해 은닉성을 높이고, 효율적이고 안전한 코드를 짤 수 있다.
class PhotoExporterIDI{
    private let filter: NoirFilter
    
    init(filter: NoirFilter) {
        self.filter = filter
    }
    
    func export(image: UIImage){
        filter.apply(to: image)
        
        print("사진을 익스포트 합니다.")
    }
}

let exporter2 = PhotoExporterIDI(filter: NoirFilter())
exporter2.export(image: img)

의존관계 역전 원칙

  • coupling,결합도: 다른 타입에 의존하는 정도
  • 커플링을 제거하는 방법이 의존관계 역전 원칙(Dependency Inversion Principle, DIP)이다.
    • 고수준의 모듈은 저수준의 모듈에 의존하지 않고 추상화에 의존해야 한다.
      • 위를 예로 들면, photoExporter가 filter 클래스에 의존하면 안된다는 것이다.
    • 추상화는 세부 구현에 의존하면 안된다. 세부 구현이 추상화에 의존해야 한다.
  • swift에서는 Protocol로 구현한다.

3. Interface Injection

  • 앞 선 예시에서 각 필터들이 Filter 프로토콜을 준수하게 한다.
protocol Filter{
    func apply(to image: UIImage) -> UIImage
}

class NoirFilter: Filter{
    func apply(to image: UIImage) -> UIImage{
        print("느와르 필터 적용중...")
        return image
    }
}

class SkyFilter: Filter{
    func apply(to image: UIImage) -> UIImage{
        print("스카이 필터 적용중...")
        return image
    }
}

class OceanFilter: Filter{
    func apply(to image: UIImage) -> UIImage{
        print("오션 필터 적용중...")
        return image
    }
}

이렇게 프로토콜을 생성하게 되면 실제 PhotoExporter에서 사용하는 Filter를 전부 프로토콜로 변경 가능하게 되고, 여기에 NoirFilter, OceanFilter, SkyFilter중 무엇이 오든 상관없게 된다.

class PhotoExporterII{
    private var filter: Filter
    
    init(filter: Filter) { //Interface Injection
        self.filter = filter
    }
    
    func export(image: UIImage){
        filter.apply(to: image)
        
        print("사진을 익스포트 합니다.")
    }
}

let exporter3 = PhotoExporterII(filter: NoirFilter())
exporter3.export(image: img)

장점

  1. 유닛테스트를 하기 쉬어짐
  2. 코드 재사용성이 높아짐
  3. 코드 가독성이 높아짐
profile
iOS 개발자가 되기 위한 스터디룸...

0개의 댓글