Комбинированный доступ к серверу извлекает данные в игровой площадке, но не в функции. Почему?

#swift #combine

#swift #объединить

Вопрос:

Следующий код выдает результат, который я ожидаю:

 import UIKit
import Combine


    let myURL = URL(string: "https://disease.sh/v3/covid-19/apple/countries/Canada")

    // MARK: - Region
    struct Region: Codable {
        let country: String
        let subregions: [String]
    }


    let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: myURL!)
        .map { $0.data }
        .print("Hello Data")
        .decode(type: Region.self, decoder: JSONDecoder())

    let cancellableSink = remoteDataPublisher
        .sink(receiveCompletion: { completion in
                print(".sink() received the completion", String(describing: completion))
                switch completion {
                    case .finished:
                        break
                    case .failure(let anError):
                        print("received error: ", anError)
                }
        }, receiveValue: { someValue in
            print(".sink() received (someValue)")
        })


// =====================================================================================================
 
print("The End.")
  

Вот результат, показанный в консоли.
Как вы можете видеть, «отмены» нет:

введите описание изображения здесь


Однако, когда я обертываю код в функцию (), ‘combine’ ОТМЕНЯЕТ вывод.
Следующий код в getRegionList() не выдает результат. Вместо этого он получает «отмену», как показано в консоли, следуя приведенному ниже коду:

 import UIKit
import Combine

var regionList = [String]()
let simplePublisher = PassthroughSubject<String, Never>()


func getRegionList() {
let myURL = URL(string: "https://disease.sh/v3/covid-19/apple/countries/Canada")

// MARK: - Region
struct Region: Codable {
    let country: String
    let subregions: [String]
}


let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: myURL!)
    .map { $0.data }
    .print("Hello Data")
    .decode(type: Region.self, decoder: JSONDecoder())

    let cancellableSink = remoteDataPublisher
    .sink(receiveCompletion: { completion in
            print(".sink() received the completion", String(describing: completion))
            switch completion {
                case .finished:
                    break
                case .failure(let anError):
                    print("received error: ", anError)
            }
    }, receiveValue: { someValue in
        print(".sink() received (someValue)")
    })
}
getRegionList()
print("The End.")
  

Функция отменяется, как показано на консоли:

введите описание изображения здесь

Почему?
Единственное отличие кода в том, что он заключен в простую функцию.
Я подозреваю, что какой-то «срок службы», должно быть, истек.

Решение: должен ли я сделать concellableSink глобальным?
Каково правильное решение / синтаксис?

Ответ №1:

Вам нужно сохранить ссылку на AnyCancellable , в противном случае, когда она освобождается, она отменяет подписку, что и происходит, когда ваша функция возвращается.

Обычно это обрабатывается путем сохранения ссылки в AnyCancellable свойстве (или Set<AnyCancellable> ) экземпляра:

 class Foo {
   var cancellables: Set<AnyCancellable> = []

   //...

   func doSomething() {
      somePublisher
        .sink(...)
        .store(in: amp;cancellables)
   }
}
  

В качестве альтернативы, sink закрытие может сохранять ссылку на свой собственный AnyCancellable и освобождать его при получении значения, но, конечно, это может быть утечкой памяти, если издатель никогда ничего не публикует (хотя вы могли бы смягчить это с .timeout помощью оператора):

 func doSomething() {
   let cancellable: AnyCancellable? = nil
   cancellable = somePublisher
      .sink(receiveCompletion: { completion in
         // ...
         cancellable = nil
      }, receiveValue: { value in
         //...
      })
}