How to use one callback from singleton in multiple views - ios

how to use onCompleted in multiple classes views.
UPDATED FULL CODE !!!
import Foundation
import Alamofire
class AudioSyncManager {
//var onDownloadStart: (()->())?
var onDownloadFinished: ((_ isSuccess: Bool)->())?
var onDownloadProgress: ((_ progress: Float)->())?
static let shared = AudioSyncManager()
private var downloadRequest: DownloadRequest?
private var isDownloading = false
var listData: [MainModel] = []
func doDownloding(onStarted: #escaping ()->()) {
if listData.count == 0 || isDownloading {
return
}
let firstModel = listData.first
if checkMp3FileExists(model: firstModel!) {
self.isDownloading = false
self.listData.removeFirst()
if self.listData.count > 0 {
self.doDownloding {}
}
return
}
let mp3URLString = MyHelper.MEDIA_URL_PREFIX + (firstModel?.link)!
let url = URL(string: mp3URLString)
let destination = DownloadRequest.suggestedDownloadDestination(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask)
//isDownloading = true
onStarted()
downloadRequest = Alamofire.download(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil, to: destination)
.downloadProgress { (progress) in
self.onDownloadProgress?(Float(progress.fractionCompleted))
}.response { (response) in
self.isDownloading = false
self.onDownloadFinished?(true)
if self.listData.count > 0 {
self.listData.removeFirst()
}
if self.listData.count > 0 {
self.doDownloding{}
}
}
}
func addSingleTask(mainModel: MainModel) {
listData.append(mainModel)
doDownloding{}
}
func addListTask(newList: [MainModel]) {
listData.append(contentsOf: newList)
doDownloding{}
}
}

POINT 1
You should be getting error in the below line
let static shared = Service()
because static keyword should come first then declaration.
static let shared = Service()
POINT 2
Implement the onDownload function with Completion Handler
func doDownload(onCompleted: #escaping ()->()) {
onCompleted()
}
Call the function as below
let service = Service.shared
service.doDownload { () in
print("Called in completion Handler")
}
for more detail about Closures go through the below link.
Closures

An example of remove the property onCompleted and make it a parameter to doDownload method:
class Service {
let static shared = Service()
func doDownload(onCompleted: (()->())?) {
//...
onCompleted?()
}
}

Your code in viewDidLoad looks good but could you re visit your singleton class and could you try running your code after adding a private init method calling super.init and see if your code works.

Related

SWIFT How to Send Data back to the ViewController from a SocketAPI Class?

