This playground demonstrates key SwiftUI and Combine concepts including:

  • Network requests using async/await and Combine
  • MVVM architecture
  • SwiftUI views and navigation
  • Protocol-based networking
  • JSON decoding
import Foundation
import SwiftUI
import PlaygroundSupport
import Combine

// MARK: - Networking

// Protocol defining network operations
protocol NetworkManaging {
	func fetch<T:Decodable>(url: String) throws -> T
	func uploadFile(url: String)
}

extension NetworkManaging {
	func uploadfile(url: String) {
		
	}
}

// MARK: - Models

// Post model conforming to Decodable for JSON parsing and Identifiable for SwiftUI lists
struct Post: Decodable, Identifiable {
    let id: Int
    let title: String
}

extension Post: Hashable {
	func hash(into hasher: inout Hasher) {
		hasher.combine(id)
	}
}

// MARK: - Network Functions

// Async/await version of fetch posts
func fetchPosts() async throws -> [Post] {
    let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts")!))
    return try JSONDecoder().decode([Post].self, from: data)
}

// Combine version of fetch posts
func fetchPostsCombine() -> AnyPublisher<[Post], Error> {
    return URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
        .map(\.data)
        .decode(type: [Post].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

Task {
	do {
		let posts = try await fetchPosts()
		for post in posts {
			print("Title: \(post.title)")
		}
	} catch {
		print("Failed to fetch posts: \(error)")
	}
}

// MARK: - View Model

// ViewModel using @MainActor to ensure UI updates happen on main thread
@MainActor
class VM: ObservableObject {
    // Published property for SwiftUI to observe
    @Published var posts: [Post] = []
    // Set to store and manage Combine subscriptions
    private var cancellables = Set<AnyCancellable>()
    
    init() {
    }
	func load() {
		Task { [weak self] in
			guard let self = self else { return }
			self.posts = try! await fetchPosts()
		}
	}
	func loadCombine() {
		fetchPostsCombine().sink { completion in
			switch completion {
			case .failure(_):
				print("Error")
			case .finished:
				break
			}
		} receiveValue: { posts in
			self.posts = posts
		}
		.store(in: &cancellables)
	}
}

// MARK: - Views

// Main content view using NavigationStack for navigation
struct ContentView: View {
    // StateObject to maintain ViewModel instance
    @StateObject var vm: VM = VM()
    
    var body: some View {
		NavigationStack {
			ForEach(vm.posts) { post in
				NavigationLink {
					Text(post.title)
				} label: {
					Text(post.title)
				}

			}
		}
		.onAppear {
			vm.loadCombine()
		}
	}
}

PlaygroundPage.current.setLiveView(ContentView())