Swift 함수의 구현 및 추상 분리 구현(함수 DI)

31268 단어 iOSSwiftMacOStech

말하다


오늘 저녁트위터에 떠도는 포켓몬 만화 하나하나가 너무 좋아서 매일 고통스러워 빨리 포켓몬과 함께 살고 싶어요.
이번에는 함수의 실상과 추상적인 분리에 대해 고찰한다.
실장과 추상의 분리를 고려하고 아래의 대응을 고려하면 함수도 추상을 잡을 수 있다.
이루어지다
추상
구조류
합의
함수.
?
순함수는 실장과 추상을 명확하게 분리할 수 있다.다음 함수의 정의는 구축할 때 실현을 결정하지만, 분리되면 실행 전의 선택이 지연됩니다.따라서 의존성 주입을 실행할 때 가능하다.
// 通常の関数の定義
func add(first: Int, second: Int) -> Int {
  first + second
}

// 分離した場合の定義
let impl: (Int, Int) -> Int = { first, second in first + second }
func add(first: Int, second: Int) -> Int {
  impl(first, second)
}

고급 함수


그럼, 이대로는 좀 쓰기 힘들다.따라서 함수를 호출하면 실장으로 되돌아오면 조리가 뚜렷한 것 같아 고급 함수라고 부른다.이를 실현하기 위해 함수형에 라벨이 있다면 즐거울 것이다.
다음은 설치 예입니다.하지만 지금은 라벨을 줄 수 없습니다.Swift3부터 클론 프로그램에서 유형 순으로만 판별하기 때문에 분리할 때의 정의를 함수로 표시할 수 없습니다.
https://stackoverflow.com/questions/39613272/xcode-8-function-types-cannot-have-argument-label-breaking-my-build
// ❌
func add_maker(
    impl: @escaping (Int, Int) -> Int = {
        first, second in first + second
    }
) -> (first: Int, second: Int) -> Int {
    { (first: Int, second: Int) -> Int in
        impl(first, second)
    }
}
let add = add_maker()
add(first: 1, second: 2)

// ✅
func add_maker(
    impl: @escaping (Int, Int) -> Int = {
        first, second in first + second
    }
) -> (Int, Int) -> Int {
    { (first: Int, second: Int) -> Int in
        impl(first, second)
    }
}
let add = add_maker()
add(1, 2)
고급 함수를 사용하는 방법으로는 라벨을 붙일 수 없을 것 같으니 다른 방법을 연구해 보자.

구조적 함수화


구조를 함수처럼 호출하는 방법이 존재한다.Swift5.2에서 callAsFunction까지의 방법을 정의하여 함수처럼 구조 실례를 처리할 수 있다.
이것으로 매개 변수의 라벨을 표시합니다.호출된 곳에서 설치를 각각 정의할 수도 있고 extension에서 일반적으로 처리해야 할 처리를 정의할 수도 있습니다.의미 있는 함수를 처리할 때 아무것도 하지 않는 것을 추가로 정의할 수 있다.
struct Sample {
  let impl: (Int, Int) -> Int

  func callAsFunction(first: Int, second: Int) -> Int {
        return impl(first, second)
    }
}

extension Sample {
  static func live() -> Self {
    .init { (first: Int, second: Int) -> Int in
      first + second
    }
  }
}

let sample: Sample = .live()
sample(first: 1, second: 2)
느낌이 너무 좋아요!함수급에서도 실장과 추상적인 분리를 잘 실현할 수 있고 테스트 코드도 쓰기 쉽다.

구조의 공통화


그렇다면 이 코드를 보았을 때 같은 유형이 세 번이나 쓰여 있다는 것을 알아차렸습니까?이것은 변경할 때 어려움을 느낄 수 있으니 한 번의 정의로 잘 표현하는 방법을 연구해 보세요.
우리는 일반적인 프로그램 설계에서 같은 값을 이용하기 위해 변수를 변수에 묶는다. 이것은 우리가 형식 단계에서도 표현하고자 하는 코드이다.
struct Sample {
    typealias Input = (first: Int, second: Int)
    typealias Output = Int

    let impl: (Input) -> Output

    func callAsFunction(args: Input) -> Output {
        impl(args)
    }
}