I have ChatsocketIO class which handles all the SocketIO functions related to the chat functionality of the app. The skeleton of that class looks like below.
My Question is when the Socket receives a message it fires the "socket!.on("msg")", However i am confused how to pass this back to the view controller class which i am calling this API class. I have added the View Controller class as well below..
class ChatServiceAPI {
// MARK: - Properties
var manager: SocketManager? = nil
var socket: SocketIOClient? = nil
//Chat Variables
var items:[Message] = []
// MARK: - Life Cycle
init() {
setupSocket()
setupSocketEvents()
socket?.connect()
}
static let shared = ChatServiceAPI();
func stop() {
socket?.removeAllHandlers()
}
// MARK: - Socket Setup
func setupSocket() {
socket = manager!.defaultSocket;
}
func setupSocketEvents() {
if socket!.status != .connected{
socket!.connect()
}
socket!.on(clientEvent: .connect) { (data, emitter) in
print("==connecting==");
}
socket!.on("msg") { (data, emitter) in
let mesage = Message()
let jsonObject = JSON(data[0])
let messageString: String? = jsonObject["msg"].string
let userIDFrom: Int? = jsonObject["profile"]["id"].int
if(userIDFrom != Int(self.userIDTo)) {
return
}
if( userIDFrom == nil)
{
return
}
mesage.data = JSON(["from_user": self.userIDTo, "to_user": self.userID, "msg": self.convertStringToHtmlCode(msg: messageString ?? ""), "created": Date().description(with: .current)])
self.items.insert(mesage, at: 0)
//self.viewMessagesList.reload(messages: self.items, conversation: self.conversation)
}
socket!.on("disconnect") { (data, emitter) in
print("===disconnect==");
self.isConnected = false
}
socket!.connect();
}
// MARK: - Socket Emits
func register(user: String) {
socket?.emit("add user", user)
}
func send(message: String, toUser: String) {
let data = NSMutableDictionary()
data.setValue(message, forKey: "msg")
data.setValue(toUser.lowercased(), forKey: "to")
socket!.emit("send", data);
return;
}
}
In My View Controller, I have something like below, I want to pass ChatSocketAPI's "self.items" to the below-calling controller, when a msg comes, I am confused about how to do this?
import UIKit
import SocketIO
import SwiftyJSON
import EZAlertController
class MessagingViewController: UIViewController {
let viewMessagesList = MessagesListViewController()
let bottomMenuChatView = BottomMenuChatView(frame: CGRect.zero)
var isAnimation = false
let emojiView = EmojiView(frame: CGRect.zero)
func setupSocketIO() {
ChatServiceAPI.init();
self.socket = ChatServiceAPI.shared.socket;
}
override func viewDidLoad() {
super.viewDidLoad()
self.setupSocketIO()
ChatServiceAPI.shared.page = 0;
ChatServiceAPI.shared.items = []
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setupNavigation()
if Reachability.isConnectedToNetwork() == false {
EZAlertController.alert("", message: "Please check your internet connection")
return
}
// bottom menu setup
bottomMenuChatView.btnSendMessage.addTarget(self, action: #selector(tappedBtnSendMessage(btn:)), for: .touchUpInside)
bottomMenuChatView.btnEmoji.addTarget(self, action: #selector(tappedBtnEmoji(btn:)), for: .touchUpInside)
bottomMenuChatView.textFieldMessage.delegate = self
}
Make your MessagingViewController a listener for the ChatServiceAPI. You can implement that like this:
Create a protocol like the following:
protocol MessageListener: AnyObject {
func messageReceived(text: String)
}
and make you controller conform to it:
extension MessagingViewController: MessageListener {
func messageReceived(text: String) {
// do whatever you need with the message
}
}
Then, in the ChatServiceAPI you can create a var named listeners:
private var listeners: [MessageListener] = []
and methods for adding and removing listeners:
func add(listener: MessageListener) {
self.listeners.append(listener)
}
func remove(listener: MessageListener) {
self.listeners.remove(listener)
}
For the last part, in your ChatServiceAPI, when the "msg" event is received, you need to send the message to all of your registered listeners. So, something like this:
socket!.on("msg") { (data, emitter) in
...
for listener in self.listeners {
listener.messageReceived(text: ...)
}
}
Now you also need to register your viewController as a listener. So you would call ChatServiceAPI.shared.add(listener: self) in your viewDidLoad.
Don't forget to also call ChatServiceAPI.shared.remove(listener: self) to prevent memory leaks.
You can back data to your view controller by writing socket.ON inside a function that have escaping block.
Here is my sample code that I use
func getResponse(completion: #escaping(_ resMessage: String) -> Void) {
guard let socket = manager?.defaultSocket else {
return
}
socket.on(myEventName) { (dataArray, socketAck) -> Void in
guard let data = UIApplication.jsonData(from: dataArray[0]) else {
return
}
do {
let responseMsg = try JSONDecoder().decode(String.self, from: data)
completion(responseMsg)
} catch let error {
print("Something happen wrong here...\(error)")
completion("")
}
}
}
Then you can call this function getResponse in viewDidLoad inside your viewController. Like this -
self.socketInstance.getResponse { socketResponse in
// socketResponse is the response
// this will execute when you get new response from socket.ON
}

Swift How to solve a Memory "Leak"

I have encountered a memory "leak" that I don't understand. I have a caching class whose purpose is to limit the number of instances of heavy Data items. Even when it stores NO instances of those items, they are being retained somehow. If they are from a background thread, I believe they are released upon completion of the task. But, of course, as demonstrated by the code, on the main thread they persist.
This occurs on iPadOS 14.8, iPadOS 15 and MacCatalyst.
What am I missing or don't understand?
class DataCache {
var id: String
var data: Data? {
get {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("\(id).txt")
return try! Data(contentsOf: url)
}
set {
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("\(id).txt")
try! newValue!.write(to: url)
}
}
init(id: String, data: Data) {
self.id = id
self.data = data
}
}
class Item {
static var selection = [Item]()
static var items = [String:Item]()
var id: String = UUID().uuidString
var itemData: DataCache
init() {
itemData = DataCache(id: id,
data: Data(String(repeating: "dummy", count: 4_000_000).utf8)
)
}
required init(_ other: Item) {
self.itemData = DataCache(id: self.id, data: other.itemData.data!)
}
func duplicate(times: Int) {
for index in 0..<times {
print(index)
Item.selection.append(Item(self))
}
}
}
#main struct Main {
static func main() throws {
let item = Item()
perform(item)
performOnSelection() { item in
let _ = Item(item)
}
while (true) {}
}
static func perform(_ item: Item) {
item.duplicate(times: 100)
}
static func performOnSelection(perform action: #escaping (Item)->Void) {
var done = false
DispatchQueue.global().async {
for item in Item.selection {
action(item)
}
done = true
}
while !done { sleep (1) }
}
}
You have autorelease objects being created. Insert an autoreleasepool to drain the pool periodically, e.g.:
func performOnSelection(perform action: #escaping (Item) -> Void) {
...
autoreleasepool {
perform(item)
}
performOnSelection() { item in
autoreleasepool {
let _ = Item(item)
}
}
...
}
and
func duplicate(times: Int) {
for index in 0..<times {
print(index)
autoreleasepool { [self] in
Item.selection.append(Item(self))
}
}
}
E.g. without autoreleasepool:
And with:
It turns off to be much faster when it is not dealing with all of those lingering autorelease objects, too. And peak memory usage has gone from 3.4gb to 47mb.

How to proper inject dependency using swinject

I'm trying to inject dependency using Swinject, and I have no clue what I'm doing wrong.
I have protocol, that handles registring user.
protocol AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) }
and a class that conforms to this protocol make all the logic:
class AuthService: AuthServiceProtocol {
func registerUser(email: String, password: String, completion: #escaping CompletionHandler) {
let lowerCaseMail = email.lowercased()
let body: [String: Any] = [
"email": lowerCaseMail,
"password" : password
]
Alamofire.request(URL_REGISTER, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseString { (response) in
if response.result.error == nil {
completion(true)
} else {
completion(false)
debugPrint(response.result.error as Any)
}
}
}
}
so, in AppDelegate we register container and it looks like:
let container = Container() { container in
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
return controller
}
}
but in CreateAccountVC authService is empty. Any ideas how can i do it?
CreateAccountVC is a subclass of ViewController, i have try'ed it by property, and constructors, but it's nil all the time.
Check your code:
var container : Container {
let container = Container()
container.register(AuthServiceProtocol.self) { _ in AuthService() }.inObjectScope(.container)
container.register(CreateAccountVC.self) { r in
let controller = CreateAccountVC()
controller.authService = r.resolve(AuthServiceProtocol.self)
print(r.resolve(AuthServiceProtocol.self))
return controller
}
return container
}
You have computed property and every time you call it, it creates a NEW Container object.
Refactor your code to have a single Container and I believe you will be good to go.
EDIT:
Here's a working code snippet.
Below is a small wrapper class to abstract concrete DI service (in case Swinject is one day replace by something else):
import Swinject
public class ConfigurationProvider {
// Currently using Swinject
private let backingService = Container()
// Singleton
public static let shared = ConfigurationProvider()
// Hidden initializer
private init() {}
// MARK: - Bind / Resolve
public func bind<T>(interface: T.Type, to assembly: T) {
backingService.register(interface) { _ in assembly }
}
public func resolve<T>(interface: T.Type) -> T! {
return backingService.resolve(interface)
}
}
// Extension methods to ignore 'shared.' call, like:
// ConfigurationProvider.bind(interface: IAssembly, to: Assembly())
// ConfigurationProvider.resolve(interface: IAssembly)
public extension ConfigurationProvider {
static func bind<T>(interface: T.Type, to assembly: T) {
ConfigurationProvider.shared.bind(interface: interface, to: assembly)
}
static func resolve<T>(interface: T.Type) -> T! {
return ConfigurationProvider.shared.resolve(interface: interface)
}
}
Usage:
class RSAuthLoginModuleAssembly: IAuthLoginModuleAssembly {
}
// Register:
ConfigurationProvider.bind(interface: IAuthLoginModuleAssembly.self, to: ConcreteAuthLoginModuleAssembly())
// Resolve:
guard let assembly = ConfigurationProvider.resolve(interface: IAuthLoginModuleAssembly.self) else {
throw NSError(domain: "Assembly cannot be nil", code: 999, userInfo: nil)
}

