Delegate method not called using Singleton - delegates

Similar questions have already been asked but they haven't helped. I'm trying to call protocol method declared in Singleton class from a view controller but the called method never invokes.
My protocol and class are:
import Foundation
protocol ServiceDelegate {
func serviceData(data: [String: Any])
}
class Service {
private static let sharedInstance = Service() // singleton object
var delegate: ServiceDelegate?
class func shared() -> Service {
return sharedInstance
}
func login(postData:String) {
let url = URL(string: "MY_URL")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let dataBody = postData.data(using:String.Encoding.ascii, allowLossyConversion: false)
request.httpBody = dataBody
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let dataArray = responseJSON as? [String: Any] {
self.delegate?.serviceData(data: dataArray) // This is getting called
}
}
task.resume()
}
}
My view controller class where protocol is implemented is:
import UIKit
class LoginVC: UIViewController, ServiceDelegate {
var serviceInstance: Service = Service()
override func viewDidLoad() {
super.viewDidLoad()
serviceInstance.delegate = self
}
#IBAction func loginBtn(_ sender: Any) {
let info = "userName=\(String(describing: usernameText.text!))&password=\(String(describing: passwordText.text!))"
Service.shared().login(postData: info)
}
// This is not getting called:
func serviceData(data: [String : Any]) {
print(data)
}
Is the above implementation correct? Can I use private singleton object to successfully implement protocol method?

You are creating two Service instances. You should only be using the one returned by Service.shared(). That’s the actual singleton shared across your app.
In your view controller, replace this line:
serviceInstance.delegate = self
with:
Service.shared().delegate = self
And you should also delete this line (i.e., the unnecessary second instance):
var serviceInstance: Service = Service()

Related

Swift 3, URLSession dataTask completionHandler not called

I am writing a library, So not using UIKit, Even in my iOS app same code works, but when i execute in command line in doesn't . In PlayGround also it seems working.
For some reason callback is not getting triggered, so print statements are not executing.
internal class func post(request: URLRequest, responseCallback: #escaping (Bool, AnyObject?) -> ()) {
execTask(request: request, taskCallback: { (status, resp) -> Void in
responseCallback(status, resp)
})
}
internal class func clientURLRequest(url: URL, path: String, method: RequestMethod.RawValue, params: Dictionary<String, Any>? = nil) -> URLRequest {
var request = URLRequest(url: url)
request.httpMethod = method
do {
let jsonData = try JSONSerialization.data(withJSONObject: (params! as [String : Any]), options: .prettyPrinted)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
} catch let error as NSError {
print(error)
}
return request
}
private class func execTask(request: URLRequest, taskCallback: #escaping (Bool,
AnyObject?) -> ()) {
let session = URLSession(configuration: URLSessionConfiguration.default)
print("THIS LINE IS PRINTED")
let task = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
if let data = data {
print("THIS ONE IS NOT PRINTED")
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
}
})
task.resume()
}
Edits -: I am writing a library, So not using UIKit, Even in my iOS app same code works, but when i execute in command line in doesn't . In PlayGround also it seems working.
I made a simple App from scratch. (Xcode 8 beta 6 / swift 3)
In controller I pasted Your code. (plus url creation..)
I see all in debugger:
THIS ONE IS PRINTED
THIS ONE IS PRINTED, TOO
I AM BACK
so it seems workin.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let URLString = "https://apple.com"
let url = URL(string: URLString)
let request = URLRequest(url: url!)
ViewController.execTask(request: request) { (ok, obj) in
print("I AM BACK")
}
}
private class func execTask(request: URLRequest, taskCallback: #escaping (Bool,
AnyObject?) -> ()) {
let session = URLSession(configuration: URLSessionConfiguration.default)
print("THIS LINE IS PRINTED")
let task = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
if let data = data {
print("THIS ONE IS PRINTED, TOO")
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
}
})
task.resume()
}
}
I know its late for the answer but in case you have not figure out the issue or getting issue at other places, lets try this.
You need to save session variable outside method scope (make it a instance variable). Since you defined it locally in function scope. Its get deallocated before completion handler can be called, remember completion handler can't retain your session object and after execution of run loop, garbage collector will dealloc your session object. We need to retain such objects whenever we want call back from delegates or from completion handler..
self.session = URLSession(configuration: URLSessionConfiguration.default)
Did the changes suggested here, It works now.
Using NSURLSession from a Swift command line program
var sema = DispatchSemaphore( value: 0 )
private func execTask(request: URLRequest, taskCallback: #escaping (Bool,
AnyObject?) -> ()) {
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil )
session.dataTask(with: request) {(data, response, error) -> Void in
if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
}
}.resume()
sema.wait()
}
let dataTask = session.dataTask(with: request, completionHandler: {data, response,error -> Void in
print("Request : \(response)")
let res = response as! HTTPURLResponse
print("Status Code : \(res.statusCode)")
let strResponse = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Response String :\(strResponse)")
})
dataTask.resume()
Swift 3.0
Just copy below code into your view controller.
#IBAction func btnNewApplicationPressed (_ sender: UIButton) {
callWebService()
}
func callWebService() {
// Show MBProgressHUD Here
var config :URLSessionConfiguration!
var urlSession :URLSession!
config = URLSessionConfiguration.default
urlSession = URLSession(configuration: config)
// MARK:- HeaderField
let HTTPHeaderField_ContentType = "Content-Type"
// MARK:- ContentType
let ContentType_ApplicationJson = "application/json"
//MARK: HTTPMethod
let HTTPMethod_Get = "GET"
let callURL = URL.init(string: "https://itunes.apple.com/in/rss/newapplications/limit=10/json")
var request = URLRequest.init(url: callURL!)
request.timeoutInterval = 60.0 // TimeoutInterval in Second
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData
request.addValue(ContentType_ApplicationJson, forHTTPHeaderField: HTTPHeaderField_ContentType)
request.httpMethod = HTTPMethod_Get
let dataTask = urlSession.dataTask(with: request) { (data,response,error) in
if error != nil{
return
}
do {
let resultJson = try JSONSerialization.jsonObject(with: data!, options: []) as? [String:AnyObject]
print("Result",resultJson!)
} catch {
print("Error -> \(error)")
}
}
dataTask.resume()
}
Sometimes, for me, the solution when completionHandler were not called in these cases was because the flag "Allow Arbitrary loads" on Info.plist was defined as NO.
Allow Arbitrary loads flag defined as YES

