I use singleton pattern for RabbitMQ:
final class ServiceBus{
static let Instance = ServiceBus()
var conn: RMQConnection;
var ch: RMQChannel;
var clientExchange: RMQExchange;
var clientQueue: RMQQueue;
private init() {
let delegate = RMQConnectionDelegateLogger()
self.conn = RMQConnection(uri: myUri, delegate: delegate)
self.conn.start()
self.ch = self.conn.createChannel()
self.clientExchange = ch.direct("Client")
self.clientQueue = ch.queue("", options: .exclusive)
}
}
I also have a lot of pages and I need subscribe to exchange via my sessionID. I subscribe again at every ViewController now:
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
ServiceBus.Instance.clientQueue.bind(ServiceBus.Instance.clientExchange, routingKey: sessionID)
ServiceBus.Instance.clientQueue.subscribe({(_ message: RMQMessage) -> Void in
let message = String(data: message.body, encoding: .utf8)!
//...
})
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
ServiceBus.Instance.clientQueue.bind(ServiceBus.Instance.clientExchange, routingKey: sessionID)
ServiceBus.Instance.clientQueue.subscribe({(_ message: RMQMessage) -> Void in
let message = String(data: message.body, encoding: .utf8)!
//...
})
}
}
How can I create a separate function to subscribe for exchange and use the same seperate function at different ViewControllers?
Related
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
}
I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[DataModelItem])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
func requestData() {
}
private func setDataWithResponse(response:[AnyObject]){
var data = [DataModelItem]()
for item in response{
if let tableViewModel = DataModelItem(data: item as? [String : String]){
data.append(tableViewModel)
}
}
delegate?.didRecieveDataUpdata(data: data)
}
}
And for DataModelItem:
class DataModelItem{
var name:String?
var id:String?
init?(data:[String:String]?) {
if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
self.name = serviceName
self.id = serviceId
}
else{
return nil
}
}
}
Controller:
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
How to implement simple MVC design pattern in Swift?
As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.
For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.
How to call a DataModel init function in "requestData" function
It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.
In the view controller:
let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()
I am just sharing my answer here and I am using a codable. It will be useful for anyone:
Model:
import Foundation
struct DataModelItem: Codable{
struct Result : Codable {
let icon : String?
let name : String?
let rating : Float?
let userRatingsTotal : Int?
let vicinity : String?
enum CodingKeys: String, CodingKey {
case icon = "icon"
case name = "name"
case rating = "rating"
case userRatingsTotal = "user_ratings_total"
case vicinity = "vicinity"
}
}
let results : [Result]?
}
NetWork Layer :
import UIKit
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[String])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
var theatreNameArray = [String]()
var theatreVicinityArray = [String]()
var theatreiconArray = [String]()
func requestData() {
Service.sharedInstance.getClassList { (response, error) in
if error != nil {
self.delegate?.didFailUpdateWithError(error: error!)
} else if let response = response{
self.setDataWithResponse(response: response as [DataModelItem])
}
}
}
private func setDataWithResponse(response:[DataModelItem]){
for i in response[0].results!{
self.theatreNameArray.append(i.name!)
self.theatreVicinityArray.append(i.vicinity!)
self.theatreiconArray.append(i.icon!)
}
delegate?.didRecieveDataUpdata(data: theatreNameArray)
print("TheatreName------------------------->\(self.theatreNameArray)")
print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
print("Theatreicon------------------------->\(self.theatreiconArray)")
}
}
Controller :
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
APIManager :
class Service : NSObject{
static let sharedInstance = Service()
func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
guard let gitUrl = URL(string: "") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(DataModelItem.self, from: data)
completion!([gitData],nil)
} catch let err {
print("Err", err)
completion!(nil,err as NSError)
}
}.resume()
}
}
I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application.
You may refer its documentation at :
Managing Shared resources using singleton
With this you wont need to initialise this class instance every time you need to access data.
I'm having a bit of confusion on how to properly create a SINCall object. I understand that SINCall is a type protocol, and in swift I tried to create it as such:
var _call: SINCall?
When I try to call, my app crashes because the _call is nil.
However, if I do add SINCall to the list of protocols next to SINCallDelegate, and SINCallClientDelegate, I get the error that I'm not conforming to the SINCall protocol.
class CallViewController: UIViewController, SINCallDelegate, SINCallClientDelegate {
var userName: String? {
didSet {
}
}
var recepientUser: String? {
didSet {
}
}
var _call: SINCall?
var appKey = "xxx"
var appSecret = "xxx"
var host = "xxx"
var client: SINClient {
return Sinch.clientWithApplicationKey(appKey, applicationSecret: appSecret, environmentHost: host, userId: userName!)
}
override func viewDidLoad() {
super.viewDidLoad()
print("Lock and load")
client.callClient().delegate = self
client.setSupportCalling(true)
client.start()
client.startListeningOnActiveConnection()
callUser()
}
func callUser() {
self._call = client.callClient().callUserWithId(self.recepientUser!)
self._call!.delegate = self
}
func client(client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
call.delegate = self
self._call = call
self._call!.answer()
}
// callDidProgress, callDidEstablish, callDidEnd implemented below ...
Is the client started when you make the call, it can take a few seconds so you should probably start the the client in the app delegate when its launched, and then when the client is started you can do a call, you probably get nil now because the client is not started
I'm working on an app, that should request some data from my server. I'm using Alamofire to do that, and then use SWXMLHash to parse the XML data. There are two View Controllers, on the first one I can write a shipment number, then override function prepareForSegue and send that number to the next View Controller that should display data from server and updateUI on viewDidLoad, but it does not. Where is a problem?
My Class:
class Shipment {
private var _shipmentNumber: String!
private var _shipmentStatus: String!
private var _trackURL: String!
var shipmentNumber: String {
if _shipmentNumber == nil {
_shipmentNumber = ""
}
return _shipmentNumber
}
var shipmentStatus: String {
if _shipmentStatus == nil {
_shipmentStatus = ""
}
return _shipmentStatus
}
init(spNumber: String) {
self._shipmentNumber = spNumber
_trackURL = "..."
}
func requestXmlInformation(completed: DownloadComplete) {
let url = NSURL(string: _trackURL)!
Alamofire.request(.GET, url).responseData { response in
if let xmlToParse = response.data as NSData! {
let xml = SWXMLHash.parse(xmlToParse)
do {
let xmlSpWeight = try xml["fmresultset"]["resultset"]["record"]["field"].withAttr("name", "ТotalWeight")["data"].element!.text! as String
self._shipmentStatus = xmlSpStatus
print(self._shipmentStatus)
} catch let err as NSError {
print(err.debugDescription)
}
}
}
}
}
My Second View Controller
#IBOutlet weak var numberLbl: UILabel!
#IBOutlet weak var weightLbl: UILabel!
#IBOutlet weak var statusLbl: UILabel!
#IBOutlet weak var packageQtyLbl: UILabel!
var shipment: Shipment!
override func viewDidLoad() {
super.viewDidLoad()
shipment.requestXmlInformation { () -> () in
self.updateUi()
print(self.statusLbl.text)
}
}
updateUI function:
func updateUi() {
numberLbl.text = shipment.shipmentNumber
weightLbl.text = shipment.shipmentWeight
statusLbl.text = shipment.shipmentStatus
packageQtyLbl.text = shipment.shipmentPackageQty
}
It prints data in terminal but i think updateUI function does not work.
Make sure that the code in your requestXmlInformation closure is called on the main thread. You shouldn't update the UI in background threads.
shipment.requestXmlInformation { () -> () in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.updateUi()
print(self.statusLbl.text)
})
}
Also, you don't seem to call the complete closure anywhere in your requestXmlInformation method
I am currently working on an app and I am having an issue. When the user login the webservice, if the login is successful the server responds with JSON, where we use the "firstName" and "SecondName" to then create our "User" which is a struct defined in another file called User.swift . Then, what I want to do is user the "firstName" that has been given to the "User struct" as a UILabel in my homepageview that comes after a successful login. when I try to give my label User.prenom(which is firstName in french) I get the error: User.type does not have a member called...
Here is my code:
the client file where the Login Method is defined:
import Foundation
import Alamofire
import SwiftyJSON
private let _instance = Client()
class Client {
// Router is used to do a request to the server.
private enum Router: URLRequestConvertible {
private static let baseURL = "https://mobile.uqam.ca/portail_etudiant/"
// stores the authentication token.
static var code_perm: String?
static var nip:String?
// Login request.
case Login(String, String)
// URLRequestConvertible protocol.
var URLRequest: NSURLRequest {
// Returns the path, http method and parameters for the request.
var (path: String, method: Alamofire.Method, parameters: [String: AnyObject]) = {
switch self {
case .Login (let code_perm, let nip):
let params: [String: AnyObject] = [
"code_perm": code_perm,
"nip": nip,
]
return ("proxy_dossier_etud.php", .POST, params)
}
}()
// Setup the URLRequest.
let url = NSURL(string: Router.baseURL)
let urlRequest = NSMutableURLRequest(URL: url!.URLByAppendingPathComponent(path))
urlRequest.HTTPMethod = method.rawValue
if let code_perm = Router.code_perm {
if let nip = Router.nip{
parameters["nip"] = nip
parameters["code_perm"] = code_perm
}
}
let encoding = Alamofire.ParameterEncoding.URL
return encoding.encode(urlRequest, parameters: parameters).0
}
}
// Singleton
class var sharedInstance: Client {
return _instance
}
private init() {}
// Login logs in the user with his email and password.
func login(code_perm:String, nip:String, callback:(LoginResponse?) -> Void) {
Alamofire.request(Router.Login(code_perm, nip)).responseJSON { (_, _, data, error) in
if(error != nil) {
callback(nil)
return
}
var json = JSON(data!)
let prenom = json["socio"]["prenom"].stringValue
let nom = json["socio"]["nom"].stringValue
Router.code_perm = code_perm
Router.nip = nip
callback(LoginResponse(
user: User(prenom: prenom,nom: nom)
))
}
}
}
the loginViewController where the login function is called
import UIKit
class LoginViewController: UIViewController {
#IBOutlet weak var LoginScreenImage: UIImageView!
#IBOutlet weak var codeTextField: UITextField!
#IBOutlet weak var nipTextField: UITextField!
#IBOutlet weak var loadingLogin: UIActivityIndicatorView!
let client = Client.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
LoginScreenImage.image = UIImage(named: "UQAMLOGO")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func connect() {
let code_perm = codeTextField.text
let nip = nipTextField.text
self.loadingLogin.startAnimating()
if code_perm != "" && nip != "" {
client.login(code_perm, nip: nip, callback: { (response) in
if let response = response {
self.loadingLogin.stopAnimating()
let homeViewController = self.storyboard!.instantiateViewControllerWithIdentifier("HomeViewController") as HomeViewController
self.showViewController(homeViewController, sender: self)
} else {
self.loadingLogin.stopAnimating()
let badLogin = UIAlertController(title: "Échec de connexion", message: "La combinaison du code permanent et du nip n'est pas bonne", preferredStyle: .Alert)
let reessayer = UIAlertAction(title: "Réessayer", style: .Default, handler: { (reessayer) -> Void in
self.dismissViewControllerAnimated(true , completion: nil)
})
badLogin.addAction(reessayer)
self.presentViewController(badLogin, animated: true, completion: nil)
}
})
}
}
}
the User.swift while where the user struct is
import Foundation
struct User {
var prenom :String
var nom: String
}
struct LoginResponse {
var user: User
}
and finally the HomePageViewController where I try to give the value to my label:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var schedule: UIImageView!
#IBOutlet weak var courses: UIImageView!
#IBOutlet weak var email: UIImageView!
#IBOutlet weak var grades: UIImageView!
#IBOutlet weak var bienvenueLabel: UILabel!
let client = Client.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
schedule.image = UIImage(named:"schedule")
courses.image = UIImage(named: "courses")
email.image = UIImage(named:"mail")
grades.image = UIImage(named:"grades")
bienvenueLabel.text = User.prenom
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Thanks everyone for the help and have a great day/night
Charles
You are accessing the class instead of an instance. Instead, you should pass the response instance to your HomeViewController:
class HomeViewController : .. {
// ...
var loginResponse : LoginResponse
// ...
override func viewDidLoad() {
// ...
bienvenueLabel.text = loginResponse.user.prenom
}
}
// ...
client.login(code_perm, nip: nip, callback: { (response) in
if let loginResponse = response as LoginResponse {
self.loadingLogin.stopAnimating()
let homeViewController = self.storyboard!.instantiateViewControllerWithIdentifier("HomeViewController") as HomeViewController
homeViewController.loginResponse = loginResponse
// assign your instance ^^^^^^^^^^^^^^^^^^^^^^^^
self.showViewController(homeViewController, sender: self)
}
You are accessing the class instead of an instance. Instead, you should pass the response instance to your HomeViewController:
class HomeViewController : .. {
// ...
var loginResponse : LoginResponse
// ...
override func viewDidLoad() {
// ...
bienvenueLabel.text = loginResponse.user.prenom
}
}
// ...
client.login(code_perm, nip: nip, callback: { (response) in
if let loginResponse = response as LoginResponse {
self.loadingLogin.stopAnimating()
let homeViewController = self.storyboard!.instantiateViewControllerWithIdentifier("HomeViewController") as HomeViewController
homeViewController.loginResponse = loginResponse
// assign your instance ^^^^^^^^^^^^^^^^^^^^^^^^
self.showViewController(homeViewController, sender: self)
}
This really isn't very good structure, but it should at least answer your question.