APIs Públicas
Repositorio 100% nativo en Swift en UIKIt en el que se realizan conexiones a diferentes API Rest. Para cada API he utilizado 3 diferentes patrones para manejar la asincronía:
- Patrón callback
- Librería Combine
- Modelo de concurrencia Async-await
Proporciona en formato json un número generado de manera aleatoria y su detalle como su equivalente en Binario, su version en Romano, Chino, si es primo, perfecto, triangular, etc.
Para la gestión de la asincronía se utiliza Combine, el patrón callback y URLSession:
var subscribers = Set<AnyCancellable>()
func getNumberOfTheDay(url: URL = .numbersURL, _ completion: @escaping (Number) -> ()) {
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.sink { completed in
switch completed {
case .finished: break
case .failure(let error): print("show \(error)")
}
} receiveValue: { data in
let number = try! JSONDecoder().decode(Number.self, from: data)
completion(number)
}.store(in: &subscribers)
}
Proporciona en formato json un chiste aleatorio relacionado el legendario Chuck Norris.
Implementación
Patron Callback y URLSession para la conexión a la API ChuckNorrisNetwork
///#Patrón Callback JSON Genérico
func fetchJson<JSON:Codable>(url: URL,
type: JSON.Type,
session: URLSession,
callback: @escaping ((Result<JSON,PersistenceError>) -> Void)) {
session.dataTask(with: url) { data, response, error in
guard error == nil else {
return callback(.failure(.general("error General")))
}
guard let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
return callback(.failure(.status))
}
guard let data = data else {
return callback(.failure(.data))
}
do {
let result = try JSONDecoder().decode(JSON.self, from: data)
callback(.success(result))
} catch let error {
callback(.failure(.json(error.localizedDescription)))
}
}.resume()
}
Patrón callback para la notificación del cambio en de los valores en las propiedades.
func updateTableView() {
modelLogic.tableAction = { [weak self] _ in
RunLoop.main.perform {
self?.favoritesJokesTableView.reloadData()
}
}
}
Persistencia de datos con un archivo JSON que se guarda en carpeta de documentos de la app.
TableView para los items persistidos
Unit Tests del ModelLogic y un Mock de URLSession gracias a URLPRotocol ChuckURLSessionMock
Proporciona información sobre la división administrativa de regiones, departamentos y municipios en Francia. Datos por municipio como código postal, población.
Implementación
Combine y URLSession para la conexión a la API CommunesNetwork
Combine para la notificación del cambio en de los valores en las propiedades.
override func viewDidLoad() {
super.viewDidLoad()
self.clearsSelectionOnViewWillAppear = false
self.navigationItem.rightBarButtonItem = self.editButtonItem
modelLogic.fetchRegions()
///Actualiza la tableView` cuando ha descargado las *regions*
modelLogic.persistence.subject
.sink { [weak self] _ in
self?.tableView.reloadData()
}.store(in: &subscribers)
}
protocol CommunesFetcher {
var session: URLSession { get }
var subject: PassthroughSubject<String,Never> { get }
func valuesReceived()
func getJSON<JSON:Decodable>(url: URL, type: JSON.Type, receiveValue: @escaping (JSON) -> ())
}
TableView para los listados
Unit Tests del ModelLogic con un Mock en la petición a red CommunesUnitTests
final class CommunesNetworkMock: CommunesFetcher {
var session: URLSession
var subject: PassthroughSubject<String, Never>
func valuesReceived() {
}
init(subject: PassthroughSubject<String, Never> = PassthroughSubject<String, Never>(), session: URLSession = .shared) {
self.subject = subject
self.session = session
}
func getJSON<JSON>(url: URL, type: JSON.Type, receiveValue: @escaping (JSON) -> ()) where JSON : Decodable {
switch url {
case .regionsURL: receiveValue(Region.samples() as! JSON)
case .departementsURL: receiveValue(Departement.samples() as! JSON)
case .franceCommunes: receiveValue(Commune.samples()as! JSON)
default: break
}
}
}
API que genera un avatar personalizable en formato SVG, PNG o JPG, con opciones como color de fondo, ojos, boca, tamaño, rotación, etc.
Implementación
Hecho con el modelo de concurrencia Async-await
func getEmojiWithOptions(_ model: DiceBearModel) async throws -> UIImage {
try await fetchImage(url: DiceBearModel(
funEmojiWithBackgroundColor: model.backgroundColor,
backgroundType: model.backgroundType,
eyes: model.eyes,
mouth: model.mouth).url, session: session)
}
NotificationCenter para la notificación del cambio en de los valores en las propiedades.
Permite la persistencia de datos través de CoreData. APIRestDemoDataBase
//MARK: DiceBear
extension APIRestDemoDataBase {
func fetchEmojis() throws -> [DiceBearEmojiModel] {
let request = EmojiEntity.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \EmojiEntity.createdAt, ascending: false)]
do {
let emojis = try context.fetch(request)
return emojis.compactMap { $0.emojiModel }
} catch {
throw DataBaseError.fetch
}
}
func insert(emoji: DiceBearEmojiModel) throws {
let newEmojiEntityItem = emoji.getEmojiEntityItem(from: emoji, in: context)
context.insert(newEmojiEntityItem)
do {
try context.save()
} catch {
throw DataBaseError.insert
}
}
}
CollectionView para mostrar los elementos persistidos
Países
Implementación
Hecho con la funcionalidad Continuations que permite crear un puente entre la función de red con callback para convertirlo a un código asíncrono (async-await). Countries Network
Diferentes APIs de las que se obtiene un listado de países ( 250 ) y a partir de esa información se consulta en otras APIs para la descarga de la bandera del icono de la tabla, la bandera en tamaño 128×96, y una cuarta API con APIKey que permite la consulta de la regiones y ciudades del país seleccionado.
Interfaz de red que implementa un ApiKey para el acceso a las regiones y ciudades.
static func cities(for ccaCode: String, region: String) -> URL {
//Validar que haya 3 letras mínimo
let components = region.components(separatedBy: " ")
let first = components.first?.prefix(3)
let last = components.last?.prefix(3)
let code = last ?? first ?? region.prefix(3)
let hint = code.count < 3 ? "123" : code //"123" == No region hint
return URL(string: "http://battuta.medunes.net/api/city/\(ccaCode.lowercased())/search/?region=\(hint)&key=\(BATAK)")!
}
La APIKey la he almacenado en un json en el documentsDirectory, evitando así ponerla en código
extension URL {
static let TK = URL.documentsDirectory.appending(path: "TK.json")
}
struct TK: Codable {
let name: String
let value: String
}
var BATAK: String {
if let data = try? Data(contentsOf: .TK),
let keys = try? JSONDecoder().decode([TK].self, from: data),
let value = keys.first(where: { $0.name == "BATTK" } )?.value {
return value
} else {
return ""
}
}
NotificationCenter para la notificación del cambio en de los valores en las propiedades.
MapKit para mostrar la localización de la ciudad elegida y calcular la distancia hasta la localización del usuario CountryDetailViewController
Marvel Characters
API que obtiene de la API Developer Marvel un listado de imágenes de los personajes, a los que se les puede marcar como favorito.
Implementación
Hecho con el modelo de concurrencia Async-await
NotificationCenter para la notificación del cambio en de los valores en las propiedades.
NotificationCenter.default.addObserver(forName: .marvelCharacters, object: nil, queue: .main) { [weak self] _ in
self?.collectionView.collectionViewLayout.invalidateLayout()
self?.collectionView.reloadData()
if let snapshot = self?.modelLogic.snapshot {
self?.dataSource.apply(snapshot, animatingDifferences: true)
self?.activityIndicator.stopAnimating()
}
}
Las CollectionViews están implementadas con la clase DiffableDataSource para la gestión de los elementos. MarvelCharactersModelLogic
El diseño de las celdas de las CollectionViews está implementado en SwiftUI gracias el UIHostingConfiguration MarvelCharactersCollectionViewController
lazy var dataSource: UICollectionViewDiffableDataSource<Int,MarvelCellCharacter> = {
UICollectionViewDiffableDataSource<Int,MarvelCellCharacter>(collectionView: collectionView) { [self] collectionView, indexPath, character in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "marvelCharacterCell", for: indexPath)
cell.contentConfiguration = UIHostingConfiguration {
MarvelCharacterCellView(character: character)
}
return cell
}
}()
Grupos de tareas permiten la concurrencia de Async-await en la devolución de las imágenes de los personajes MarvelCharactersNetwork
Interfaz de red que implementa un timestamp, ApiKey y hash para el acceso a la API
¿Quieres recibir posts, cheatCodes, enlaces y katas en Swift para practicar?
Quincenalmente recibirás en tu correo electrónico la newsletter, solo hace falta tu correo electrónico.