extension Sample {
    static func live() -> Self {
        .init { args in
            args.first + args.second
        }
    }
}

let sample: Sample = .live()
sample(args: (first: 1, second: 2))
한 유형을 성공적으로 정리했습니다!한 함수에 덧붙인 부분도 매우 적다고 한다.
유사한 함수를 더 많이 정의하는 것을 고려해 보자. 공통된 요소를 많이 볼 수 있다.
  • let impl: (Input) -> Output
  • func callAsFunction(args: Input) -> Output { impl(args) }
  • 또한 우리는 파라미터가 있는 상황을 연구하고 있으며 통용성을 고려할 때 파라미터가 없는 상황을 고려해 주십시오.
    아래의 기술은 협의에서 같은 기술을 총결하였다.
    입력이 Void일 때 함수는 f()처럼 넣어야 한다.그러나 지금은 f(args: ())처럼 매개 변수를 비워 두어야 하기 때문에 조건이 있는 기본 실시 방안을 추가해야 한다.
    protocol DIFunction {
        associatedtype Input
        associatedtype Output
    
        var impl: (Input) -> Output { get }
    
        func callAsFunction(args: Input) -> Output
    }
    
    extension DIFunction where Input == Void {
        func callAsFunction(args: Input = ()) -> Output {
            impl(args)
        }
    }
    
    extension DIFunction {
        func callAsFunction(args: Input) -> Output {
            impl(args)
        }
    }
    
    struct ASample: DIFunction {
        let impl: ((first: Int, second: Int)) -> Int
    }
    
    struct BSample: DIFunction {
        let impl: (Void) -> Int
    }
    
    extension ASample {
        static func live() -> Self {
            .init { args in
                args.first + args.second
            }
        }
    }
    
    extension BSample {
        static func live() -> Self {
            .init { return 42 }
        }
    }
    
    let asample: ASample = .live()
    asample(args: (first: 4, second: 2)) // Output: 6
    let bsample: BSample = .live()
    bsample() // Output: 42
    
    각 함수로 처리하고자 하는 구조체의 정의가 완성되었습니다!이것을 사용하면 함수를 분리할 수 있는 실장과 처리의 흔적이 나타난다.

    함수 관계


    만약 A 함수가 B 함수에 필요하다면, 우리는 어디에서 그것의 의존 관계를 해결해야 합니까?
    B 함수에 A 함수가 필요한 경우 B 함수를 호출합니다.그러나 실행할 때마다 A 함수를 생성하는 것을 피해야 한다.예를 들어 JSONESNCoder 등은 초기 설정이 어느 정도 이뤄졌다면 사용할 수 있었을 것으로 보인다.
    다행히도 이것은 간단하게 해결할 수 있다.정의가 실현되었을 때 static 함수live() 등으로 지금까지 정의되었다.전달은 이 매개 변수에 의존하는 관계를 통해 순조롭게 진행된다.
    struct Linear: DIFunction {
        let impl: ((first: Int, second: Int)) -> Int
    }
    
    struct Bias: DIFunction {
        let impl: (Void) -> Int
    }
    
    extension Linear {
        static func live(
            bias: Bias = .live() // ✅
        ) -> Self {
            .init { args in
                return args.first * bias() + args.second
            }
        }
    }
    
    extension Bias {
        static func live() -> Self {
            .init {
                return 10
            }
        }
    
        static func debug() -> Self {
            .init {
                print("debug")
                return 100
            }
        }
    }
    
    let linearFunctions: [Linear] = [
        .live(),
        .live(bias: .live()),
        .live(bias: .debug()),
        .live(bias: .init(impl: { 1000 })),
        .init(impl: { args in
            return 420_000 + args.first * 10 + args.second
        })
    ]
    

    끝말


    함수라는 덩어리가 어떻게 분해할 수 있는 요소로 구성되는지 살펴보자.
    모든 것을 가상 실현으로 바꾸어 처리 프로세스를 테스트하는 등 주입 가능한 쓰기에 의존하면 범위를 확대할 수 있다.
    그럼 이따 봐요.
    나는 트위터를 하고 있는데, 아주 적절하게 말했다.

    좋은 웹페이지 즐겨찾기