How to make a Swift Class Singleton instance thread safe?

I have a singleton class as so:
class Database {
static let instance:Database = Database()
private var db: Connection?
private init(){
do {
db = try Connection("\(path)/SalesPresenterDatabase.sqlite3")
}catch{print(error)}
}
}
Now I access this class using Database.instance.xxxxxx to perform a function within the class. However when I access the instance from another thread it throws bizarre results as if its trying to create another instance. Should I be referencing the instance in the same thread?
To clarify the bizarre results show database I/o errors because of two instances trying to access the db at once
Update
please see this question for more info on the database code: Using transactions to insert is throwing errors Sqlite.swift
class var shareInstance: ClassName {
get {
struct Static {
static var instance: ClassName? = nil
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token, {
Static.instance = ClassName()
})
return Static.instance!
}
}
USE: let object:ClassName = ClassName.shareInstance
Swift 3.0
class ClassName {
static let sharedInstance: ClassName = { ClassName()} ()
}
USE: let object:ClassName = ClassName.shareInstance
In Swift 3.0 add a private init to prevent others from using the default () initializer.
class ClassName {
static let sharedInstance = ClassName()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
Singleton thread class.
final public class SettingsThreadSafe {
public static let shared = SettingsThreadSafe()
private let concurrentQueue = DispatchQueue(label: "com.appname.typeOfQueueAndUse", attributes: .concurrent)
private var settings: [String: Any] = ["Theme": "Dark",
"MaxConsurrentDownloads": 4]
private init() {}
public func string(forKey key: String) -> String? {
var result: String?
concurrentQueue.sync {
result = self.settings[key] as? String
}
return result
}
public func int(forKey key: String) -> Int? {
var result: Int?
concurrentQueue.sync {
result = self.settings[key] as? Int
}
return result
}
public func set(value: Any, forKey key: String) {
concurrentQueue.async( flags: .barrier ) {
self.settings[key] = value
}
}
}
Unit to test the singleton class.
func testConcurrentUsage() {
let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
let expect = expectation(description: "Using SettingsThreadSafe.shared from multiple threads shall succeed")
let callCount = 300
for callIndex in 1...callCount {
concurrentQueue.async {
SettingsThreadSafe.shared.set(value: callIndex, forKey: String(callIndex))
}
}
while SettingsThreadSafe.shared.int(forKey: String(callCount)) != callCount {
// nop
}
expect.fulfill()
waitForExpectations(timeout: 5) { (error) in
XCTAssertNil(error, "Test expectation failed")
}
}

Losing reference to a property of an instance in Swift

I'm encountering a problem where a property of an instance of a class I've created is seemingly losing reference to one its values.
Essentially I have a class like this:
class Channel {
var callbacks: [String: (JSON) -> Void]
var subscribed = false
let name: String
init(name: String) {
self.name = name
self.callbacks = [:]
}
func bind(eventName: String, callback: (JSON) -> Void) {
self.callbacks[eventName] = callback
}
func handleEvent(eventName: String, eventData: String) {
if let cb = self.callbacks[eventName] {
let json = JSON(object: eventData)
cb(json)
}
}
}
and then inside a ViewController I have the following code:
class ViewController: UIViewController {
let wSock = wSocket(key: "afa4d38348f89ba9c398")
func channelSetup() {
var ch = wSock.subscribe("test-channel")
ch.bind("test-event", { (data: JSON) -> Void in
println("I'm the callback getting called")
})
println(ch.callbacks)
}
override func viewDidLoad() {
super.viewDidLoad()
channelSetup()
}
}
In the println of ch.callbacks it shows that there is a key-value pair in the dictionary.
However, when the channel receives an event later on when there is a message received over the socket, the callback is no longer there. In terms of code, here is the code in full:
import UIKit
class ViewController: UIViewController {
let wSock = wSocketClient(key: "afa4d38348f89ba9c398")
func channelSetup() {
var ch = wSock.subscribe("test-channel")
ch.bind("test-event", { (data: JSON) -> Void in
println("I'm the callback getting called")
})
println(ch.callbacks)
}
override func viewDidLoad() {
super.viewDidLoad()
channelSetup()
}
}
class wSocketClient {
let connection: Connection
init(key: String, encrypted: Bool = false) {
var url = "SOCKET_URL"
connection = Connection(url: url)
}
func subscribe(channelName: String) -> Channel {
return self.connection.addChannel(channelName)
}
func connect() {
self.connection.open()
}
}
class Connection: WebSocketDelegate {
let url: String
lazy var socket: WebSocket = { [unowned self] in
return self.connectInternal()
}()
let connected = false
var channels = Channels()
init(url: String) {
self.url = url
}
func addChannel(channelName: String) -> Channel {
return Channel(name: channelName)
}
func open() {
if self.connected {
return
} else {
self.socket = connectInternal()
}
}
func connectInternal() -> WebSocket {
let ws = WebSocket(url: NSURL(string: self.url)!)
ws.delegate = self
ws.connect()
return ws
}
func websocketDidReceiveMessage(text: String) {
let data = (text as NSString).dataUsingEncoding(NSUTF8StringEncoding)
let json = JSON(data: data!)
if let channelName = json["channel"].stringValue {
if let internalChannel = self.channels.find(channelName) {
if let eName = json["event"].stringValue {
if let eData = json["data"].stringValue {
internalChannel.handleEvent(eName, eventData: eData) // this is the part of the code where the channel should eventually call the callback
}
}
}
}
}
}
class Channel {
var callbacks: [String: (JSON) -> Void]
var subscribed = false
let name: String
init(name: String) {
self.name = name
self.callbacks = [:]
}
func bind(eventName: String, callback: (JSON) -> Void) {
self.callbacks[eventName] = callback
}
func handleEvent(eventName: String, eventData: String) {
if let cb = self.callbacks[eventName] { // here self.callbacks is empty and the callback has disappeared
let json = JSON(object: eventData)
cb(json)
}
}
}
class Channels {
var channels = [String: Channel]()
func add(channelName: String) -> Channel {
if let channel = self.channels[channelName] {
return channel
} else {
let newChannel = Channel(name: channelName)
self.channels[channelName] = newChannel
return newChannel
}
}
func find(channelName: String) -> Channel? {
return self.channels[channelName]
}
}
So basically when the WebSocket receives some data that is for the given channel, it should check for the event name, and if there is a callback with that event name, call the callback associated to that event name. However, the channel apparently has no callbacks when the handleEvent method is called, even though at the bottom of viewDidLoad it shows as having a callback in the callbacks property for the channel.
Any ideas as to where / why the callback is disappearing?
Update
I've now tried moving the definition of the channel, ch outside of the channelSetup function so it's like this, but with no luck:
class ViewController: UIViewController {
let wSock = wSocket(key: "afa4d38348f89ba9c398")
var ch: Channel = nil
func channelSetup() {
ch = wSock.subscribe("test-channel")
ch.bind("test-event", { (data: JSON) -> Void in
println("I'm the callback getting called")
})
println(ch.callbacks)
}
override func viewDidLoad() {
super.viewDidLoad()
channelSetup()
}
}
I've solved this but not because it was something going on in Swift that I didn't understand. Instead it was just that the way that I had setup the code meant that there were duplicate channel objects being created and the callback was being added to only one of the channels.

Resources