CoreLocation can't use with Estimotes Beacon

I build a app with Estimotes indoor Location ,any I use CoreLocation to find our beacon, and my app will change label.text. App can run,but didn't work ,label.text can't change.
override func viewDidLoad() {
super.viewDidLoad()
let path = NSBundle.mainBundle().pathForResource("location", ofType: "json")
do {
let content = try String(contentsOfFile: path!, encoding: NSUTF8StringEncoding)
let locationSetup = ESTLocationBuilder.parseFromJSON(content)
//set up delegate
manager.delegate = self
locationManager.delegate = self
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.AuthorizedWhenInUse) {
locationManager.requestWhenInUseAuthorization()
}
locationManager.startRangingBeaconsInRegion(region)
}
func locationManager(manager: CLLocationManager, didRangeBeacons beacons:[CLBeacon], inRegion region: CLBeaconRegion){
let knownBeacons = beacons.filter{ $0.proximity != CLProximity.Unknown }
if (knownBeacons.count > 0){
let closestBeacon = knownBeacons[0] as CLBeacon
if(closestBeacon.minor.integerValue == 41016)
{
self.snow.text = "find"
}
}
}
First thing to try:
I would move the initialization of locationManger = CLLocationManager() outside the class instance declaration and inside viewDidLoad. I have seen problems initializing the location manager during object construction.

Accessing PFObject Subclassed Properties from a Query

