Swift. Collecting data from different queries

Foreword

The material is an example that demonstrates the capabilities of the language and allows a deeper understanding of the work of various frameworks (for example, Combine) that make working with the network easier.

The material is intended for novice developers for general information.

All examples in this publication are extremely simplified versions of the code and should not be used when developing applications as they are. All operations performed with the Swift language must be safe and subject to checks.

All my information trash, I collect on my wall in VCso welcome.

Let’s imagine a typical situation. Our application needs to connect to the server and receive various data. For example, let it be: a list of categories, a list of products, the cost of products. It doesn’t matter, it’s just some abstract task.

We cannot collect this data for a number of specific reasons within the framework of one request, therefore our task is to fulfill several requests and after all of them are completed (we receive the data), we will take some action. It is impossible to perform actions before receiving all the data, the procedure for obtaining data is unknown to us. these are asynchronous operations and the amount of data is as unknown as the network speed, etc. I think it needs no explanation.

Let’s think about what we will do to implement. Of course, the first thought is to google and copy-paste the code with the stackoverlow, but let’s solve the problem ourselves. Moreover, solving it, we will not use boxed solutions from Apple. We need to do this within the capabilities of the language using URLSession… Let’s create a solution that meets our requirements.

Well, let’s start. Let’s create a class MyHttpExample

class MyHttpExample {
    
}

And add a method to it request

func request(){}

method will be open

public func request(){}

and will take two arguments

public func request(url urlString: String, key: String){}

link type String and a key, the purpose of which you will understand a little later.

In the method we will use URLSession to send requests, but first we need a convenient data type that satisfies our requests, so we will create in the class MyHttpExample data type Wrapper

struct Wrapper {
	let response: URLResponse
	let data: String
}

since this is an example, we will not receive the real Data, but we will do with its imitation, URLResponse Let’s take it to make sure that the code is performing its tasks. Now back to the method request

public func request(url urlString: String, key: String) -> MyHttpExample {
    guard let url = URL.init(string: urlString) else { return self }
    let session = URLSession.shared
    let task = session.dataTask(with: url){ (_, response, _) in
        //различные проверки
    }
    task.resume()
    return self
}

I suppose there is nothing special to explain, we form the task and process the completion. Why the method returns the current instance (we will use instances), you will understand a little later. Now let’s add a dictionary that will store our data and a request counter.

private var count = 0
public var dictonary: [String: Wrapper] = [:]

we will use the newly created dictionary as an observer.

public var dictonary: [String: Wrapper] = [:] { didSet {} }

We will also add a method that will execute an observer and a property containing closures in an array, which we will pass to requests

private var completions: [() -> Void]? = []
    
private func done(){}

we make the property optional since to retrieve data from the dictionary, we will use the passed closure, into which we will pass the instance of the class to which we previously passed the closure, and since classes and closures represent a reference data type, this will lead to a memory leak (closed reference loop), but we will pass it to it ourselves nil upon completion, which will allow the garbage collector to get rid of the unneeded instance.

Next, let’s create a method for adding closures to an array.

public func addClosure(_ closure: @escaping () -> Void) -> MyHttpExample{
    self.completions?.append {
        closure()
    }
    return self
}

Now we can set the behavior of the observer by checking the amount of data in the dictionary with the request counter, which we will increase with each call of the method request

public var dictonary: [String: Wrapper] = [:] {
    didSet {
        if dictonary.count == self.count {
            self.done()
        }
    }
}

describe the method done

private func done(){
    for completion in completions ?? [] {
        completion()
    }
    completions = nil
}

and improve the method request

public func request(url urlString: String, key: String) -> MyHttpExample {
    self.count += 1
    guard let url = URL.init(string: urlString) else { return self }
    let session = URLSession.shared
    let task = session.dataTask(with: url){ (_, response, _) in
        //различные проверки
        self.dictonary[key] = Wrapper(response: response!, data: key)
    }
    task.resume()
    return self
}

as the date we pass the key, for the example this will be enough.

note

we do not check response and forcibly fetching, not handling errors, etc. since this is an example, therefore, you should not use this code without modifications

As a result, we will get the following code.

class MyHttpExample {
    
    private var count = 0
    public var dictonary: [String: Wrapper] = [:] {
        didSet {
            if dictonary.count == self.count {
                self.done()
            }
        }
    }
    
    private var completions: [() -> Void]? = []
    
    private func done(){
        for completion in completions ?? [] {
            completion()
        }
        completions = nil
    }
    
    public func addClosure(_ closure: @escaping () -> Void) -> MyHttpExample{
        self.completions?.append {
            closure()
        }
        return self
    }
    
    public func request(url urlString: String, key: String) -> MyHttpExample {
        self.count += 1
        guard let url = URL.init(string: urlString) else { return self }
        let session = URLSession.shared
        let task = session.dataTask(with: url){ (_, response, _) in
            //различные проверки
            self.dictonary[key] = Wrapper(response: response!, data: key)
        }
        task.resume()
        return self
    }
    
    struct Wrapper {
        let response: URLResponse
        let data: String
    }
}

Now let’s check it out. First, to receive a response

add a deinitializer to keep track of the state of the instance in the future, and now let’s indulge

I have compressed the code a little (ignoring indents) to fit on the screen

Behavior meets expectation.

Now we will add a delay element to make sure that the instance is destroyed, as well as see if the closure works after deinitialization, in fact, for this we will run the code as part of the application on the device.

Xcode swears because cannot calculate the result of the code.

The actual code presented should be enough to understand the general concept, with certain modifications it can be used in small projects. Of course, we need to make it more convenient to declare an instance out of our necessity, wrap it all in a container, handle various situations, etc. But that is another story.

See you later …

Similar Posts

Leave a Reply