Extensiones de URLSession
URLSession puede hacer lo siguiente:
- Transferir datos a una URL.
- Descargar el contenido de una URL como un archivo binario que se guarda en el sistema de ficheros.
- Subir un archivo binario a una URL.
- Tareas de Streams para comunicar dos partes por medio de flujos de datos (streams).
- Tareas de Websockets para comunicar dos sockets.
Entre todas estas funcionalidades, URLSession solo expone la primera (transferir datos a una URL) con una interfaz de Combine por medio del operador .dataTaskPublisher(for:). En el siguiente ejemplo se puede ver cómo descargar los datos de un sitio web. Aquí no hay que olvidar almacenar la suscripción, porque la tarea nunca iniciará si la suscripción se elimina. Observar que los datos descargados llegan por el closure de receiveValue, pero igual hay que procesar el evento de fin (receiveCompletion) donde puede venir un posible error.
let url = URL(string: "https://goyesdev.com/mydata.json")!
URLSession.shared
.dataTaskPublisher(for: url)
.sink(receiveCompletion: { completion in
if case .failure(let err) = completion {
print("Hubo un error")
}
}, receiveValue: { data, response in
print("Descargué unos datos de tamaño \(data.count), y la respuesta fue: \(response)")
})
.store(in: &subscriptions)
Soporte a Codable
Se puede usar JSONDecoder para decodificar una respuesta web. A continuación presento el código para hacerlo de forma imperativa con tryMap. Cabe resaltar que cada vez que se recibe una nueva respuesta, se vuelve a crear el JSONDecoder dentro del tryMap.
let subscription = URLSession.shared
.dataTaskPublisher(for: url)
.tryMap { data, _ in
try JSONDecoder().decode(MyType.self, from: data)
}
.sink( ... )
El operador decode(type:decoder:) permite hacerlo de forma declarativa:
let subscription = URLSession.shared
.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: MyType.self, decoder: JSONDecoder())
.sink( ... )
Como .dataTaskPublisher(for:) emite tuplas de tipo (Data, URLResponse), es necesario aplicar map antes de decode(type:decoder:) para extraer el primer dato.
Publicando datos de una petición web a múltiples suscriptores
Es posible que varios clientes necesiten suscribirse al resultado de la misma petición web, de tipo Publisher. Por defecto, crear dos suscripciones al mismo Publisher provocará dos ejecuciones del mismo pipeline, o sea, en este caso: dos peticiones web. El operador share() puede ayudar a crear un observador caliente para no iniciar dos peticiones web, sin embargo, la petición empieza tan pronto ocurre la primera suscripción y es posible que alguna suscripción posterior no alcance a recibir el resultado.
Ante esta problemática, hay que crear manualmente un multicast (que crea un ConnectablePublisher que emite valores a través de un Subject) y hacerle connect cuando ya estén listas todas las suscripciones.
En este caso, tener presente guardar todas las suscripciones.
let url = URL(string: "https://www.raywenderlich.com")!
let publisher = URLSession.shared
.dataTaskPublisher(for: url)
.map(\.data)
// Se crea el multicast
.multicast { PassthroughSubject<Data, URLError>() }
// Agregar suscriptores al multicast
let subscription1 = publisher.sink( ... )
let subscription2 = publisher.sink( ... )
// Conectar el ConnectablePublisher
let subscription = publisher.connect()
Cuestionario
1. ¿Qué tipo de tarea de URLSession tiene soporte directo para Combine mediante dataTaskPublisher(for:)?
2. ¿Por qué es necesario almacenar la suscripción de un dataTaskPublisher en una variable o colección?
3. ¿Qué diferencia práctica hay entre usar tryMap y decode(type:decoder:) para decodificar datos JSON?
4. ¿Por qué se aplica map(\.data) antes de usar decode(type:decoder:) en un pipeline con dataTaskPublisher?
5. Explica por qué usar solo share() puede causar que algunas suscripciones no reciban datos en una petición web compartida.
6. ¿Qué emite el publisher creado con URLSession.shared.dataTaskPublisher(for:)? ✅
- [ ] Solo los datos (
Data) - [ ] Una tupla
(Data, URLResponse) - [ ] Solo la respuesta (
URLResponse)
7. ¿Qué hace el operador decode(type:decoder:) en Combine? ✅
- [ ] Convierte los datos binarios en texto plano
- [ ] Usa un
JSONDecoderu otro decodificador para transformar los datos en un tipoCodable - [ ] Filtra las respuestas HTTP exitosas
8. ¿Qué ocurre si creas dos suscripciones independientes al mismo dataTaskPublisher sin usar share() ni multicast()? ✅
- [ ] Se realiza solo una petición y se comparte la respuesta
- [ ] Se realizan dos peticiones web separadas
- [ ] La segunda suscripción queda en espera
9. ¿Qué operador permite crear manualmente un ConnectablePublisher que se inicia con .connect()? ✅
- [ ]
multicast - [ ]
share - [ ]
makeConnectable
10. ¿Cuál es la función principal del método .connect() en un publisher multicasted? ✅
- [ ] Cancelar las suscripciones activas
- [ ] Iniciar la emisión de valores hacia todos los suscriptores
- [ ] Reiniciar el pipeline desde cero
Solución
1. ¿Qué tipo de tarea de URLSession tiene soporte directo para Combine mediante dataTaskPublisher(for:)?
La tarea de transferir datos a un servidor (
dataTask).
2. ¿Por qué es necesario almacenar la suscripción de un dataTaskPublisher en una variable o colección?
En caso contrario, el
Publisherse cancela cuando el scope donde se creó la suscripción termina.
3. ¿Qué diferencia práctica hay entre usar tryMap y decode(type:decoder:) para decodificar datos JSON?
tryMaprequiere escribir la lógica de decodificación dentro del closure, mientras quedecode(type:decoder:)es el operador especializado de Combine que hace el trabajo automaticamente de forma declarativa. Además, solo se necesita crear una vez el decodificador.
4. ¿Por qué se aplica map(\.data) antes de usar decode(type:decoder:) en un pipeline con dataTaskPublisher?
Porque
dataTaskPublisher(for:)devuelve una tupla y solo necesitamos pasar los datos al siguiente paso del pipeline (i.e.decode(type:decoder:)).
5. Explica por qué usar solo share() puede causar que algunas suscripciones no reciban datos en una petición web compartida.
share()inicia la petición cuando se suscribe el primer observador; si la petición ya terminó, los nuevos suscriptores no recibirán el resultado.
6. ¿Qué emite el publisher creado con URLSession.shared.dataTaskPublisher(for:)?
- [✅] Una tupla
(Data, URLResponse)
7. ¿Qué hace el operador decode(type:decoder:) en Combine?
- [✅] Usa un
JSONDecoderu otro decodificador para transformar los datos en un tipoCodable
8. ¿Qué ocurre si creas dos suscripciones independientes al mismo dataTaskPublisher sin usar share() ni multicast()?
- [✅] Se realizan dos peticiones web separadas
9. ¿Qué operador permite crear manualmente un ConnectablePublisher que se inicia con .connect()?
- [✅]
multicast
10. ¿Cuál es la función principal del método .connect() en un publisher multicasted?
- [✅] Iniciar la emisión de valores hacia todos los suscriptores
Top comments (0)