I have this function. Whenever I comment the "v": 2 parameter it works like a charm, then I add it and when trying to call the function from my VC it doesn't get fired off. I think the problem is somewhere at the top. Like I said, when commenting the "v:2" parameter, everything goes smooth. I've checked my postman and the URL Request adding that parameter does give me the right response, but my function is not being called in my VC. Any ideas? Please I'm desperate ):
public func getBusinessBy(location loc: String, type: Int, loading: Bool, OnSuccess success: #escaping (_ businesses: [NSDictionary]) -> Void, OnFailed failed: #escaping (_ error: String) -> Void) {
if loading {
GlobalLoader.show()
}
let bUrl = HttpUtils.getBusinessesBy(type: type)
let params: Parameters = [
"location": loc,
"type": type,
"v": 2,
"params": Config.ENABLE_SEARCH ? Config.SEARCH_BUSINESS_PARAMS : Config.COMMON_BUSINESS_PARAMS,
]
Alamofire.request(bUrl, method: .get, parameters: params, encoding: URLEncoding.queryString, headers: [:]).responseJSON() { response in
switch response.result {
case .success(let val):
print("yaaaaay!")
if let res = val as? NSDictionary {
if let businesses = res.results() {
let allBusiesses = businesses.sorted(by: { (d1, d2) -> Bool in
(d1.object(forKey: "open") as! Bool) && !(d2.object(forKey: "open") as! Bool)
})
// Storing All Businesses to global
Session.sharedInstance.setAllBusiness(allBusiesses)
var tmpCat = [NSDictionary]()
var tmpPro = [NSDictionary]()
var tmpPMotion = [NSDictionary]()
var tmpPBusiness = [NSDictionary]()
for busin in allBusiesses {
guard let categories = busin["categories"] as? [NSDictionary] else {
return
}
tmpCat.append(contentsOf: categories)
var flag = false
for cat in categories {
if let prod = cat["products"] as? [NSDictionary] {
for p in prod {
var pClone = NSMutableDictionary()
pClone = p.mutableCopy() as! NSMutableDictionary
let pivot = NSMutableDictionary()
pivot["business_id"] = busin.getId()
pivot["business_name"] = busin.getName()
pivot["business_description"] = busin.getDescription()
pivot["business_enabled"] = busin.isOpened()
pivot["category_name"] = cat.getName()
pivot["category_description"] = cat.getDescription()
pivot["category_enabled"] = cat.isEnabled()
pClone["pivot"] = pivot
tmpPro.append(pClone)
if p.isFeatured() {
tmpPMotion.append(pClone)
if !flag {
tmpPBusiness.append(busin)
flag = true
}
}
}
}
}
}
Session.sharedInstance.setAllProduct(tmpPro)
Session.sharedInstance.setPromotions(tmpPMotion)
Session.sharedInstance.setPromBusinesses(tmpPBusiness)
Session.sharedInstance.setAllCategory(tmpCat)
// }
success(businesses)
}
} else {
failed(val as? String ?? "Passing error!")
}
if loading {
// SVProgressHUD.dismiss()
GlobalLoader.hide()
}
break
case .failure(let error):
print("naaaaay!")
failed(error.localizedDescription)
if loading {
// SVProgressHUD.dismiss()
GlobalLoader.hide()
}
break
}
}
}
Related
I'm trying to implement an async call to a function loadUserFromFirebase and populate and return an array based on user attributes that I then use to write to a firebase collection.
I'm trying to use the Combine framework from Swift using future and promises but for some reason, the receiveCompletion and receiveValue don't get called and thus I get an empty array from my async function call.
Here is my code:
var cancellable = Set<AnyCancellable>()
func loadUserFromFirebase(groupUserIds: [String], handler: #escaping ([groupMate]) -> Void) {
var groupmateID = 0
var groupMates : [groupMate] = []
print("groupUserIds: \(groupUserIds)" )
for groupUserUID in groupUserIds {
print("groupUserUID: \(groupUserUID)" )
let future = Future<groupMate, Never> { promise in
self.ref.collection("Users").document(groupUserUID).getDocument(){
(friendDocument, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
// print("friendDocument: \(String(describing: friendDocument?.data()))" )
let groupUsername = (friendDocument?.data()?["username"]) as! String
let groupUID = (friendDocument?.data()?["uid"]) as! String
let groupName = (friendDocument?.data()?["name"]) as! String
let groupPic = (friendDocument?.data()?["imageurl"]) as! String
promise(.success(groupMate(id: groupmateID, uid: groupUID , name: groupName , username: groupUsername, pic: groupPic)))
}
groupmateID += 1
}
}
print("in receiveCompletion")
future.sink(receiveCompletion: { completion in
print("in receiveCompletion")
print(1, completion)
switch completion {
case .failure(let error):
print(3, error)
handler([])
return
case .finished:
break
}
},
receiveValue: {
print("in receiveValue")
groupMates.append($0)
print(groupMates)
handler(groupMates)
}).store(in: &cancellable)
}
}
func creategroup(groupName: String){
addedTogroupUsers.append(self.uid)
print("here111")
loadUserFromFirebase(groupUserIds: addedTogroupUsers) { groupMates in
print("here222")
let groupData: [String: Any] = [
"groupName": "\(groupName)",
"groupmates": groupMates
]
print("here333 \(groupData)")
print("groupMates are \(self.groupMates)")
var groupref: DocumentReference? = nil
groupref = self.ref.collection("groups").addDocument(data: groupData) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(groupref!.documentID)")
for addedgroupUser in self.addedTogroupUsers {
self.ref.collection("Users").document(addedgroupUser).updateData([
"groups": FieldValue.arrayUnion([groupref!.documentID])
])
}
}
}
print("groupName is \(groupName) and addedTogroup are \(self.addedTogroupUsers)")
}
}
I'm trying to see if AnyCancellable is the way to go but since I'm using a chained array of future promises, I'm not sure how to implement it. Please let me know how you'd solve this problem so that the array does get populated since the documents do exist and the print inside the method call work but the groupMates array in the createGroup function prints an empty array afterwards. Thanks!
Edit: Added AnyCancallable to Code along with completion handler as suggested
dealing with async functions can be tricky. You are getting an empty array, because you are returning too early, in loadUserFromFirebase. Try this approach (untested) using the old style closure:
func loadUserFromFirebase(groupUserIds: [String], handler: #escaping ([groupMate]) -> Void) { // <-- here
var groupmateID = 0
var groupMates : [String] = []
print("groupUserIds: \(groupUserIds)" )
for groupUserUID in groupUserIds {
print("groupUserUID: \(groupUserUID)" )
let future = Future<groupMate, Never> { promise in
self.ref.collection("Users").document(groupUserUID).getDocument(){ (friendDocument, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
print("friendDocument: \(String(describing: friendDocument?.data()))" )
let groupUsername = (friendDocument?.data()?["username"]) as! String
let groupUID = (friendDocument?.data()?["uid"]) as! String
let groupName = (friendDocument?.data()?["name"]) as! String
let groupPic = (friendDocument?.data()?["imageurl"]) as! String
promise(.success(groupMate(id: groupmateID, uid: groupUID , name: groupName , username: groupUsername, pic: groupPic)))
}
groupmateID += 1
}
}
future.sink(receiveCompletion: { completion in
print("in receiveCompletion")
print(1, completion)
switch completion {
case .failure(let error):
print(3, error)
handler([]) // <-- here
return // <-- here
case .finished:
break
}
},
receiveValue: {
print("in receiveValue")
groupMates.append($0)
print(groupMates)
handler(groupMates) // <-- here
})
}
// <-- do not return here
}
func creategroup(groupName: String) {
addedTogroupUsers.append(self.uid)
// -- here wait until you get the data
loadUserFromFirebase(groupUserIds: addedTogroupUsers) { groupMates in
let groupData: [String: Any] = [
"groupName": "\(groupName)",
"groupmates": groupMates // <-- here
]
print("groupMates are \(self.groupMates)")
var groupref: DocumentReference? = nil
groupref = ref.collection("groups").addDocument(data: groupData) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(groupref!.documentID)")
for addedgroupUser in self.addedTogroupUsers {
self.ref.collection("Users").document(addedgroupUser).updateData([
"groups": FieldValue.arrayUnion([groupref!.documentID])
])
}
}
}
print("groupName is \(groupName) and addedTogroup are \(addedTogroupUsers)")
}
}
Note, if you are targeting ios 15, macos 12, you are far better served if you use the swift 5.5 async/await/task features. They really work well.
EDIT: trying to return all results
func loadUserFromFirebase(groupUserIds: [String], handler: #escaping ([groupMate]) -> Void) {
var groupmateID = 0
var groupMates : [String] = []
print("groupUserIds: \(groupUserIds)" )
var arr: [Future<groupMate, Never>] = [Future<groupMate, Never>]()
var cancellable = Set<AnyCancellable>()
for groupUserUID in groupUserIds {
print("groupUserUID: \(groupUserUID)" )
let future = Future<groupMate, Never> { promise in
self.ref.collection("Users").document(groupUserUID).getDocument(){ (friendDocument, err) in
if let err = err {
print("Error getting documents \(err)")
} else {
print("friendDocument: \(String(describing: friendDocument?.data()))" )
let groupUsername = (friendDocument?.data()?["username"]) as! String
let groupUID = (friendDocument?.data()?["uid"]) as! String
let groupName = (friendDocument?.data()?["name"]) as! String
let groupPic = (friendDocument?.data()?["imageurl"]) as! String
promise(.success(groupMate(id: groupmateID, uid: groupUID , name: groupName , username: groupUsername, pic: groupPic)))
}
groupmateID += 1
}
}
arr.append(future)
}
Publishers.MergeMany(arr)
.collect()
.sink { _ in
print("-----> merging ")
} receiveValue: { value in
print("-----> value: \(value)")
groupMates = value // <--- maybe append?
print(groupMates)
handler(groupMates)
}
.store(in: &cancellable)
}
I am using OpenVidu for Video Chat, Using this Repo https://github.com/OpenVidu/openvidu-ios-app I know this has bugs, but I have to use this as This is working fine at Android and WEB. I am able to make it working where my Local video can be seen on OpenVidu Web but Remote or other person who has joined session from Web whose video(Video Stream and Audio Stream) is not coming at my end. however I can see Remote Participant ID and Name at my end when a user joined the session.
Attached Image is the screen shot showing Remote streams are nil.
Below is the WebSocketListener Class I am using, I have updated pods so have to update delegates as well.
//
// WebSocketListener.swift
// WebRTCapp
//
// Created by Sergio Paniego Blanco on 01/05/2018.
// Copyright © 2018 Sergio Paniego Blanco. All rights reserved.
//
import Foundation
import Starscream
import WebRTC
class WebSocketListener: WebSocketDelegate {
let JSON_RPCVERSION = "2.0"
let useSSL = true
var socket: WebSocket
var helloWorldTimer: Timer?
var id = 0
var url: String
var sessionName: String
var participantName: String
var localOfferParams: [String: String]?
var iceCandidatesParams: [[String:String]]?
var userId: String?
var remoteParticipantId: String?
var participants: [String: RemoteParticipant]
var localPeer: RTCPeerConnection?
var peersManager: PeersManager
var token: String
var views: [UIView]!
var names: [UILabel]!
var key: String?
init(url: String, sessionName: String, participantName: String, peersManager: PeersManager, token: String, views: [UIView], names: [UILabel]) {
self.url = url
self.sessionName = sessionName
self.participantName = participantName
self.participants = [String: RemoteParticipant]()
self.peersManager = peersManager
self.token = token
var request = URLRequest(url: URL(string: url)!)
request.timeoutInterval = 20
socket = WebSocket(request: request)
socket.delegate = self
socket.connect()
self.localPeer = self.peersManager.localPeer
self.iceCandidatesParams = []
self.views = views
self.names = names
}
// func websocketDidConnect(socket: WebSocketClient) {
// print("Connected")
// pingMessageHandler()
// var joinRoomParams: [String: String] = [:]
// joinRoomParams["recorder"] = "false"
// joinRoomParams["platform"] = "iOS"
// joinRoomParams[JSONConstants.Metadata] = "{\"clientData\": \"" + "iOSUser" + "\"}"
// joinRoomParams["secret"] = "MY_SECRET"
// joinRoomParams["session"] = sessionName
// joinRoomParams["token"] = token
// sendJson(method: "joinRoom", params: joinRoomParams)
// if localOfferParams != nil {
// sendJson(method: "publishVideo",params: localOfferParams!)
// }
// }
func pingMessageHandler() {
helloWorldTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(WebSocketListener.doPing), userInfo: nil, repeats: true)
doPing()
}
#objc func doPing() {
var pingParams: [String: String] = [:]
pingParams["interval"] = "5000"
sendJson(method: "ping", params: pingParams)
socket.write(ping: Data())
}
var isConnected = false
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
print("Disconnect: " + error.debugDescription)
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
isConnected = true
print("websocket is connected: \(headers)")
pingMessageHandler()
var joinRoomParams: [String: String] = [:]
joinRoomParams["recorder"] = "false"
joinRoomParams["platform"] = "iOS"
joinRoomParams[JSONConstants.Metadata] = "{\"clientData\": \"\(self.participantName)\"}"
joinRoomParams["secret"] = ""
joinRoomParams["session"] = sessionName
joinRoomParams["token"] = token
sendJson(method: "joinRoom", params: joinRoomParams)
if localOfferParams != nil {
sendJson(method: "publishVideo",params: localOfferParams!)
}
case .disconnected(let reason, let code):
isConnected = false
print("websocket is disconnected: \(reason) with code: \(code)")
case .text(let string):
print("Received text: \(string)")
let data = string.data(using: .utf8)!
do {
let json: [String: Any] = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String : Any]
if json[JSONConstants.Result] != nil {
handleResult(json: json)
} else {
handleMethod(json: json)
}
} catch let error as NSError {
print("ERROR parsing JSON: ", error)
}
case .binary(let data):
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
isConnected = false
case .error(let error):
isConnected = false
print(error.debugDescription)
}
}
// func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
// print("Recieved message: " + text)
// let data = text.data(using: .utf8)!
// do {
// let json: [String: Any] = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as! [String : Any]
//
// if json[JSONConstants.Result] != nil {
// handleResult(json: json)
// } else {
// handleMethod(json: json)
// }
//
// } catch let error as NSError {
// print("ERROR parsing JSON: ", error)
// }
// }
func handleResult(json: [String: Any]) {
let result: [String: Any] = json[JSONConstants.Result] as! [String: Any]
if result[JSONConstants.SdpAnswer] != nil {
saveAnswer(json: result)
} else if result[JSONConstants.SessionId] != nil {
if result[JSONConstants.Value] != nil {
let value = result[JSONConstants.Value] as! [[String:Any]]
if !value.isEmpty {
addParticipantsAlreadyInRoom(result: result)
}
self.userId = result[JSONConstants.Id] as? String
for var iceCandidate in iceCandidatesParams! {
iceCandidate["endpointName"] = self.userId
sendJson(method: "onIceCandidate", params: iceCandidate)
}
}
} else if result[JSONConstants.Value] != nil {
print("pong")
} else {
print("Unrecognized")
}
}
func addParticipantsAlreadyInRoom(result: [String: Any]) {
let values = result[JSONConstants.Value] as! [[String: Any]]
for participant in values {
print(participant[JSONConstants.Id]!)
self.remoteParticipantId = participant[JSONConstants.Id]! as? String
let remoteParticipant = RemoteParticipant()
remoteParticipant.id = participant[JSONConstants.Id] as? String
let metadataString = participant[JSONConstants.Metadata] as! String
let data = metadataString.data(using: .utf8)!
do {
if let metadata = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? Dictionary<String,Any>
{
remoteParticipant.participantName = metadata["clientData"] as? String
}
} catch let error as NSError {
print(error)
}
self.participants[remoteParticipant.id!] = remoteParticipant
self.peersManager.createRemotePeerConnection(remoteParticipant: remoteParticipant)
let mandatoryConstraints = ["OfferToReceiveAudio": "true", "OfferToReceiveVideo": "true"]
let sdpConstraints = RTCMediaConstraints(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil)
remoteParticipant.peerConnection!.offer(for: sdpConstraints, completionHandler: {(sessionDescription, error) in
print("Remote Offer: " + error.debugDescription)
self.participants[remoteParticipant.id!]!.peerConnection!.setLocalDescription(sessionDescription!, completionHandler: {(error) in
print("Remote Peer Local Description set " + error.debugDescription)
})
var remoteOfferParams: [String:String] = [:]
remoteOfferParams["sdpOffer"] = sessionDescription!.sdp
remoteOfferParams["sender"] = self.remoteParticipantId! + "_CAMERA"
self.sendJson(method: "receiveVideoFrom", params: remoteOfferParams)
})
self.peersManager.remotePeer!.delegate = self.peersManager
}
}
func saveAnswer(json: [String:Any]) {
let sessionDescription = RTCSessionDescription(type: RTCSdpType.answer, sdp: json["sdpAnswer"] as! String)
if localPeer == nil {
self.localPeer = self.peersManager.localPeer
}
if (localPeer!.remoteDescription != nil) {
participants[remoteParticipantId!]!.peerConnection!.setRemoteDescription(sessionDescription, completionHandler: {(error) in
print("Remote Peer Remote Description set: " + error.debugDescription)
if self.peersManager.remoteStreams.count >= self.participants.count {
DispatchQueue.main.async {
print("Count: " + self.participants.count.description)
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
let videoTrack = self.peersManager.remoteStreams[self.participants.count-1].videoTracks[0]
videoTrack.add(renderer)
// Add the view and name to the first free space available
var index = 0
while (index < 2 && !(self.names[index].text?.isEmpty)!) {
index += 1
}
if index < 2 {
self.names[index].text = self.participants[self.remoteParticipantId!]?.participantName
self.names[index].backgroundColor = UIColor.black
self.names[index].textColor = UIColor.white
self.embedView(renderer, into: self.views[index])
self.participants[self.remoteParticipantId!]?.index = index
self.views[index].bringSubview(toFront: self.names[index])
}
}else
{
#if arch(arm64)
let renderer = RTCMTLVideoView(frame: self.views[self.participants.count-1].frame)
#else
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
#endif
let videoTrack = self.peersManager.remoteStreams[self.participants.count-1].videoTracks[0]
videoTrack.add(renderer)
// Add the view and name to the first free space available
var index = 0
while (index < 2 && !(self.names[index].text?.isEmpty)!) {
index += 1
}
if index < 2 {
self.names[index].text = self.participants[self.remoteParticipantId!]?.participantName
self.names[index].backgroundColor = UIColor.black
self.names[index].textColor = UIColor.white
self.embedView(renderer, into: self.views[index])
self.participants[self.remoteParticipantId!]?.index = index
self.views[index].bringSubview(toFront: self.names[index])
}
}
}
}
})
} else {
localPeer!.setRemoteDescription(sessionDescription, completionHandler: {(error) in
print("Local Peer Remote Description set: " + error.debugDescription)
})
}
}
func handleMethod(json: Dictionary<String,Any>) {
if json[JSONConstants.Params] != nil {
let method = json[JSONConstants.Method] as! String
let params = json[JSONConstants.Params] as! Dictionary<String, Any>
switch method {
case JSONConstants.IceCandidate:
iceCandidateMethod(params: params)
case JSONConstants.ParticipantJoined:
participantJoinedMethod(params: params)
case JSONConstants.ParticipantPublished:
participantPublished(params: params)
case JSONConstants.ParticipantLeft:
participantLeft(params: params)
default:
print("Error handleMethod, " + "method '" + method + "' is not implemented")
}
}
}
func iceCandidateMethod(params: Dictionary<String, Any>) {
// if (params["endpointName"] as? String == userId) {
// saveIceCandidate(json: params, endPointName: nil)
// } else {
// saveIceCandidate(json: params, endPointName: params["endpointName"] as? String)
// }
DispatchQueue.main.async {
if params["senderConnectionId"] != nil {
self.key = "senderConnectionId"
} else {
self.key = "endpointName"
}
if (params[self.key ?? ""] as? String == self.userId) {
self.saveIceCandidate(json: params, endPointName: params["endpointName"] as? String)
} else {
self.saveIceCandidate(json: params, endPointName: params[self.key ?? ""] as? String)
}
}
}
// func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
// print("Received data: " + data.description)
// }
func participantJoinedMethod(params: Dictionary<String, Any>) {
let remoteParticipant = RemoteParticipant()
remoteParticipant.id = params[JSONConstants.Id] as? String
self.participants[params[JSONConstants.Id] as! String] = remoteParticipant
let metadataString = params[JSONConstants.Metadata] as! String
let data = metadataString.data(using: .utf8)!
do {
if let metadata = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? Dictionary<String,Any>
{
remoteParticipant.participantName = metadata["clientData"] as? String
self.peersManager.createRemotePeerConnection(remoteParticipant: remoteParticipant)
} else {
print("bad json")
}
} catch let error as NSError {
print(error)
}
participantPublished(params: params)
}
func participantPublished(params: Dictionary<String, Any>) {
self.remoteParticipantId = params[JSONConstants.Id] as? String
print("ID: " + remoteParticipantId!)
let remoteParticipantPublished = participants[remoteParticipantId!]!
let mandatoryConstraints = ["OfferToReceiveAudio": "true", "OfferToReceiveVideo": "true"]
remoteParticipantPublished.peerConnection!.offer(for: RTCMediaConstraints.init(mandatoryConstraints: mandatoryConstraints, optionalConstraints: nil), completionHandler: { (sessionDescription, error) in
remoteParticipantPublished.peerConnection!.setLocalDescription(sessionDescription!, completionHandler: {(error) in
print("Remote Peer Local Description set")
})
var remoteOfferParams: [String: String] = [:]
remoteOfferParams["sdpOffer"] = sessionDescription!.description
remoteOfferParams["sender"] = remoteParticipantPublished.id! + "_webcam"
self.sendJson(method: "receiveVideoFrom", params: remoteOfferParams)
})
self.peersManager.remotePeer!.delegate = self.peersManager
}
func participantLeft(params: Dictionary<String, Any>) {
print("participants", participants)
print("params", params)
let participantId = params["connectionId"] as! String
participants[participantId]!.peerConnection!.close()
if UIDevice().userInterfaceIdiom == .phone && UIScreen.main.nativeBounds.height == 2436 {
let renderer = RTCEAGLVideoView(frame: self.views[0].frame)
//REMOVE VIEW
if(self.peersManager.remoteStreams.count > 0){
let videoTrack = self.peersManager.remoteStreams[0].videoTracks[0]
videoTrack.remove(renderer)
if let index = self.participants.keys.index(of: participantId) {
let i = participants.distance(from: participants.startIndex, to: index)
self.views[i].willRemoveSubview(renderer)
self.names[i].text = ""
self.names[i].backgroundColor = UIColor.clear
}
}
participants.removeValue(forKey: participantId)
}else
{
#if arch(arm64)
let renderer = RTCMTLVideoView(frame: self.views[0].frame)
#else
let renderer = RTCEAGLVideoView(frame: self.views[self.participants.count-1].frame)
#endif
//REMOVE VIEW
if(self.peersManager.remoteStreams.count > 0){
let videoTrack = self.peersManager.remoteStreams[0].videoTracks[0]
videoTrack.remove(renderer)
if let index = self.participants.keys.index(of: participantId) {
let i = participants.distance(from: participants.startIndex, to: index)
self.views[i].willRemoveSubview(renderer)
self.names[i].text = ""
self.names[i].backgroundColor = UIColor.clear
}
participants.removeValue(forKey: participantId)
}
}
}
func saveIceCandidate(json: Dictionary<String, Any>, endPointName: String?) {
let iceCandidate = RTCIceCandidate(sdp: json["candidate"] as! String, sdpMLineIndex: json["sdpMLineIndex"] as! Int32, sdpMid: json["sdpMid"] as? String)
if (endPointName == nil || participants[endPointName!] == nil) {
self.localPeer = self.peersManager.localPeer
self.localPeer!.add(iceCandidate)
} else {
participants[endPointName!]!.peerConnection!.add(iceCandidate)
}
}
func sendJson(method: String, params: [String: String]) {
let json: NSMutableDictionary = NSMutableDictionary()
json.setValue(method, forKey: JSONConstants.Method)
json.setValue(id, forKey: JSONConstants.Id)
id += 1
json.setValue(params, forKey: JSONConstants.Params)
json.setValue(JSON_RPCVERSION, forKey: JSONConstants.JsonRPC)
let jsonData: NSData
do {
jsonData = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions()) as NSData
let jsonString = NSString(data: jsonData as Data, encoding: String.Encoding.utf8.rawValue)! as String
print("Sending = \(jsonString)")
socket.write(string: jsonString)
} catch _ {
print ("JSON Failure")
}
}
func addIceCandidate(iceCandidateParams: [String: String]) {
iceCandidatesParams!.append(iceCandidateParams)
}
func embedView(_ view: UIView, into containerView: UIView) {
containerView.addSubview(view)
containerView.backgroundColor = UIColor.white.withAlphaComponent(0.8)
view.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
}
}
Any One who has used this code, needed help here. I have Participant ID what is the way to get Audio Video streams, Do I need to make some connection(Peer connection)? or I will get these streams in the socket connection response only.
Today again one combine problem I currently run in and I hope that someone of you can help. How can normal unit tests be written for ObservableObjects classes which contain #Published attributes? How can I subscribe in my test to them to get the result object which I can assert?
The injected mock for the web service works correctly, loadProducts() function set exactly the same elements from the mock in the fetchedProducts array.
But I don't know currently how to access this array in my test after it is filled by the function because it seems that I cannot work with expectations here, loadProducts() has no completion block.
The code looks like this:
class ProductsListViewModel: ObservableObject {
let getRequests: GetRequests
let urlService: ApiUrls
private let networkUtils: NetworkRequestUtils
let productsWillChange = ObservableObjectPublisher()
#Published var fetchedProducts = [ProductDTO]()
#Published var errorCodeLoadProducts: Int?
init(getRequestsHelper: GetRequests, urlServiceClass: ApiUrls = ApiUrls(), utilsNetwork: NetworkRequestUtils = NetworkRequestUtils()) {
getRequests = getRequestsHelper
urlService = urlServiceClass
networkUtils = utilsNetwork
}
// nor completion block in the function used
func loadProducts() {
let urlForRequest = urlService.loadProductsUrl()
getRequests.getJsonData(url: urlForRequest) { [weak self] (result: Result<[ProductDTO], Error>) in
self?.isLoading = false
switch result {
case .success(let productsArray):
// the products filled async here
self?.fetchedProducts = productsArray
self?.errorCodeLoadProducts = nil
case .failure(let error):
let errorCode = self?.networkUtils.errorCodeFrom(error: error)
self?.errorCodeLoadProducts = errorCode
print("error: \(error)")
}
}
}
}
The test I try to write looks like this at the moment:
import XCTest
#testable import MyProject
class ProductsListViewModelTest: XCTestCase {
var getRequestMock: GetRequests!
let requestManagerMock = RequestManagerMockLoadProducts()
var productListViewModel: ProductsListViewModel!
override func setUp() {
super.setUp()
getRequestMock = GetRequests(networkHelper: requestManagerMock)
productListViewModel = ProductsListViewModel(getRequestsHelper: getRequestMock)
}
func test_successLoadProducts() {
let loginDto = LoginResponseDTO(token: "token-token")
UserDefaults.standard.save(loginDto, forKey: CommonConstants.persistedLoginObject)
productListViewModel.loadProducts()
// TODO access the fetchedProducts here somehow and assert them
}
}
The Mock looks like this:
class RequestManagerMockLoadProducts: NetworkRequestManagerProtocol {
var isSuccess = true
func makeNetworkRequest<T>(urlRequestObject: URLRequest, completion: #escaping (Result<T, Error>) -> Void) where T : Decodable {
if isSuccess {
let successResultDto = returnedProductedArray() as! T
completion(.success(successResultDto))
} else {
let errorString = "Cannot create request object here"
let error = NSError(domain: ErrorDomainDescription.networkRequestDomain.rawValue, code: ErrorDomainCode.unexpectedResponseFromAPI.rawValue, userInfo: [NSLocalizedDescriptionKey: errorString])
completion(.failure(error))
}
}
func returnedProductedArray() -> [ProductDTO] {
let product1 = ProductDTO(idFromBackend: "product-1", name: "product-1", description: "product-description", price: 3.55, photo: nil)
let product2 = ProductDTO(idFromBackend: "product-2", name: "product-2", description: "product-description-2", price: 5.55, photo: nil)
let product3 = ProductDTO(idFromBackend: "product-3", name: "product-3", description: "product-description-3", price: 8.55, photo: nil)
return [product1, product2, product3]
}
}
Maybe this article can help you
Testing your Combine Publishers
To solve your issue I will use code from my article
typealias CompetionResult = (expectation: XCTestExpectation,
cancellable: AnyCancellable)
func expectValue<T: Publisher>(of publisher: T,
timeout: TimeInterval = 2,
file: StaticString = #file,
line: UInt = #line,
equals: [(T.Output) -> Bool])
-> CompetionResult {
let exp = expectation(description: "Correct values of " + String(describing: publisher))
var mutableEquals = equals
let cancellable = publisher
.sink(receiveCompletion: { _ in },
receiveValue: { value in
if mutableEquals.first?(value) ?? false {
_ = mutableEquals.remove(at: 0)
if mutableEquals.isEmpty {
exp.fulfill()
}
}
})
return (exp, cancellable)
}
your test needs to use this function
func test_successLoadProducts() {
let loginDto = LoginResponseDTO(token: "token-token")
UserDefaults.standard.save(loginDto, forKey: CommonConstants.persistedLoginObject)
/// The expectation here can be extended as needed
let exp = expectValue(of: productListViewModel .$fetchedProducts.eraseToAnyPublisher(), equals: [{ $0[0].idFromBackend == "product-1" }])
productListViewModel.loadProducts()
wait(for: [exp.expectation], timeout: 1)
}
The easy and clearest way for me is simply to test #published var after X seconds. An example bellow :
func test_successLoadProducts() {
let loginDto = LoginResponseDTO(token: "token-token")
UserDefaults.standard.save(loginDto, forKey: CommonConstants.persistedLoginObject)
productListViewModel.loadProducts()
// TODO access the fetchedProducts here somehow and assert them
let expectation = XCTestExpectation()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
XCTAssertEqual(self.productListViewModel.fetchedProducts, ["Awaited values"])
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
}
I hope that helps !
I want to delete a post, if the post has equal/more then 5 dislikes. I implemented the counter part and this works already. But the post will not be deleted even if I have 6 dislikes like on this post below:
Here you can see my database.
This is the code, I want to delete the posts after 5 dislikes:
// Dislike Button
func addTapGestureToDislikeImageView() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleDidTapDislike))
dislikeImageView.addGestureRecognizer(tapGesture)
dislikeImageView.isUserInteractionEnabled = true
}
#objc func handleDidTapDislike() {
guard let postId = post?.id else { return }
PostApi.shared.incrementDislikes(postId: postId, onSuccess: { (post) in
self.updateDislike(post: post)
self.post?.dislikes = post.dislikes
self.post?.isDisliked = post.isDisliked
self.post?.dislikeCount = post.dislikeCount
}, onError: { (errorMessage) in
ProgressHUD.showError(errorMessage)
})
}
func updateDislike(post: PostModel) {
if post.isDisliked == false || post.dislikes == nil {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-50")
} else {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-filled-50")
}
guard let count = post.dislikeCount else { return }
if count >= 5 {
deletePost()
}
}
func deletePost() {
// Remove the post from the DB
let ref = Database.database().reference()
ref.child("posts").child((post?.id)!).removeValue { error,ref in
if error != nil {
print(error!.localizedDescription)
}
}
}
Here I increment Dislikes:
func incrementDislikes(postId id: String, onSuccess: #escaping (PostModel) -> Void, onError: #escaping (_ errorMessage: String?) -> Void) {
let postRef = REF_POSTS.child(id)
postRef.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = UserApi.shared.CURRENT_USER_ID {
var dislikes: Dictionary<String, Bool>
dislikes = post["dislikes"] as? [String : Bool] ?? [:]
var dislikeCount = post["dislikeCount"] as? Int ?? 0
if let _ = dislikes[uid] {
// Unstar the post and remove self from stars
dislikeCount -= 1
dislikes.removeValue(forKey: uid)
} else {
// Star the post and add self to stars
dislikeCount += 1
dislikes[uid] = true
}
post["dislikeCount"] = dislikeCount as AnyObject?
post["dislikes"] = dislikes as AnyObject?
// Set value and report transaction success
currentData.value = post
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
onError(error.localizedDescription)
}
guard let dic = snapshot?.value as? [String: Any] else { return }
guard let postId = snapshot?.key else { return }
let updatePost = PostModel(dictionary: dic, key: postId)
onSuccess(updatePost)
}
}
Thanks for your help!
I think you need to share more information, or any error you see, or debug it and test where the action stops. But try this one maybe it will work.
#objc func handleDidTapDislike() {
guard let postId = post?.id else { return }
PostApi.shared.incrementDislikes(postId: postId, onSuccess: { (post) in
self.updateDislike(post: post)
self.post?.dislikes = post.dislikes
self.post?.isDisliked = post.isDisliked
self.post?.dislikeCount = post.dislikeCount
if post.dislikeCount > 4 {
deletePost()
}
}, onError: { (errorMessage) in
ProgressHUD.showError(errorMessage)
})}
and then edit updateDislike() function
func updateDislike(post: PostModel) {
if post.isDisliked == false || post.dislikes == nil {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-50")
} else {
dislikeImageView.image = UIImage(named: "icons8-gefaellt-nicht-filled-50")
}
}
I am trying to clean my app from memory leaks and I have a few problems understanding this
Why Alamofire function Request.serializeResponseJSON is called 30 seconds after I've launched app: I did not touch anything or navigate anywhere, the screen was static.
Why does it leaks?
Why does my code leaks?
I get same leaks when the screen have loaded.
What I've tried so far:
Autoreleasepool;
Appending to and initialising arrays in every way that possible;
Changing all variable (class, func) to be optional/not
optional/weak;
Initialising classes in UIViewController;
Initialising classes in main thread;
Searching these problems in the internet.
I've found out, using Xcode memory tool, that it is somehow connected with _ContiguousArrayStorage, but I do not understand how and what is it actually.
I am out of any ideas what is wrong here. Any tips would be much appreciated.
Here is all related code:
My general API request
public func requestWithLocation(_ httpmethod: Alamofire.HTTPMethod, URL: String, parameters: [String: AnyObject]?, completionHandler: #escaping CompletionHandler) -> (){
var header: HTTPHeaders = [:]
var location: [String: Double] = [:]
let locationManager = CLLocationManager()
if (CLLocationManager.authorizationStatus() == .authorizedWhenInUse
|| CLLocationManager.authorizationStatus() == .authorizedAlways) && locationManager.location != nil {
location = [
"lon" : locationManager.location!.coordinate.longitude,
"lat" : locationManager.location!.coordinate.latitude
]
}
if User.sharedInstance.token != "" {
header["Authorization"] = User.sharedInstance.token
}
var parametersWithLocation = parameters ?? [:]
parametersWithLocation["location"] = location as AnyObject
Alamofire.request("\(serverAddress)/\(URL)", method: httpmethod, parameters: parametersWithLocation, encoding: JSONEncoding.default, headers: header).validate().responseJSON { response in
var data: JSON?
if response.result.value != nil {
data = JSON(response.result.value!)
}
if User.sharedInstance.token == "" {
User.sharedInstance.token = response.response?.allHeaderFields["Authorization"] as! String
} else {
if let header = response.response?.allHeaderFields["Authorization"] as? String {
if User.sharedInstance.token != header {
User.sharedInstance.token = header
}
}
}
completionHandler(data, response.result.error as NSError?)
}
}
My screen request
class func requestMainScreen(handler: #escaping ([ShortRestaurant], [ShortRestaurant], [ShortRestaurant]) -> ()) {
var dataForBestChoise: [ShortRestaurant] = []
var dataForTop: [ShortRestaurant] = []
var dataForNearest: [ShortRestaurant] = []
let group = DispatchGroup()
group.enter()
APIModel.sharedInstance.requestWithLocation(.post, URL: "restaurants/near", parameters: nil, completionHandler: {(data, error) in
guard let `data` = data else {
group.leave()
return
}
for JSON in data["restaurants"].arrayValue {
dataForNearest.append(ShortRestaurant.initFromJSON(JSON)) //here is leak
}
group.leave()
})
group.enter()
APIModel.sharedInstance.requestWithLocation(.post, URL: "restaurants/top", parameters: nil, completionHandler: {(data, error) in
guard let `data` = data else {
group.leave()
return
}
for JSON in data["restaurants"].arrayValue {
dataForTop.append(ShortRestaurant.initFromJSON(JSON))//here is leak
}
group.leave()
})
group.enter()
APIModel.sharedInstance.requestWithLocation(.post, URL: "restaurants/personal", parameters: nil, completionHandler: {(data, error) in
guard let `data` = data else {
group.leave()
return
}
for JSON in data["restaurants"].arrayValue {
dataForBestChoise.append(ShortRestaurant.initFromJSON(JSON)) //here is leak
}
group.leave()
})
group.notify(queue: DispatchQueue.main) {
handler(dataForBestChoise, dataForTop, dataForNearest)
}
}
My classes (I know this kind of initialisation is kinda wrong, but I'd changed to init(data: JSON) - did not help:
class func initFromJSON(_ data: JSON) -> ShortRestaurant {
let restaurant = ShortRestaurant()
restaurant.id = data["id"].stringValue
restaurant.name = data["name"].stringValue
restaurant.image = data["img"].stringValue
restaurant.description = data["shortDesc"].stringValue
restaurant.nameOfMetrostatin = data["address"]["metro"]["name"].stringValue
restaurant.mapType = data["mapType"].stringValue
restaurant.address = data["address"]["street"].stringValue
restaurant.longitude = data["address"]["location"][0].doubleValue
restaurant.latitude = data["address"]["location"][1].doubleValue
restaurant.phone = data["phone"].stringValue
restaurant.workTime = data["currentWork"].stringValue
restaurant.avarageBill = data["price"].stringValue
restaurant.peopleInfo = data["croud"].stringValue
restaurant.rating = data["rating"].stringValue
restaurant.ratingTrend = data["trend"].stringValue
restaurant.distance = data["distance"].doubleValue
restaurant.isFavourited = data["isFavourited"].bool ?? false
restaurant.specialOfferDescription = data["discounts"]["name"].string
restaurant.specialOfferName = data["discounts"]["type"].string
restaurant.alertText = data["label"]["name"].string
restaurant.alertIcon = data["label"]["type"].string
restaurant.alertBackground = data["label"]["color"].string
restaurant.avaliableDates = ReservationSchedule.initArrayFrom(data: data["availableDates"])
restaurant.avaliableTimes = data["scheduleRes"].arrayObject as? [String] ?? []
restaurant.doesHaveDiscount = data["discounts"]["id"].string != nil
restaurant.doesHaveEvent = data["events"]["id"].string != nil
restaurant.weeklyTop = data["weeklyTop"].bool ?? false
restaurant.monthlyTop = data["monthlyTop"].bool ?? false
restaurant.yearTop = data["yearTop"].bool ?? false
restaurant.isActive = data["isActive"].bool ?? true
return restaurant
}
Array of these leaks:
class ReservationSchedule {
var description: String
var data: String
var dayTitle: String
var fullTitle: String
init(data: JSON) {
self.data = data["value"].stringValue
self.dayTitle = data["day"].stringValue
self.description = data["label"].stringValue
self.fullTitle = data["title"].stringValue
}
class func initArrayFrom(data: JSON) -> [ReservationSchedule] {
var schedule: [ReservationSchedule] = []
for day in data.arrayValue {
schedule.append(ReservationSchedule.init(data: day)) //here is leak
}
return schedule
}
}
Have you tried setting URLCache time to request, This will remove the the cache of request and free's the memory