How can a closure be used to only complete when data is loaded? (swift) - swift

I'm slowly coming to terms with closures.
This is taken from the following post:
https://medium.com/#stasost/ios-three-ways-to-pass-data-from-model-to-controller-b47cc72a4336
I understand the function definition requestData is taking a closure which is called with completion(data):
class DataModel {
func requestData(completion: ((data: String) -> Void)) {
// the data was received and parsed to String
let data = "Data from wherever"
completion(data)
}
}
class ViewController: UIViewController {
private let dataModel = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataModel.requestData { [weak self] (data: String) in
self?.useData(data: data)
}
}
private func useData(data: String) {
print(data)
}
}
While I understand requestData is being called in viewDidLoad below and that (data:String) is being passed into requestData I don't quite get what is being done when completion(data) is being called.
Is completion(data) executing the code that is happening after the keyword "in"?
{ [weak self] (data: String) in
self?.useData(data: data)
}
And I had a question about the order in which things are happening. Is it:
a) let data = "Data from wherever"
b) completion(data)
c) self?.useData(data: data)
In the current app I'm working on when the user starts the app I make an api call to load data. But i'm still a tiny bit unsure about how to tell the ViewController that the data has finished loading.
Thanks.

You understand that a function can take, say, an Int as parameter?
func f(_ i:Int) {
// do something with i
}
Well, a function can also take a function as parameter:
func requestData(completion: ((data: String) -> Void)) {
// do something with completion
}
In that declaration, completion is a function — a function that takes one parameter, called data.
What can you do when you receive a function as a parameter? One obvious thing to do with it would be to call it:
func requestData(completion: ((data: String) -> Void)) {
completion(data: "well howdy there")
}
It remains only to talk about the syntax of passing the desired function to requestData as its completion parameter. Here is one way:
func output(data: String) {
print(data)
}
requestData(completion:output)
Here's a nicer way, one that avoids giving the passed function a name:
requestData(completion:{
data in
print(data)
})
Finally, since completion is the only parameter, we are allowed to use "trailing closure" syntax — we delete the completion: label and the parentheses of the call:
requestData {
data in
print(data)
}

Related

executeJavascript does not call completionHandler when inside a DispatchQueue

I've written a function that's supposed to return the HTML string that makes up a WKWebview. However, the completion handler is never called, and the project freezes indefinitely. I've also already adopted the WKScriptMessageHandler protocol so that's not the problem.
public func getHTML() -> String {
var result = ""
let group = DispatchGroup()
group.enter()
DispatchQueue.main.async {
self.webView.evaluateJavaScript("document.documentElement.outerHTML.toString()", completionHandler: {(html: Any?, error: Error?) in
if (error != nil) {
print(error!)
}
result = html as! String
group.leave()
})
}
group.wait()
print("done waiting")
return result
}
I've found several examples on how to get the html, like here, but I don't want to merely print, I want to be able to return its value. I'm not experienced with DispatchQueues, but I do know for that WKWebView's evaluateJavaScript completion handler always runs on the main thread

How to get data from NSDocument's read method