I am using Parse and have created a subclass of PFObject. When creating objects it makes things much easier. Once objects are created, I am experimenting with querying the database and accessing the custom properties I created. What I am finding is that I cannot use dot notation to access the properties when I am working the the PFObjects returned from the query. Is this normal?
Here is subclass I created.
import Foundation
import UIKit
import Parse
class MessagePFObject: PFObject
{
#NSManaged var messageSender : String
#NSManaged var messageReceiver : String
#NSManaged var messageMessage : String
#NSManaged var messageSeen : Bool
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Custom Query method.
override class func query() -> PFQuery?
{
let query = PFQuery(className: MessagePFObject.parseClassName())
query.includeKey("user")
query.orderByDescending("createdAt")
return query
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
init(messageSenderInput: String?, messageReceiverInput: String?, messageMessageInput: String?)
{
super.init()
self.messageSender = messageSenderInput!
self.messageReceiver = messageReceiverInput!
self.messageMessage = messageMessageInput!
self.messageSeen = false
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
override init()
{
super.init()
}
}
//++++++++++++++++++++++++++++++++++++ EXTENSION +++++++++++++++++++++++++++++++++++++++++++++++++++
extension MessagePFObject : PFSubclassing
{
class func parseClassName() -> String
{
return "MessagePFObject"
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
override class func initialize()
{
var onceToken: dispatch_once_t = 0
dispatch_once(&onceToken) {
self.registerSubclass()
}
}
}
Here is my query and what I am required to do to access the properties. createdAt, updatedAt, etc are all available with dot notation but none of my custom properties are. You can see I access messageSeen with element.objectForKey("messageSeen").
let predicate = NSPredicate(format: "messageSender == %# OR messageReceiver == %#", self.currentUser!.username!, self.currentUser!.username!)
let query = messagePFObject.queryWithPredicate(predicate)
query!.findObjectsInBackgroundWithBlock ({ (objects, error) -> Void in
if error == nil && objects!.count > 0
{
for element in objects!
{
print(element)
print(element.parseClassName)
print(element.objectId)
print(element.createdAt)
print(element.updatedAt)
print(element.objectForKey("messageSeen"))
}
}
else if error != nil
{
print(error)
}
})
If this is normal then that is fine. I just want to make sure I am not missing something.
Take care,
Jon
Your object subclass has to implement the PFSubclassing protocol and you need to call MessagePFObject.registerSubclass() in your app delegate.
The parse documentation is very good : https://parse.com/docs/ios/guide#objects-subclasses

Swift - Pass a reference to a class

I have a class which will perform a search. Once complete I want the search class to pass back the results to which ever instance (of another class) started the search. My thinking was to pass a reference to the class which instantiates the search class and use that reference to call a function. Here's a basic example of what I'm trying to do. How can I get this to work, or is there another/better way?
Search Class (I've tried AnyObject and UITableViewContoller):
class SearchClass : NSObject, NSURLConnectionDelegate, NSURLConnectionDataDelegate {
var callingClass : AnyObject? = nil //To store reference to the "other" class
var searchResults : [[String : AnyObject]]? = nil
init(callingClass: AnyObject) { //I don't know the name of the ViewController class which will instantiates this as it will be called by several different classes
self.callingClass = callingClass
}
func startSearch(searchString: String) {
//NSURLConnection etc
}
func connectionDidFinishLoading(connection: NSURLConnection) {
//more code
searchResults = ...
callingClass!.searchCompleted(searchResults) //Pass the search results back to the class instance which started the search
}
}
Other Classes:
class RandomViewController : UITableViewController {
//The casting on the next line fails
let Searcher = SearchClass(callingClass: self as! UITableViewController) //OR AnyObject
func randomFunction() {
searcher.startSearch("search query")
}
func searchComplete(searchResults: [[String : AnyObject]]) {
self.searchResults = searchResults
tableView.reloadData()
}
}
You can add a closure parameter to your startSearch function in your search class:
typealias SearchResultHandler = (result: [String : AnyObject]) -> ()
func startSearch(query: String, resultHandler: SearchResultHandler) {
// send the search request and in the completion handler of the request call your result handler:
resultHandler(result: searchResult)
}
Which you would then call from any class:
let searcher = SearchClass()
searcher.startSearch("query") { (result) -> () in
self.searchResults = results
tableView.reloadData()
}
You can use generics when you don't know what the class is going to be:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html
However....
This seems like something that you should use protocol / delegates for.
// NOTE: weak references must be class type
protocol SearchClassDelegate: class {
func searchComplete(results: [[String: AnyObject]?])
}
// This is where you define T as the generic class
class SearchClass {
weak var delegate: SearchClassDelegate? // This is your "callingClass"
// NOTE: make sure it's weak
// ...
func connectionDidFinishLoading() {
// ...
self.delegate?.searchComplete(results)
}
}
// Set to the delegate
class ViewController: UIViewController, SearchClassDelegate {
// ...
// Make sure you set the delegate
// Here is where you implement this function
func searchComplete(results: [[String : AnyObject]?]) {
// Do whatever
// ...reloadData() etc.
}
}

Can someone explain dispatch_async() for me in this scenario

I had the following method in a separate class:
class API: NSObject {
var data = NSData()
var delegate: APIProtocol?
func getItems(callback: (Array<Image>) -> ()) {
let urlPath: NSString = "http://localhost:3000/files"
let url = NSURL(string: urlPath)
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
let config = NSURLSessionConfiguration.defaultSessionConfiguration() as NSURLSessionConfiguration
let session = NSURLSession(configuration: config) as NSURLSession
var dataTask = NSURLSessionDataTask()
dataTask = session.dataTaskWithRequest(request) { (data, response, error) in
if (error == nil) {
println("API at URL \(url)")
let responseArray = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: nil) as NSArray
var images = Image[]()
for item: AnyObject in responseArray {
var location = Image(dict: item as NSDictionary)
images.append(location)
}
var img = images[0] as Image
callback(images)
//self.delegate?.didReceiveResponse(responseArray)
}
}
dataTask.resume()
}
}
I couldn't get my tableView to reload when calling self.tableView.reloadData() inside the callback() until I added the dispatch_async() around it.
My questions are:
1) Why wouldn't it work without it and is it the proper thing for me to do now that it's refreshing the tableView correctly?
2) Is there another way to get it working without having to add the dispatch on the main queue?
api.getItems() { (theArray) in
dispatch_async(dispatch_get_main_queue(), {
self.images = theArray
self.tableView.reloadData()
if (viaPullToRefresh) {
self.refreshControl.endRefreshing()
}
})
}
When creating a NSURLSession you can specify the delegate/completion queue. If you don't specify a queue
the session creates a serial operation queue for performing all
delegate method calls and completion handler calls.
So this means that your callbacks are called on a private serial queue. Now, all UI must be updated on the main queue, this is why tableView.reloadData() wasn't working.
To remove the dispatch call to the main_queue create the session like this
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: nil, delegateQueue: NSOperationQueue.mainQueue())

Resources