WEBRTC: Local streams are visible on OPENVIDU WEB(other Participant) end But Remote Streams are coming NIL on my end locally swift iOS - swift
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.
Related
Alamofire request not being executed after adding one parameter
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 } } }
How do I store values by referring to classes in FireStore
I'm creating a chat app using the MessageKit library in Swift 4.2 and FireStore. My problem is that I can't store data acquired by real time communication using addSnapshotListener in the Message class and confirmed the contents of the class, but I do not think it is wrong. messageListener is working properly. When handleDocumentChange is executed, it returns nil. I checked the return value with document.data (), but the value returned. How do I store values by referring to classes? guard var message = Message (document: change.document) else { print ("return Message") return } The data entered for FireStore is as follows { "channels": [{ "MOuL1sdbrnh0x1zGuXn7": { // channel id "name": "Puppies", "thread": [{ "3a6Fo5rrUcBqhUJcLsP0": { // message id "content": "Wow, that's so cute!", "created": "May 12, 2018 at 10:44:11 PM UTC-5", "senderID": "YCrPJF3shzWSHagmr0Zl2WZFBgT2", "senderUsername": "naturaln0va", "recipientProfilePictureURL":"URL", "recipientID":"ezample", "recipientUsername" :"A" "recipientProfilePictureURL":"aaaaa" }, }] }, }] } That's my message class: class Message: MessageType { var id: String? var sentDate: Date var kind: MessageKind lazy var sender: Sender = Sender(id: atcSender.uid ?? "No Id", displayName: atcSender.uid ?? "No Name") var atcSender: User var recipient: User var messageId: String { return id ?? UUID().uuidString } var image: UIImage? = nil var downloadURL: URL? = nil let content: String init(messageId: String, messageKind: MessageKind, createdAt: Date, atcSender: User, recipient: User) { self.id = messageId self.kind = messageKind self.sentDate = createdAt self.atcSender = atcSender self.recipient = recipient switch messageKind { case .text(let text): self.content = text default: self.content = "" } } init(user: User, image: UIImage) { self.image = image content = "" sentDate = Date() id = nil self.kind = MessageKind.text("xxx") self.atcSender = user self.recipient = user } init?(document: QueryDocumentSnapshot) { let data = document.data() guard let sentDate = data["created"] as? Date else { return nil } guard let senderID = data["senderID"] as? String else { return nil } guard let senderUsername = data["senderUsername"] as? String else { return nil } guard let senderProfilePictureURL = data["senderProfilePictureURL"] as? String else { return nil } guard let recipientID = data["recipientID"] as? String else { return nil } guard let recipientUsername = data["recipientUsername"] as? String else { return nil } guard let recipientProfilePictureURL = data["recipientProfilePictureURL"] as? String else { return nil } id = document.documentID self.sentDate = sentDate self.atcSender = User(uid: senderID, username: senderUsername, firstname: "", lastname: "", email: "", profileUrl: senderProfilePictureURL) self.recipient = User(uid: recipientID, username: recipientUsername, firstname: "", lastname: "", email: "", profileUrl: recipientProfilePictureURL) if let content = data["content"] as? String { self.content = content downloadURL = nil } else if let urlString = data["url"] as? String, let url = URL(string: urlString) { downloadURL = url self.content = "" } else { return nil } self.kind = MessageKind.text(content) } required init(jsonDict: [String: Any]) { fatalError() } var description: String { return self.messageText } var messageText: String { switch kind { case .text(let text): return text default: return "" } } var channelId: String { let id1 = (recipient.username ?? "") let id2 = (atcSender.username ?? "") return id1 < id2 ? id1 + id2 : id2 + id1 } } extension Message: DatabaseRepresentation { var representation: [String : Any] { var rep: [String : Any] = [ "created": sentDate, "senderID": atcSender.uid ?? "", "senderUsername": atcSender.username ?? "", "senderProfilePictureURL": atcSender.profileUrl ?? "", "recipientID": recipient.uid ?? "", "recipientUsername": recipient.username ?? "", "recipientProfilePictureURL": recipient.profileUrl ?? "", ] if let url = downloadURL { rep["url"] = url.absoluteString } else { rep["content"] = content } return rep }}extension Message: Comparable { static func == (lhs: Message, rhs: Message) -> Bool { return lhs.id == rhs.id } static func < (lhs: Message, rhs: Message) -> Bool { return lhs.sentDate < rhs.sentDate } } ChatViewController: import UIKit import MessageKit import MessageInputBar import Firebase import FirebaseFirestore import FirebaseAuth class ChatViewController: MessagesViewController { private let db = Firestore.firestore() private var reference: CollectionReference? private var messages: [Message] = [] private var messageListener: ListenerRegistration? private let user: User private let channel: Channel let uid = Auth.auth().currentUser?.uid init(user: User, channel: Channel) { self.user = user self.channel = channel super.init(nibName: nil, bundle: nil) title = channel.name } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { messageListener?.remove() } override func viewDidLoad() { super.viewDidLoad() guard let id = channel.id else { navigationController?.popViewController(animated: true) return } reference = db.collection(["channels", id, "thread"].joined(separator: "/")) // reference?.addSnapshotListener { querySnapshot, error in // guard let snapshot = querySnapshot else { // print("Error fetching snapshots: \(error!)") // return // } // snapshot.documentChanges.forEach { diff in // if (diff.type == .added) { // print("New city: \(diff.document.data())") // } // if (diff.type == .modified) { // print("Modified city: \(diff.document.data())") // } // if (diff.type == .removed) { // print("Removed city: \(diff.document.data())") // } // } // } messageListener = reference?.addSnapshotListener { querySnapshot, error in guard let snapshot = querySnapshot else { print("Error listening for channel updates: \(error?.localizedDescription ?? "No error")") return } snapshot.documentChanges.forEach { change in self.handleDocumentChange(change) print("handleDocumentChange") } } self.navigationItem.title = title messageInputBar.delegate = self messagesCollectionView.messagesDataSource = self messagesCollectionView.messagesLayoutDelegate = self messagesCollectionView.messagesDisplayDelegate = self messageInputBar.sendButton.tintColor = UIColor.lightGray //scrollsToBottomOnKeyboardBeginsEditing = true // default false //maintainPositionOnKeyboardFrameChanged = true // default false } private func save(_ message: Message) { reference?.addDocument(data: message.representation) { error in if let e = error { print("Error sending message: \(e.localizedDescription)") return } self.messagesCollectionView.scrollToBottom() } } private func insertNewMessage(_ message: Message) { guard !messages.contains(message) else { return } messages.append(message) messages.sort() let isLatestMessage = messages.index(of: message) == (messages.count - 1) let shouldScrollToBottom = messagesCollectionView.isAtBottom && isLatestMessage messagesCollectionView.reloadData() if shouldScrollToBottom { DispatchQueue.main.async { self.messagesCollectionView.scrollToBottom(animated: true) } } } private func handleDocumentChange(_ change: DocumentChange) { guard var message = Message(document: change.document) else { print("return Message") return } switch change.type { case .added: print("add Message") insertNewMessage(message) default: break } } } Console prints New city: ["senderUsername": panyayan, "senderID": RAMIqHAVeoU4TKkm3FDw7XUwgym2, "created": FIRTimestamp:seconds=1544623185 nanoseconds=412169933>, "recipientUsername": panyayan, "content": AAA, "recipientID": RAMIqHAVeoU4TKkm3FDw7XUwgym2, "recipientProfilePictureURL": https:, "senderProfilePictureURL": https:] return Message handleDocumentChange
Dont know if you still need it but: The document.data() field created is a FIRTimestamp. When you try to init the Message object you use guard let sentDate = data["created"] as? Date else { return nil } Thats might be a reason, why your object is nil. Try something like guard let sentTimestamp = data["created"] as? Timestamp else { return nil } ... self.sentDate = sentTimestamp.dateValue()
Deleting Posts from Firebase
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") } }
swift CGPDFDocument parsing
I'm trying to use Swift to parse the contents of PDF documents, following Apple's programming guide (in which all the examples are ObjC...) let filepath = "/Users/ben/Desktop/Test.pdf" let localUrl = filepath as CFString if let pdfURL = CFURLCreateWithFileSystemPath(nil, localUrl, CFURLPathStyle.cfurlposixPathStyle, false) { if let pdf = CGPDFDocument(pdfURL) { if let inf = pdf.info { CGPDFDictionaryApplyFunction(inf, { (key, object, info) -> Void in print("\(key), \(object), \(info)") }, nil) } if let cat = pdf.catalog { CGPDFDictionaryApplyFunction(cat, { (key, object, info) -> Void in print("\(key), \(object), \(info)") }, nil) } } } While this seems to produce some results, it's just strings of hex digits. 0x00007ff29f43ce00, 0x00007ff29f492bd0, nil 0x00007ff29f443b60, 0x00007ff29f492cd0, nil 0x00007ff29f482590, 0x00007ff29f492dd0, nil 0x00007ff29f482a40, 0x00007ff29f492ed0, nil 0x00007ff29f482e30, 0x00007ff29f492fe0, nil 0x00007ff29f47da20, 0x00007ff29f4930e0, nil 0x00007ff29f474ac0, 0x00007ff29f842b50, nil 0x00007ff29f43f5d0, 0x00007ff29f842bf0, nil 0x00007ff29f485eb0, 0x00007ff29f842a60, nil 0x00007ff29f482f70, 0x00007ff29f842ab0, nil 0x00007ff29f48b1c0, 0x00007ff29f48f6d0, nil So how do I get the actual data? Ideally, I'm trying to get at the document metadata and things like fonts contained.
Swift 4 - Here is an updated version of Daniel's excellent example which compiles in Swift 4. import Foundation import Quartz print("Hello, World!") func printPDFKeys( key: UnsafePointer<Int8>, object: CGPDFObjectRef) { //, info: UnsafeMutableRawPointer) { // let _: CGPDFDictionaryRef = CGPDFDictionaryRef(info) let keyString = String(cString: UnsafePointer<CChar>(key), encoding: .isoLatin1) let objectType = CGPDFObjectGetType(object) if keyString == nil { return } print("key \(keyString!) is present in dictionary, type \(objectType.rawValue)") var ptrObjectValue:UnsafePointer<Int8>? = nil switch objectType { // ObjectType is enum of: // Null // Boolean // Integer // Real // Name // String // Array // Dictionary // Stream case .boolean: // Boolean var objectBoolean:CGPDFBoolean = 0 if CGPDFObjectGetValue(object, objectType, &objectBoolean) { let testbool = NSNumber(value: objectBoolean) print("Boolean value \(testbool)") } case .integer: // Integer var objectInteger:CGPDFInteger? = nil if CGPDFObjectGetValue(object, objectType, &objectInteger) { print("Integer value \(objectInteger)") } case .real: // Real var objectReal:CGPDFReal? = nil if CGPDFObjectGetValue(object, objectType, &objectReal) { print("Real value \(objectReal)") } case .name: // Name if (CGPDFObjectGetValue(object, objectType, &ptrObjectValue)) { let stringName = String(cString: UnsafePointer<CChar>(ptrObjectValue!), encoding: String.Encoding.isoLatin1) print("Name value: \(stringName!)") } case .string: // String _ = CGPDFObjectGetValue(object, objectType, &ptrObjectValue) let stringValue = CGPDFStringCopyTextString(OpaquePointer(ptrObjectValue!)) print("String value: \(stringValue!)") case .array: // Array print("Array") var objectArray:CGPDFArrayRef? = nil if (CGPDFObjectGetValue(object, objectType, &objectArray)) { print("array: \(arrayFromPDFArray(pdfArray: objectArray!))") } case .dictionary: // Dictionary var objectDictionary:CGPDFDictionaryRef? = nil if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) { let count = CGPDFDictionaryGetCount(objectDictionary!) print("Found dictionary with \(count) entries") if !(keyString == "Parent") && !(keyString == "P") { //catalogLevel = catalogLevel + 1 CGPDFDictionaryApplyFunction(objectDictionary!, { (key, object, info) -> Void in printPDFKeys(key: key, object: object) // , info: info) }, nil) // CGPDFDictionaryApplyFunction(objectDictionary!, printPDFKeys as! CGPDFDictionaryApplierFunction, nil) //catalogLevel = catalogLevel - 1 } } case .stream: // Stream print("Stream") var objectStream:CGPDFStreamRef? = nil if (CGPDFObjectGetValue(object, objectType, &objectStream)) { let _: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream! )! var fmt: CGPDFDataFormat = .raw let streamData: CFData = CGPDFStreamCopyData(objectStream!, &fmt)!; let data = NSData(data: streamData as Data) let dataString = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) let dataLength: Int = CFDataGetLength(streamData) print("data stream (length=\(dataLength)):") if dataLength < 400 { print(dataString) } } default: print("Null") } } // convert a PDF array into an objC one func arrayFromPDFArray(pdfArray: CGPDFArrayRef ) -> NSMutableArray { var _:Int = 0 let tmpArray: NSMutableArray = NSMutableArray() let count = CGPDFArrayGetCount(pdfArray) for i in 0..<count { var value:CGPDFObjectRef? = nil if (CGPDFArrayGetObject(pdfArray, i, &value)) { if let object = objectForPDFObject(object: value!) { tmpArray.add(object) } } } return tmpArray } func objectForPDFObject( object: CGPDFObjectRef) -> AnyObject? { let objectType: CGPDFObjectType = CGPDFObjectGetType(object) var ptrObjectValue:UnsafePointer<Int8>? = nil switch (objectType) { case .boolean: // Boolean var objectBoolean = CGPDFBoolean() if CGPDFObjectGetValue(object, objectType, &objectBoolean) { let testbool = NSNumber(value: objectBoolean) return testbool } case .integer: // Integer var objectInteger = CGPDFInteger() if CGPDFObjectGetValue(object, objectType, &objectInteger) { return objectInteger as AnyObject } case .real: // Real var objectReal = CGPDFReal() if CGPDFObjectGetValue(object, objectType, &objectReal) { return objectReal as AnyObject } case .string: _ = CGPDFObjectGetValue(object, objectType, &ptrObjectValue) let stringValue = CGPDFStringCopyTextString(OpaquePointer(ptrObjectValue!)) return stringValue case .dictionary: // Dictionary var objectDictionary:CGPDFDictionaryRef? = nil if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) { let count = CGPDFDictionaryGetCount(objectDictionary!) print("In array, found dictionary with \(count) entries") CGPDFDictionaryApplyFunction(objectDictionary!, { (key, object, info) -> Void in printPDFKeys(key: key, object: object) // , info: info) }, nil) // CGPDFDictionaryApplyFunction(objectDictionary!, printPDFKeys as! CGPDFDictionaryApplierFunction, nil) } case .stream: // Stream var objectStream:CGPDFStreamRef? = nil if (CGPDFObjectGetValue(object, objectType, &objectStream)) { let _: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream! )! var fmt: CGPDFDataFormat = .raw let streamData: CFData = CGPDFStreamCopyData(objectStream!, &fmt)!; let data = NSData(data: streamData as Data) let dataString = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) print("data stream (length=\(CFDataGetLength(streamData))):") return dataString } default: return nil } return nil } func parse () { let filepath = ("~/Desktop/doc.pdf" as NSString).expandingTildeInPath let urlDocument = NSURL(fileURLWithPath: filepath) let myDocument = CGPDFDocument(urlDocument) if myDocument != nil { let numPages = myDocument?.numberOfPages print("Number of pages: \(numPages)") // Get complete catalog let myCatalog = myDocument?.catalog CGPDFDictionaryApplyFunction(myCatalog!, { (key, object, info) -> Void in printPDFKeys(key: key, object: object) // , info: info) }, nil) // CGPDFDictionaryApplyFunction(myCatalog!, printPDFKeys, nil) let myInfo = myDocument?.info CGPDFDictionaryApplyFunction(myInfo!, { (key, object, info) -> Void in printPDFKeys(key: key, object: object) // , info: info) }, nil) // CGPDFDictionaryApplyFunction(myInfo!, printPDFKeys, nil) } else { print("Cannot open PDF document") } } parse()
Your parsing retrieving high level dictionary and info data is correct, but you need to expand the decoding in CGPDFDictionaryApplyFunction to display the values of PDF data according their types (integer, string, array, dictionary, and so on). The syntax of the CGPDFDictionaryApplierFunction you are calling is: typealias CGPDFDictionaryApplierFunction = (UnsafePointer<Int8>, COpaquePointer, UnsafeMutablePointer<()>) -> Void Your program is displaying the pointers to the data, you could access the data values according their types as below (Swift 2): let filepath = "/Users/ben/Desktop/Test.pdf" let urlDocument = NSURL(fileURLWithPath: filepath) let myDocument = CGPDFDocumentCreateWithURL(urlDocument) if myDocument != nil { let numPages = CGPDFDocumentGetNumberOfPages(myDocument) print("Number of pages: \(numPages)") // Get complete catalog let myCatalog = CGPDFDocumentGetCatalog(myDocument) CGPDFDictionaryApplyFunction(myCatalog, printPDFKeys, nil) let myInfo = CGPDFDocumentGetInfo(myDocument) CGPDFDictionaryApplyFunction(myInfo, printPDFKeys, nil) } else { print("Cannot open PDF document") } In order to be called from the CGPDFDictionaryApplyFunction, the printPDFKeys is to be called as a global function (outside your main class), alternately you could insert the code in a closure of CGPDFDictionaryApplyFunction as in your example above. The below code is shortened and is not including complete protection against errors and null values. func printPDFKeys( key: UnsafePointer<Int8>, object: COpaquePointer, info: UnsafeMutablePointer<()>) { let contentDict: CGPDFDictionaryRef = CGPDFDictionaryRef(info) let keyString = String(CString: UnsafePointer<CChar>(key), encoding: NSISOLatin1StringEncoding) let objectType = CGPDFObjectGetType(object) if keyString == nil { return } print("key \(keyString!) is present in dictionary, type \(objectType.rawValue)") var ptrObjectValue = UnsafePointer<Int8>() switch objectType { // ObjectType is enum of: // Null // Boolean // Integer // Real // Name // String // Array // Dictionary // Stream case .Boolean: // Boolean var objectBoolean = CGPDFBoolean() if CGPDFObjectGetValue(object, objectType, &objectBoolean) { let testbool = NSNumber(unsignedChar: objectBoolean) print("Boolean value \(testbool)") } case .Integer: // Integer var objectInteger = CGPDFInteger() if CGPDFObjectGetValue(object, objectType, &objectInteger) { print("Integer value \(objectInteger)") } case .Real: // Real var objectReal = CGPDFReal() if CGPDFObjectGetValue(object, objectType, &objectReal) { print("Real value \(objectReal)") } case .Name: // Name if (CGPDFObjectGetValue(object, objectType, &ptrObjectValue)) { let stringName = String(CString: UnsafePointer<CChar>(ptrObjectValue), encoding: NSISOLatin1StringEncoding) print("Name value: \(stringName!)") } case .String: // String let valueFound = CGPDFObjectGetValue(object, objectType, &ptrObjectValue) let stringValue = CGPDFStringCopyTextString(COpaquePointer(ptrObjectValue)) print("String value: \(stringValue!)") case .Array: // Array print("Array") var objectArray = CGPDFArrayRef() if (CGPDFObjectGetValue(object, objectType, &objectArray)) { print("array: \(arrayFromPDFArray(objectArray))") } case .Dictionary: // Dictionary var objectDictionary = CGPDFDictionaryRef() if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) { let count = CGPDFDictionaryGetCount(objectDictionary) print("Found dictionary with \(count) entries") if !(keyString == "Parent") && !(keyString == "P") { //catalogLevel = catalogLevel + 1 CGPDFDictionaryApplyFunction(objectDictionary, printPDFKeys, nil) //catalogLevel = catalogLevel - 1 } } case .Stream: // Stream print("Stream") var objectStream = CGPDFStreamRef() if (CGPDFObjectGetValue(object, objectType, &objectStream)) { let dict: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream ) var fmt: CGPDFDataFormat = .Raw let streamData: CFDataRef = CGPDFStreamCopyData(objectStream, &fmt)!; let data = NSData(data: streamData) let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) let dataLength: Int = CFDataGetLength(streamData) print("data stream (length=\(dataLength)):") if dataLength < 400 { print(dataString) } } default: print("Null") } } // convert a PDF array into an objC one func arrayFromPDFArray(pdfArray: CGPDFArrayRef ) -> NSMutableArray { var i:Int = 0 var tmpArray: NSMutableArray = NSMutableArray() let count = CGPDFArrayGetCount(pdfArray) for i in 0..<count { var value = CGPDFObjectRef() if (CGPDFArrayGetObject(pdfArray, i, &value)) { if let object = objectForPDFObject(value) { tmpArray.addObject(object) } } } return tmpArray } func objectForPDFObject( object: CGPDFObjectRef) -> AnyObject? { let objectType: CGPDFObjectType = CGPDFObjectGetType(object) var ptrObjectValue = UnsafePointer<Int8>() switch (objectType) { case .Boolean: // Boolean var objectBoolean = CGPDFBoolean() if CGPDFObjectGetValue(object, objectType, &objectBoolean) { let testbool = NSNumber(unsignedChar: objectBoolean) return testbool } case .Integer: // Integer var objectInteger = CGPDFInteger() if CGPDFObjectGetValue(object, objectType, &objectInteger) { return objectInteger } case .Real: // Real var objectReal = CGPDFReal() if CGPDFObjectGetValue(object, objectType, &objectReal) { return objectReal } case .String: let valueFound = CGPDFObjectGetValue(object, objectType, &ptrObjectValue) let stringValue = CGPDFStringCopyTextString(COpaquePointer(ptrObjectValue)) return stringValue case .Dictionary: // Dictionary var objectDictionary = CGPDFDictionaryRef() if (CGPDFObjectGetValue(object, objectType, &objectDictionary)) { let count = CGPDFDictionaryGetCount(objectDictionary) print("In array, found dictionary with \(count) entries") CGPDFDictionaryApplyFunction(objectDictionary, printPDFKeys, nil) } case .Stream: // Stream var objectStream = CGPDFStreamRef() if (CGPDFObjectGetValue(object, objectType, &objectStream)) { let dict: CGPDFDictionaryRef = CGPDFStreamGetDictionary( objectStream ) var fmt: CGPDFDataFormat = .Raw let streamData: CFDataRef = CGPDFStreamCopyData(objectStream, &fmt)!; let data = NSData(data: streamData) let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) print("data stream (length=\(CFDataGetLength(streamData))):") return dataString } default: return nil } return nil }
Made a parser (based on previous answers) that crawls the PDF hierarchy and gives you a JSON. // Parse PDF into JSON. PDFParser.parse(pdfUrl: pdfFileURL, into: jsonFileURL) // Parse PDF into Dictionary. let pdf: [String:Any?] = PDFParser.parse(pdfUrl: pdfFileURL) Gives you: { "Catalog" : { "Pages<Dictionary>" : { "MediaBox<Array>" : [ 0, 0, 612, 792 ], "Type<Name>" : "Pages", "Kids<Array>" : [ { "Rotate<Integer>" : 0, "MediaBox<Array>" : [ 0, 0, 595.27499999999998, 841.88999999999999 ], "Parent<Dictionary>" : "<PARENT_NOT_SERIALIZED>", "Resources<Dictionary>" : { "ColorSpace<Dictionary>" : { "Cs1<Array>" : [ "ICCBased", { "N<Integer>" : 3, "Filter<Name>" : "FlateDecode", "Alternate<Name>" : "DeviceRGB", "Length<Integer>" : 2612 } ] } ... To get from CGPDFDocument (like original question): // Get document catalog. guard let document = CGPDFDocument(pdfFileURL as CFURL), let catalog = document.catalog else { return } // Parse into dictionary. let catalogDictionary = PDFParser.value(from: catalog) Gives you a pretty usual Swift dictionary. Console output: Optional(["Pages<Dictionary>": Optional({ "Count<Integer>" = 1; "Kids<Array>" = ( { "ArtBox<Array>" = ( "28.3465", "325.193", "393.389", "813.543" ); "Contents<Stream>" = { Data = "q Q q 0 0 595.276 841.89 re W n 1 0 1 0 k /Gs1 gs 201.8862 420.9449 m 201.8862\n473.8269 244.7562 516.6959 297.6372 516.6959 c 350.5192 516.6959 393.3892\n473.8269 393.3892 420.9449 c 393.3892 368.0629 350.5192 325.1939 297.6372\n325.1939 c 244.7562 325.1939 201.8862 368.0629 201.8862 420.9449 c f Q q 28.346 530.078 283.464 283.465\nre W n 0 0 0 1 k /Gs1 gs BT 12 0 0 12 28.3467 803.499 Tm /Tc1 1 Tf [ (h) 4\n(ttp://epp) 7 (z.eu) ] TJ ET Q"; "Filter<Name>" = FlateDecode; "Length<Integer>" = 237; }; "MediaBox<Array>" = ( 0, 0, "595.2760000000001", "841.89" ); "Parent<Dictionary>" = "<PARENT_NOT_SERIALIZED>"; "Resources<Dictionary>" = { "ExtGState<Dictionary>" = { "Gs1<Dictionary>" = { "OPM<Integer>" = 1; "Type<Name>" = ExtGState; }; }; ... ParsePDF.swift: // // PDFParser.swift // PDFParser // // Copyright (c) 2020 Geri Borbás http://www.twitter.com/_eppz // // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // import Foundation import PDFKit class PDFParser { /// Shorthand for type strings. static let namesForTypes: [CGPDFObjectType:String] = [ .null : "Null", .boolean : "Boolean", .integer : "Integer", .real : "Real", .name : "Name", .string : "String", .array : "Array", .dictionary : "Dictionary", .stream : "Stream", CGPDFObjectTypeObject : "Object", ] struct Message { static let parentNotSerialized = "<PARENT_NOT_SERIALIZED>" static let couldNotParseValue = "<COULD_NOT_PARSE_VALUE>" static let couldNotGetStreamData = "<COULD_NOT_GET_STREAM_DATA>" static let unknownStreamDataFormat = "<UNKNOWN_STREAM_DATA_FORMAT>" } /// Parse a PDF file into a JSON file. static func parse(pdfUrl: URL, into jsonURL: URL) { do { let pdf = PDFParser.parse(pdfUrl: pdfUrl) let data = try JSONSerialization.data(withJSONObject: pdf, options: .prettyPrinted) try data.write(to: jsonURL, options: []) } catch { print(error) } } /// Parse a PDF file into a JSON file. static func parse(pdfUrl: URL) -> [String:Any?] { // Document. guard let document = CGPDFDocument(pdfUrl as CFURL), let catalog = document.catalog, let info = document.info else { print("Cannot open PDF.") return [:] } // Parse. return [ "Catalog" : PDFParser.value(from: catalog), "Info" : PDFParser.value(from: info) ] } static func value(from object: CGPDFObjectRef) -> Any? { switch (CGPDFObjectGetType(object)) { case .null: return nil case .boolean: var valueRef: CGPDFBoolean = 0 if CGPDFObjectGetValue(object, .boolean, &valueRef) { return Bool(valueRef == 0x01) } case .integer: var valueRef: CGPDFInteger = 0 if CGPDFObjectGetValue(object, .integer, &valueRef) { return valueRef as Int } case .real: var valueRef: CGPDFReal = 0.0 if CGPDFObjectGetValue(object, .real, &valueRef) { return Double(valueRef) } case .name: var objectRefOrNil: UnsafePointer<Int8>? = nil if CGPDFObjectGetValue(object, .name, &objectRefOrNil), let objectRef = objectRefOrNil, let string = String(cString: objectRef, encoding: String.Encoding.isoLatin1) { return string } case .string: var objectRefOrNil: UnsafePointer<Int8>? = nil if CGPDFObjectGetValue(object, .string, &objectRefOrNil), let objectRef = objectRefOrNil, let stringRef = CGPDFStringCopyTextString(OpaquePointer(objectRef)) { return stringRef as String } case .array: var arrayRefOrNil: CGPDFArrayRef? = nil if CGPDFObjectGetValue(object, .array, &arrayRefOrNil), let arrayRef = arrayRefOrNil { var array: [Any] = [] for index in 0 ..< CGPDFArrayGetCount(arrayRef) { var eachObjectRef: CGPDFObjectRef? = nil if CGPDFArrayGetObject(arrayRef, index, &eachObjectRef), let eachObject = eachObjectRef, let eachValue = PDFParser.value(from: eachObject) { array.append(eachValue) } } return array } case .stream: var streamRefOrNil: CGPDFStreamRef? = nil if CGPDFObjectGetValue(object, .stream, &streamRefOrNil), let streamRef = streamRefOrNil, let streamDictionaryRef = CGPDFStreamGetDictionary(streamRef) { // Get stream dictionary. var streamNSMutableDictionary = NSMutableDictionary() Self.collectObjects(from: streamDictionaryRef, into: &streamNSMutableDictionary) var streamDictionary = streamNSMutableDictionary as! [String: Any?] // Get data. var dataString: String? = Message.couldNotGetStreamData var streamDataFormat: CGPDFDataFormat = .raw if let streamData: CFData = CGPDFStreamCopyData(streamRef, &streamDataFormat) { switch streamDataFormat { case .raw: dataString = String(data: NSData(data: streamData as Data) as Data, encoding: String.Encoding.utf8) case .jpegEncoded, .JPEG2000: dataString = NSData(data: streamData as Data).base64EncodedString() #unknown default: dataString = Message.unknownStreamDataFormat } } // Add to dictionary. streamDictionary["Data"] = dataString return streamDictionary } case .dictionary: var dictionaryRefOrNil: CGPDFDictionaryRef? = nil if CGPDFObjectGetValue(object, .dictionary, &dictionaryRefOrNil), let dictionaryRef = dictionaryRefOrNil { var dictionary = NSMutableDictionary() Self.collectObjects(from: dictionaryRef, into: &dictionary) return dictionary as! [String: Any?] } #unknown default: var dictionary = NSMutableDictionary() Self.collectObjects(from: object, into: &dictionary) return dictionary as! [String: Any?] } // No known case. return nil } static func collectObjects(from dictionaryRef: CGPDFDictionaryRef, into dictionaryPointer: UnsafeMutableRawPointer?) { CGPDFDictionaryApplyFunction( dictionaryRef, { (eachKeyPointer, eachObject, eachContextOrNil: UnsafeMutableRawPointer?) -> Void in // Unwrap dictionary. guard let dictionary = eachContextOrNil?.assumingMemoryBound(to: NSMutableDictionary.self).pointee else { return print("Could not unwrap dictionary.") } // Unwrap key. guard let eachKey = String(cString: UnsafePointer<CChar>(eachKeyPointer), encoding: .isoLatin1) else { return print("Could not unwrap key.") } // Type. guard let eachTypeName = PDFParser.namesForTypes[CGPDFObjectGetType(eachObject)] else { return print("Could not unwrap type.") } // Assemble. let eachDictionaryKey = "\(eachKey)<\(eachTypeName)>" as NSString // Skip parent. guard eachKey != "Parent" else { dictionary.setObject(Message.parentNotSerialized, forKey: eachDictionaryKey) return } // Parse value. guard let eachValue = PDFParser.value(from: eachObject) else { dictionary.setObject(Message.couldNotParseValue, forKey: eachDictionaryKey) fatalError("😭") // return } // Set. dictionary.setObject(eachValue, forKey: eachDictionaryKey) }, dictionaryPointer ) } }
Alamofire memory leaks Instruments
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