I'm having difficulty trying to put 'data' onto NSPasteboard. By 'data', I mean a type other than the specific PasteboardType formats: text, HTML, image, etc. (It's actually MIDI data.)
override func copy() -> Any {
let pboard = NSPasteboard.general
pboard.clearContents()
pboard.setData(data, forType: .typeMidi)
return true
}
When I try to put my data in, I get:
Cannot convert value of type '(String) throws -> Data' to expected element type 'NSPasteboardWriting'.
That's probably because I've been trying to use NSDocument's data method, but it turns out that's only for writing out to disk.
The data needs to come from the read function:
override func read(from data: Data, ofType typeName: String) throws {
self.theMIDIPlayer = try AVMIDIPlayer.init(data: data, soundBankURL: nil)
if self.theMIDIPlayer == nil {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}
But it seems that the read function's arguments are not NSDocument's data function (which only relates to writing). I've no idea where the arguments come from.
Adding something like self.myData = data to the read function (in an attempt to get the data in a useful property) produce "Expected pattern" errors.
SOLVED: The problem was a schoolboy error of using copy() instead of copy(_:).
Vadian's now deleted answer was helpful in creating a custom Pasteboard type.
The complete (relevant) code is as follows:
extension NSPasteboard.PasteboardType {
static let typeMidi = NSPasteboard.PasteboardType(rawValue: "public.midi-audio")
}
class Document: NSDocument {
var theMIDIPlayer: AVMIDIPlayer?
var myData: Data?
#IBAction func copy(_: Any) {
let pboard = NSPasteboard.general
pboard.clearContents()
pboard.setData(myData, forType: .typeMidi)
}
override func read(from data: Data, ofType typeName: String) throws {
self.theMIDIPlayer = try AVMIDIPlayer.init(data: data, soundBankURL: nil)
self.myData = data
if self.theMIDIPlayer == nil {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}

Difference between a Callback and Competition Handler in Swift

In the Combine framework, I have found following text
The Combine framework provides a declarative approach for how your app
processes events. Rather than potentially implementing multiple
delegate callbacks or completion handler
Can somebody tell me what is the difference between completion handler and callback in Swift?
A delegate callback is when you have a delegate that you know in advance implements a method (e.g. because it adopts a protocol), and you call that method by name.
A completion handler is when someone hands you a function and you just call it blindly by reference.
to be clear actually you can achieve the same functionality with both ways however the there are completely different approach for designing your app
let me clarify with simple example the difference between both with the same function is making network call
delegate protocol
// enum to define the request type
enum RequestTypes {
case UserRegister
case UserLogin
}
protocol ServiceDelegate {
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes)
}
// you can also add default impl to the methods here
extension ServiceDelegate {
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes){}
}
class BaseService<ResponseModel: Codable> {
var session: URLSession!
var delegate: ServiceDelegate?
// MARK: Rebuilt Methods
func FireRequest(){
// Request Preparation
let serviceUrl = URL(string: /* your url */)!
var request = URLRequest(url: serviceUrl)
request.httpMethod = "GET"
// Firing the request
session = URLSession.init(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {/* handle error or call delegate error method here */ return }
delegate?.didCompleteRequest(responseModel: object, tag: .UserLogin)
}
}
}.resume()
}
}
class ViewController: UIViewController, ServiceDelegate {
override func viewDidLoad() {
super.viewDidLoad()
fetchNewData()
}
func fetchNewData(){
let service = BaseService<YourModel>()
service.delegate = self
service.FireRequest()
}
func didCompleteRequest(responseModel: AnyObject, tag: RequestTypes) {
if tag == /* the tag you are waiting */ .UserLogin {
// YourModel is available here
}
}
}
completion handler
class BaseService<ResponseModel: Codable> {
var session: URLSession!
// MARK: Rebuilt Methods
func FireRequest(completion: ((ResponseModel?) -> Void)?){
// Request Preparation
let serviceUrl = URL(string: /* your url */)!
var request = URLRequest(url: serviceUrl)
request.httpMethod = "GET"
// Firing the request
session = URLSession.init(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
guard let object = try? JSONDecoder().decode(ResponseModel.self , from: data) else {/* handle error or call delegate error method here */ return }
DispatchQueue.main.async {
completion?(object)
}
}
}
}.resume()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
fetchNewData()
}
func fetchNewData(){
let service = BaseService<YourModel>()
service.FireRequest(completion: { [weak self] (response) in
// yourModel Available here once the request completed
})
}
}
A delegate callback is one to one communication between various ViewControllers and classes. It basically lets you know that a particular change has been done in particular view or any where else and now you can make change after this action.
While completion handler is a block executed after completing a particular process or task.
Callback is a way to sending data back to some other function on some particular occasion. there are 2 ways to implement callbacks in swift.
Using Protocols / Delegate
Using Completion Handler
Using Protocols / Delegate Example:
Declare Protocol
protocol MyDelegate {
public method(param: String);
}
Your ViewController should extend the delegate
class YourViewController: MyDelegate {
// Your Other methods
func method(param: String) {
// Do your stuff
}
}
Now in your other classes you can send callback to ViewController through delegate object like
delegate.method(param: "your_param");
Using Completion Handler Example:
public func method(param: String, completionHandler: #escaping (_ param: String) -> Void)
{
...
// now you can send data back to the caller function using completionHandler on some particular occasion
completionHandler("param");
}
We can call this function like
method(param: String, completionHandler: { (result, alreadyUserId) in
// here you will receive callback
});
Callbacks and Completion Handlers are synonymous when referring to asynchronous methods.
I’ve found the main difference being in how its used in defining what’s returned to the caller where a callback is used when referring to a method where the scope is returned to the previous calling method and a completion handler refers to a method when it returns some Result type to the caller.

Function in completion-block is never executed

I have the following situation:
Classes involved are
ViewController (of type UIViewController)
RequestHandler
DataRequest (imports Alamofire for the request)
Step 1)
override func viewDidLoad() {
super.viewDidLoad()
requestHandler.getData(view: self)
}
So here, my instance of the RequestHandler class is calling its getData()-function.
Step 2)
func getData(view: ViewController) {
let dataRequest = DataRequest()
dataRequest.getDataTree(handler: self) { response in
view.saveData(dataTree: reponse)
}
}
In this getData method inside my class RequestHandler i'm creating an instance of the desired Request-class. In this case an instance of the class DataRequest. This instance then class its function getDataTree() and handles the response over to my ViewController by invoking the saveData()-function.
Step 3)
import Foundation
import SwiftyJSON
import Alamofire
class DataRequest {
func getDataTree(handler: RequestHandler, completion: #escaping ([String:[String:[String:[SomeStruct]]]]) -> ()) {
let requestURL = constants.Url.dataURL
Alamofire.request(requestURL, method: .post, encoding: URLEncoding.default)
.validate()
.responseJSON(completionHandler: { response in
completion(self.getServices(jsonData: JSON(data: response.data!)))
})
}
private func getServices(jsonData: JSON) -> [String:[String:[String:[SomeStruct]]]] {
var serviceDict: [String:[String:[String:[SomeStruct]]]] = [:]
for (k, subJson):(String, JSON) in jsonData {
let key = k
let dict: [String:[String:[SomeStruct]]] = getNames(jsonData: subJson)
serviceDict[key] = dict
}
return serviceDict
}
..followed by declaration of getNames-function etc until in the end, the SomeStruct objects in the array get parsed too.
So now that you got the background-information, my problem is the following:
By debugging I found out that the function getDataTree is called and executed, but the getServices-function never executes, despite being called in the completion-block.
I've only started as a programmer less than a year ago, so my experience is limited. I might aswell just make a really trivial mistake.
Thanks alot for your help already!

Unit Test HKSampleQuery in Swift

When I need to read data from HealthKit this is how my code looks like:
let stepsCount = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let stepsSampleQuery = HKSampleQuery(sampleType: stepsCount,
predicate: nil,
limit: 100,
sortDescriptors: nil)
{ [unowned self] (query, results, error) in
if let results = results as? [HKQuantitySample] {
self.steps = results
// Update some UI
}
self.activityIndicator.stopAnimating()
}
healthStore?.executeQuery(stepsSampleQuery)
This specific code was extracted from here for demo purpose.
So my question is:
How can I unit test this kind of code ?
I encapsulate this code in a function in a model class that knows nothing about the UI. It works like this:
At the place the you have your
// Update some UI
call a completion closure, that was passed to the function using a parameter.
You call this function from your controller class like this
hkModel.selectSteps() {
[unowned self] (query, results, error) in
// update UI
}
This way you have a clean separation between your query logic in the model class and your UIController code.
Now you can easily write a unit test calling the same method:
func testSteps() {
hkModel.selectSteps() {
[unowned self] (query, results, error) in
// XCTAssert(...)
}
}
The last thing you need is to respect that your test code is called asynchronously:
let stepExpectationEnd = expectationWithDescription("step Query")
hkModel.selectSteps() {
[unowned self] (query, results, error) in
// XCTAssert(...)
stepExpectationEnd.fulfill()
}
waitForExpectationsWithTimeout(10.0) {
(error: NSError?) in
if let error = error {
XCTFail(error.localizedDescription)
}
}
update
Because you asked:
I handle authorization at the test setup. looks like this:
var healthData: HealthDataManager?
override func setUp() {
super.setUp()
healthData = HealthDataManager()
XCTAssert(healthData != nil, "healthDadta must be there")
let authorizationAndAScheduleExpectation = expectationWithDescription("Wait for authorizatiion. Might be manual the first time")
healthData?.authorizeHealthKit({ (success: Bool, error: NSError?) -> Void in
print ("success: \(success) error \(error?.localizedDescription)")
// fails on iPad
XCTAssert(success, "authorization error \(error?.localizedDescription)")
self.healthData?.scheduleAll() {
(success:Bool, error:ErrorType?) -> Void in
XCTAssert(success, "scheduleAll error \(error)")
authorizationAndAScheduleExpectation.fulfill()
}
})
waitForExpectationsWithTimeout(60.0) {
error in
if let error = error {
XCTFail(error.localizedDescription)
}
}
}
The first time you run this code in a simulator, you have to approve authorization manually.
After the first run the tests run without manual intervention.