I have gone through Swift Closures and ARC in Swift and I got little confused.
I have simple scenario of calling web service and using response data.
Here is my basic implementation:
class WebServices: NSObject {
func requestDataFromServer(completion: #escaping (_ data: Data?) -> Void) {
//web service call here
completion(Data())
}
deinit {
print("WebServices deinitializer...")
}
}
class Controller: NSObject {
private let webService = WebServices()
private func useResponseData(_ data: Data) {
print("Response Data: \(data)")
}
func fetchData() {
webService.requestDataFromServer { (data) in
if let responseData = data {
self.useResponseData(responseData)//direct use of self
}
}
}
deinit {
print("Controller deinitializer...")
}
}
var controller: Controller? = Controller()
controller!.fetchData()
controller = nil
Console output is:
Response Data: 0 bytes
Controller deinitializer...
WebServices deinitializer...
My question is even I'm using selfdirectly inside closure why this implementation is not causing Reference Retain Cycle?
If I use unowned or weak then also same behavior.
And what can cause reference retain cycle in above scenario?(I don't want to cause, rather want to be aware of mistakes)
There is no issue in your code as requestDataFromServercall the completion handler directly (no async). So the caller can't have been released during you call.
But when you will implement your webservice's real call, it will be async. So user can switch page before your webservice answer. In that case, you will retain a strong reference to your controller and it will never be released. You should use a [weak self]in your closure (and so call self?.useResponseData(responseData) with self as optional).
unowned is used in case that you are sure your reference will not be nil (it's not an optional)
Ok, I guess the problem is what you are doing here is not actually async, so it executes one after another, this code causes memory leak:
import Foundation
class WebServices {
func requestDataFromServer(completion: #escaping (_ data: Data?) -> Void) {
//web service call here
print("called")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
print("called async")
completion(Data())
})
}
deinit {
print("WebServices deinitializer...")
}
}
class Controller: UIViewController {
private let webService = WebServices()
private func useResponseData(_ data: Data) {
print("Response Data: \(data)")
}
func fetchData() {
webService.requestDataFromServer { (data) in
print("called")
if let responseData = data {
self.useResponseData(responseData)//direct use of self
}
}
self.dismiss(animated: true, completion: nil)
}
deinit {
print("Controller deinitializer...")
}
}
var controller: Controller? = Controller()
controller!.fetchData()
Related
During the development of a swiftUI project, I wish to have option to switch the way data is set, either
Allowing API calls to be made as normal which sets an #Published variable, or
Set the #published variable from mocked file and NOT make the api call.
The reason is that I am limited to the number of api calls per minute.
In my example below I load the mocked data in a model called "Person".
Current solution
Set a global variable to distinguish between the two above mentioned states.
In all places where api calls were be made, I introduce a condition to optionally use mocked data and not make the api call. See .task in MyView
struct GlobalConstants {
static let use_mock_data = true
}
class ViewModel: ObservableObject {
#Published var data: [Person] = []
#MainActor
func fetchData() async {
// ... data is set in this code
}
}
Within Person model, I set a static variable that returns the decoded mock data from a json file. The decode method below is an extension to Bundle (Thanks Paul Hudsen).
extension Person {
static var mockPersons: [Person] {
Bundle.main.decode([Person].self, from: "persons.json")
}
}
struct MyView: View {
#StateObject var vm = PersonViewModel()
var body: some View {
NavigationView {
List {
ForEach(vm.data) { d in
NavigationLink {
OtherView(prop: d.detail)
} label: {
Text(d.name)
}
}
}
.task { // -----condition--------------------- //
if GlobalConstants.use_mock_data {
vm.data = Person.mockPersons
} else {
await vm.fetchData()
}
}
}
}
}
Question
What other approaches can I consider for enabling the two states? Overriding the methods in some way?
I am still on the learning curve to swift and wondering if theres a better way to enable long term maintenance in a clean and predictable way.
As fetching the data belongs to the view model my suggestion is to put the condition into fetchData.
Marking a method as async doesn't require necessarily that the executed code is asynchronous
#MainActor
class ViewModel: ObservableObject {
#Published var people: [Person] = []
func fetchData() async {
if GlobalConstants.use_mock_data {
people = Person.mockPersons
} else {
// people = await callTheAPI()
}
}
}
and replace the task in the view with
.task {
await vm.fetchData()
}
Note: people is a more meaningful name than data 😃
You should use a Service with a protocol, and then create 2 services extending the protocol, one for mock and one for data, here are some examples from one of my apps (using alamofire, but you can modify it to use it with URLSession or anything else) :
import Alamofire
class NetworkManager {
func get<T: Decodable>(url: URLConvertible, parameters: Parameters?,_ completion: #escaping (DataResponse<T, AFError>) -> Void) {
AF.request(url, parameters: parameters)
.responseDecodable(of: T.self) { response in
completion(response)
}
}
}
protocol SearchServicing {
var parameters: Parameters? { get set }
func get(_ completion: #escaping (Result<SearchResponse, AFError>) -> Void)
}
class SearchService: NetworkManager, SearchServicing {
var parameters: Parameters?
func get(_ completion: #escaping (Result<SearchResponse, AFError>) -> Void) {
let url = "YOUR_URL"
get(url: url, parameters: parameters) { response in
completion(response.result)
}
}
}
class SearchMockService: SearchServicing {
var parameters: Parameters?
var getCallCounter = 0
func get(_ completion: #escaping (Result<SearchResponse, AFError>) -> Void) {
getCallCounter += 1
let response = SearchResponse(recipes: [
Recipe.mock,
Recipe.mock,
Recipe.mock,
])
completion(.success(response))
}
}
Then add it in the ViewModel :
#MainActor
class RecipeViewModel: ObservableObject {
...
private var service: SearchServicing
init(service: SearchServicing) {
self.service = service
}
public func fetchData() {
[...]
service.parameters = ["q": ingredients.map { $0.name }.joined(separator: " ")]
service.get { [weak self] result in
switch result {
case .success(let searchResponse):
self?.totalResults = searchResponse.count
self?.results = searchResponse.recipes
case .failure(let error):
dump(error)
self?.error = AppError(error: error)
}
}
}
}
And so it allows me to do :
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView(recipeViewModel: RecipeViewModel(service: SearchMockService()))
}
}
whenever I need to test my view model and
struct MainView: View {
#ObservedResults(RecipeEntity.self) var favorites
#StateObject var recipeViewModel = RecipeViewModel(service: SearchService())
var body: some View {
...
}
}
for non mock datas service
For more informations checkout this video, it helped me to use this structure : How to implement a Mock in Swift!
I'm trying to extend my class with async/await capabilities, but at run-time there is an error in the console:
SWIFT TASK CONTINUATION MISUSE: query(_:) leaked its continuation!
Below is the class I'm trying to add the continuation to which uses a delegate:
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
private let completer: MKLocalSearchCompleter
private var completionContinuation: CheckedContinuation<[MKLocalSearchCompletion], Error>?
init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
guard !value.isEmpty else {
completionContinuation?.resume(returning: [])
completionContinuation = nil
return
}
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completionContinuation?.resume(returning: completer.results)
completionContinuation = nil
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completionContinuation?.resume(throwing: error)
completionContinuation = nil
}
}
This is how I use it:
let localSearch = LocalSearch()
do {
let results = try await localSearch.query("toront")
print(results)
} catch {
print(error)
}
What am I doing wrong or is there a better way to achieve this?
This message appears if a continuation you created via withCheckedContinuation, or withCheckedThrowingContinuation doesn't report success or failure before being discarded. This is will lead to resource leaking:
Resuming from a continuation more than once is undefined behavior. Never resuming leaves the task in a suspended state indefinitely, and leaks any associated resources. CheckedContinuation logs a message if either of these invariants is violated.
Excerpt taken from the documentation for CheckedContinuation (emphasis mine).
Here are possible causes for this to happen:
not all code paths resume the continuation, e.g. there is an if/guard/case that exits the scope without instructing the continuation to report success/failure
class Searcher {
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
someFunctionCall(withCompletion: { [weak self] in
guard let `self` = self else {
// if `result` doesn't have the expected value, the continuation
// will never report completion
return
}
continuation.resume(returning: something)
})
}
}
}
an "old"-style async function doesn't call the completion closure on all paths; this is a less obvious reason, and sometimes a harder to debug one:
class Searcher {
private let internalSearcher = InternalSearcher()
func search(for query: String) async throws -> [String] {
await withCheckedContinuation { continuation in
internalSearcher.search(query: query) { result in
// everything fine here
continuation.resume(returning: result)
}
}
}
}
class InternalSearcher {
func search(query: String, completion: #escaping ([String]) -> Void {
guard !query.isEmpty else {
return
// legit precondition check, however in this case,
// the completion is not called, meaning that the
// upstream function call will imediately discard
// the continuation, without instructing it to report completion
}
// perform the actual search, report the results
}
}
the continuation is stored as a property when a function is called; this means that if a second function call happens while the first one is in progress, then the first completion will be overwritten, meaning it will never report completion:
class Searcher {
var continuation: CheckedContinuation<[String], Error>?
func search(for query: String) async throws -> [String] {
try await withCheckedTrowingContinuation { continuation in
// note how a second call to `search` will overwrite the
// previous continuation, in case the delegate method was
// not yet called
self.continuation = continuation
// trigger the searching mechanism
}
}
func delegateMethod(results: [String]) {
self.continuation.resume(returning: results)
self.continuation = nil
}
}
#1 and #2 usually happen when dealing with functions that accept completion callbacks, while #3 usually happens when dealing with delegate methods, since in that case, we need to store the continuation somewhere outside the async function scope, in order to access it from the delegate methods.
Bottom line - try to make sure that a continuation reports completion on all possible code paths, otherwise, the continuation will indefinitely block the async call, leading to the task associated with that async call leaking its associated resources.
In your case, what likely happened is that a second query() call occurred before the first call had a chance to finish.
And in that case, the first continuation got discarded without reporting completion, meaning the first caller never continued the execution after the try await query() call, and this is not ok at all.
The following piece of code needs to be fixed, in order not to overwrite a pending continuation:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
completionContinuation = continuation
One quick solution would be to store an array of continuations, resume all continuations in the delegate methods, and clear the array afterward. Also, in your specific case, you could simply extract the validation out of the continuation code, as you are allowed to synchronously return/throw, even when in an async function:
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
guard !value.isEmpty else {
return []
}
return try await withCheckedThrowingContinuation { continuation in
continuations.append(continuation)
completer.queryFragment = value
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
continuations.forEach { $0.resume(returning: completer.results) }
continuations.removeAll()
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
continuations.forEach { $0.resume(throwing: error) }
continuations.removeAll()
}
I'd also strongly recommend converting your class to an actor, in order to avoid data races, regardless if you store one continuation, like now, or you use an array. The reason is that the continuation property is consumed from multiple threads and at some point you might end up with two threads concurrently accessing/writing the property.
I think the problem is here -
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
// storing into a variable makes this continuation instance outlive the scope of it
// In other words, it leaks OR escapes the scope
// This is same as why we need to add #escaping attribute for callback functions arguments
// those are either stored in variables like this
// or passed to other functions (escaping scope of current function)
completionContinuation = continuation
// Try commenting above line, the warning should go away
// And your code will stop working as well :)
// How to design this component is other question.
}
}
UPDATE
import MapKit
class LocalSearch: NSObject, MKLocalSearchCompleterDelegate {
typealias Completion = (_ results: [MKLocalSearchCompletion]?, _ error: Error?) -> Void
private let completer: MKLocalSearchCompleter
private var completion: Completion?
override init() {
completer = MKLocalSearchCompleter()
super.init()
completer.delegate = self
}
func query(_ value: String, completion: #escaping Completion) {
self.completion = completion
completer.queryFragment = value
}
func query(_ value: String) async throws -> [MKLocalSearchCompletion] {
try await withCheckedThrowingContinuation { continuation in
guard !value.isEmpty else {
continuation.resume(returning: [])
return
}
self.query(value, completion: { (results, error) in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: results ?? [])
}
})
}
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
completion?(completer.results, nil)
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
completion?(nil, error)
}
}
As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM
Im getting this error "Class declaration cannot close over value 'viewcontainer' defined in outer scope"
I created a procotol called NetworkResponse which have two methods on sucessResponse and onErrorResponse.
Then I have a class called Callback that extends from NetworkResponse and forced to implement that methods.
Here is my function :
public func login (callback : Callback, viewController : UIViewController) {
let callbackInstance: NetworkResponse = {
class callback : Callback {
override func onSucessResponse(response : NSDictionary){
viewController.dismiss(animated: true, completion: nil)
}
override func onErrorResponse(message : String, code : Int){
print("error")
}
}
return callback()
}()
postPath(callback: callbackInstance as? Callback)
}
I want to dismiss the controller from the anonymous class.
Any recomendation ?
No need to define a protocol and Callback class. Closure is just what you need.
import UIKit
public class TestInnerClass: UIViewController {
public func login(successCallback: ((response: NSDictionary) -> Void), errorCallback: ((message: String, code: Int) -> Void)) {
let success = false
let response = NSDictionary()
//
// Make your login request here, and change the `success` value depends on your response
// let response = ...
//
// If you are making a async request to login, then put the following codes inside your request callback closure.
//
if success {
successCallback(response: response)
} else {
errorCallback(message: "error occurred", code: -1)
}
}
override public func viewDidLoad() {
super.viewDidLoad()
login({
(response) in
// Get Called when success
self.dismissViewControllerAnimated(true, completion: nil)
}, errorCallback: ({
// Get called when failed
(message, code) in
print(message)
}))
}
}
I have written some sample codes for your case in my Github repo, and this example is using Alamofire to make network request, Just for your reference.
PS: Since I still using Xcode 7.3.1, so you may need to do some changes to the above code to adopt the swift 3 syntax requirement.
The The Swift Programming Language documentation states:
Define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time.
So the only scenario that come to my mind is this one:
class ClassWithClosure {
lazy var someClosure: () -> String = {
[unowned self] in
self.myMethod()
}
func myMethod(){
}
}
Are there more scenarios where the closure and the instance will be deallocated at the same time?
Anytime ClassWithClosure instance's reference count hits 0, the instance will be deallocated. Since the instance is getting deallocated, its properties would be deallocated as well. So anytime ClassWithClosure instance is deallocated, the closure would be deallocated as well. You can't tell the same for other way around. What I mean is if you set someClosure to nil, then it won't cause deallocation of the ClassWithClosure instance.
I'm late. Just now looking into these matters for myself. How about this?
protocol Notifier {
typealias Client = (Data) -> Void
typealias Token = Any
func register(client: Client) -> Token
// The previously registered Consumer is discarded
func deregister(token: Token)
}
class Registrant {
private let name: String
private let notifier: Notifier
private var token: Notifier.Token? = nil
init(name: String, notifier: Notifier) {
self.name = name
self.notifier = notifier
token = notifier.register { [unowned self] (data: Data) in
print("\(self.name) has been notified: \(data)")
}
}
deinit {
if let token = token {
notifier.deregister(token: token)
}
}
}