NSOperation has not deallocated - swift

I have
class BoxSizeViewDetailController: UIViewController {
weak var output: BoxSizeViewDetailControllerOutput?
#objc
private func save() {
SLPWait.show()
Thread.run {
let operation = MoveBoxWithSizeToAssemblyOperation(boxBarcode: self.boxBarcode, assemblyRef: self.assemblyRef, boxSize: self.boxSize)
operation.completion = { [weak self] error in
Thread.main {
SLPWait.hide()
if let error = error {
error.alert()
return
}
self?.output?.didSaved()
self?.pop()
}
}
let operationQueue = OperationQueue()
operationQueue.addOperations(operation, waitUntilFinished: true)
}
}
}
And
class MoveBoxWithSizeToAssemblyOperation: SLPAsyncOperation {
private let boxBarcode: String
private let assemblyRef: String
private let boxSize: BoxSize
var withSize: Bool = false
var completion: ((NSError?) -> Void)?
init(boxBarcode: String, assemblyRef: String, boxSize: BoxSize) {
self.boxBarcode = boxBarcode
self.assemblyRef = assemblyRef
self.boxSize = boxSize
}
deinit {
SLPLog.debug("Deinit", String(describing: self))
}
override func start() {
if isCancelled {
self.finish()
return
}
super.start()
}
override func main() {
defer {
self.finish()
}
let sendSizeOperation = SendSizeOperation(barcode: boxBarcode, boxSize: boxSize)
let moveBoxToAssemblyOperation = MoveBoxToAssemblyOperation(boxBarcode: boxBarcode, assemblyRef: assemblyRef)
sendSizeOperation.completion = { [unowned moveBoxToAssemblyOperation, weak self] error in
//Here is main thread
if let error = error {
moveBoxToAssemblyOperation.cancel()
self?.completion?(error)
}
}
moveBoxToAssemblyOperation.completion = { [weak self] error in
//Here is main thread
self?.completion?(error)
}
moveBoxToAssemblyOperation.addDependency(sendSizeOperation)
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .userInitiated
operationQueue.addOperations([sendSizeOperation, moveBoxToAssemblyOperation], waitUntilFinished: true)
}
}
And
public extension Thread {
class func run(execute: #escaping #convention(block) () -> Void) {
DispatchQueue.global(qos: .userInitiated).async(execute: execute)
}
class func run(execute: DispatchWorkItem) {
DispatchQueue.global(qos: .userInitiated).async(execute: execute)
}
class func main(execute: #escaping #convention(block) () -> Void) {
DispatchQueue.main.async(execute: execute)
}
class func main(execute: DispatchWorkItem) {
DispatchQueue.main.async(execute: execute)
}
}
So the problem is in this part of code
Thread.run {
let operation = MoveBoxWithSizeToAssemblyOperation(boxBarcode: self.boxBarcode, assemblyRef: self.assemblyRef, boxSize: self.boxSize)
operation.completion = { [weak self] error in
Thread.main {
SLPWait.hide()
if let error = error {
error.alert()
return
}
self?.output?.didSaved()
self?.pop()
}
}
let operationQueue = OperationQueue()
operationQueue.addOperations(operation, waitUntilFinished: true)
}
SLPWait.show() and SLPWait.hide() just show and hide an ActivityIndicator.
The operations MoveBoxWithSizeToAssemblyOperation is not dealloceted (sometimes) and sometimes is dealloceted. Why is that?
My way of thinking. Because I use Thread.run to dispatch from main queue, I think I need wait until "MoveBoxWithSizeToAssemblyOperation" completes and handle completion.
But "MoveBoxWithSizeToAssemblyOperation" completes and handle "defer { self.finish }" but not deallocated.
My misunderstanding questions:
I don't understand should I set waitUntilFinished" = true ? Because I block thread.run and not the main thread though MoveBoxToAssemblyOperation completes on main thread. Is the MoveBoxToAssemblyOperation block main thread when completes ?
When I write Thread.run { should I capture [weak self] or not ?
Why MoveBoxWithSizeToAssemblyOperation is not deallocated ?

Related

Error: No exact matches in call to instance method 'append'

Im trying to implement a stock API but get an error with ".append":
No exact matches in call to instance method 'append'.
I'm not familiar with it and need help to solve this problem
final class StockQuoteManager: QuoteManagerProtocol, ObservableObject {
#Published var quotes: [Quote] = []
func download(stocks: [String], completion: #escaping (Result<[Quote], NetworkError>) -> Void) {
var internalQuotes = [Quote]()
let downloadQueue = DispatchQueue(label: "com.app.dwonloadQueue")
let downloadGroup = DispatchGroup()
stocks.forEach { (stock) in
downloadGroup.enter()
let url = URL(string: API.quoteUrl(for: stock))!
NetworkManager<GlobalQuoteResponse>().fetch(from: url) { (result) in
switch result {
case .failure(let err):
print(err)
downloadQueue.async {
downloadGroup.leave()
}
case .success(let resp):
downloadQueue.async {
internalQuotes.append(resp.quote) // <-- ERROR
downloadGroup.leave()
}
}
}
}
downloadGroup.notify(queue: DispatchQueue.global()) {
completion(.success(internalQuotes))
DispatchQueue.main.async {
self.quotes.append(contentsOf: internalQuotes)
}
}
}
}

Swift: NotificationCenter's addObserver closure keeps getting triggered constantly

I have an AVPlayer to play an MP3 stream. I added an observer for when the stream is ended, the trouble is the closure seems to be stuck in a loop after it's triggered for the first time.
//// Variables
private var periodicTimeObserverToken: Any?
private var finishObserverToken: Any?
//// Setting up the player
self.playerItem = AVPlayerItem(url: filePath)
self.player = AVPlayer(playerItem: self.playerItem)
removePeriodicTimeObserver()
removePlayerFinishedObserver()
addPeriodicTimeObserver()
addPlayerFinishedObserver()
///
private func addPeriodicTimeObserver() {
periodicTimeObserverToken = self.player?.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.05, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: DispatchQueue.main) { [weak self] (CMTime) -> Void in
if self?.player?.currentItem?.status == .readyToPlay {
/// Calling some other external delegates
}
}
}
private func removePeriodicTimeObserver() {
if let token = periodicTimeObserverToken {
self.player?.removeTimeObserver(token)
periodicTimeObserverToken = nil
}
}
private func addPlayerFinishedObserver() {
finishObserverToken = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.pause()
}
}
private func removePlayerFinishedObserver() {
if let _ = finishObserverToken, let _player = self.player, let _playerCurrentItem = _player.currentItem {
NotificationCenter.default.removeObserver(_player)
NotificationCenter.default.removeObserver(_playerCurrentItem)
self.finishObserverToken = nil
}
}
public func endSession() {
player?.pause()
removePeriodicTimeObserver()
removePlayerFinishedObserver()
playerItem = nil
player = nil
}
The parent class calls endSession() after stream reaches the end, but self?.player?.pause() from addPlayerFinishedObserver() gets called non stop, anything inside the addObserver closure gets called continuously.
Am I doing something wrong?
I had to remove its token inside the closure:
private func addPlayerFinishedObserver() {
finishObserverToken = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.pause()
if let token = self?.finishObserverToken {
NotificationCenter.default.removeObserver(token)
}
}
}

Why does webSocketTask.receive never complete and how do I force completion in my Swift app?

I have a Swift app that uses a web socket to download stock price information from a public API. I send a message through the socket to subscribe to various stock price changes then wait for a continuous stream of messages to be received but when I turn off wifi the call to the message receive function, webSocketTask.receive, never returns. How can I force abort of the message receive function so that I alert the user that the network connection has been lost.
Here is my NetworkServices class;
import UIKit
import Network
protocol NetworkServicesDelegate: AnyObject {
func sendStockInfo(stocksInfo: [String: StockInfo])
}
final class NetworkServices: NSObject {
static let sharedInstance = NetworkServices()
var urlSession: URLSession?
var webSocketTask: URLSessionWebSocketTask?
var stocksInfo: [String: StockInfo] = [:]
var socketResults: [String: [StockInfo]] = [:]
weak var delegate: NetworkServicesDelegate?
var timer: Timer?
var stockSymbols: [String] = []
private let queue = DispatchQueue.global(qos: .background)
private let monitor = NWPathMonitor()
public private(set) var isConnected: Bool = false
public private(set) var connectionType: ConnectionType = .unknown
enum ConnectionType {
case wifi
case cellular
case wiredEthernet
case unknown
}
public func startMonitoring() {
monitor.start(queue: queue)
monitor.pathUpdateHandler = { path in
self.isConnected = path.status == .satisfied
self.getConnectionType(path)
print("DEBUG: isConnected = \(self.isConnected); connectionType = \(self.connectionType)")
if self.isConnected == false {
}
}
}
public func stopMonitoring() {
monitor.cancel()
}
private func getConnectionType(_ path: NWPath) {
if path.usesInterfaceType(.wifi) {
connectionType = .wifi
} else if path.usesInterfaceType(.cellular) {
connectionType = .cellular
} else if path.usesInterfaceType(.wiredEthernet) {
connectionType = .wiredEthernet
} else {
connectionType = .unknown
}
}
func fetchStockInfo(symbols: [String], delegate: CompanyPriceListVC) {
stockSymbols = symbols
self.delegate = delegate
let configuration = URLSessionConfiguration.default
configuration.waitsForConnectivity = false
urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
webSocketTask = urlSession?.webSocketTask(with: FINNHUB_SOCKET_STOCK_INFO_URL!)
webSocketTask?.delegate = self
webSocketTask!.resume()
for symbol in symbols {
let string = FINNHUB_SOCKET_MESSAGE_STRING + symbol + "\"}"
let message = URLSessionWebSocketTask.Message.string(string)
if isConnected {
webSocketTask!.send(message) { error in
if let error = error {
print("DEBUG: Error sending message: \(error)")
}
self.receiveMessage()
}
} else {
// Post notification to view controllers that connection has been lost
let name = Notification.Name(rawValue: isNotConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
// try to re-connect
// successful re-connect?
// No, keep trying. Yes, call send message method again
}
}
}
private func receiveMessage() {
if isConnected {
self.webSocketTask?.receive { result in
print("DEBUG: Inside closure.")
switch result {
case .failure(let error):
print("DEBUG: Error receiving message: \(error.localizedDescription)")
case .success(.string(let jsonData)):
guard let stockData = jsonData.data(using: .utf8) else { return }
self.socketResults = [:]
self.stocksInfo = [:]
let decoder = JSONDecoder()
do {
let socketData = try decoder.decode(SocketData.self, from: stockData)
guard let stockInfoData = socketData.data else { return }
for stockInfo in stockInfoData {
let symbol = stockInfo.symbol
if self.socketResults[symbol] == nil {
self.socketResults[symbol] = [StockInfo]()
}
self.socketResults[symbol]?.append(stockInfo)
}
for (symbol, stocks) in self.socketResults {
for item in stocks {
if self.stocksInfo[symbol] == nil {
self.stocksInfo[symbol] = item
} else if item.timestamp > self.stocksInfo[symbol]!.timestamp {
self.stocksInfo[symbol] = item
}
}
}
self.delegate?.sendStockInfo(stocksInfo: self.stocksInfo)
self.receiveMessage()
} catch {
print("DEBUG: Error converting JSON: \(error)")
}
default:
print("DEBUG: default")
}
}
print("DEBUG: Got here 1")
} else {
print("DEBUG: Got here 2")
// Post notification to view controllers that connection has been lost
let name = Notification.Name(rawValue: isNotConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
// try to re-connect.
// successful reconnect?
// No, keep trying. Yes, call receive message method again.
}
}
func closeWebSocketConnection() {
webSocketTask?.cancel(with: .goingAway, reason: nil)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("DEBUG: didCompleteWithError called: error = \(error)")
}
}
func fetchCompanyInfo(symbol: String, completion: #escaping (CompanyInfo?, UIImage?)->()) {
let urlString = FINNHUB_HTTP_COMPANY_INFO_URL_STRING + symbol + "&token=" + FINNHUB_API_TOKEN
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error fetching company info: \(error)")
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let companyInfo = try decoder.decode(CompanyInfo.self, from: data)
guard let logoURL = URL(string: companyInfo.logo) else { return }
let task = URLSession.shared.dataTask(with: logoURL) { data, response, error in
if let error = error {
print("Error fetching logo image: \(error)")
}
guard let data = data else { return }
guard let logoImage = UIImage(data: data) else { return }
completion(companyInfo, logoImage)
}
task.resume()
} catch {
print("Error decoding JSON: \(error)")
completion(nil, nil)
}
}
task.resume()
}
}
extension NetworkServices: URLSessionTaskDelegate, URLSessionWebSocketDelegate, URLSessionDelegate {
func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
print("DEBUG: inside taskIsWaitingForConnectivity")
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
print("DEBUG: didBecomeInvalidWithError: error = \(String(describing: error?.localizedDescription))")
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
let reasonString: String
if let reason = reason, let string = String(data: reason, encoding: .utf8) {
reasonString = string
} else {
reasonString = ""
}
print("DEBUG: didCloseWith called: close code is \(closeCode), reason is \(String(describing: reasonString))")
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("DEBUG: urlSessionDidFinishEvents called")
}
}
I have tried placing code in the pathUpdateHandler closure to cancel the task on the task object and finish current tasks and cancel on the URLSession object but neither works.
I needed to post a notification when path.status == .satisfied. The notification goes to the custom UIViewController class which then calls the FetchStockInfo(symbols:delegate) method.

Execute func after first func

self.werteEintragen() should start after weatherManager.linkZusammenfuegen() is done. Right now I use DispatchQueue and let it wait two seconds. I cannot get it done with completion func because I dont know where to put the completion function.
This is my first Swift file:
struct DatenHolen {
let fussballUrl = "deleted="
func linkZusammenfuegen () {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString)
}
func perfromRequest(urlString: String)
{
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
if error != nil{
print(error!)
return
}
if let safeFile = gettingInfo {
self.parseJSON(datenEintragen: safeFile)
}
}
task.resume()
}
}
func parseJSON(datenEintragen: Data) {
let decoder = JSONDecoder()
do {
let decodedFile = try decoder.decode(JsonDaten.self, from: datenEintragen)
TeamOne = decodedFile.data[0].home_name
} catch {
print(error)
}
}
}
And this is my second Swift File as Viewcontroller.
class HauptBildschirm: UIViewController {
func werteEintragen() {
Tone.text = TeamOne
}
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [unowned self] in
self.werteEintragen()
}
}
}
How can I implement this and where?
func firstTask(completion: (_ success: Bool) -> Void) {
// Do something
// Call completion, when finished, success or faliure
completion(true)
}
firstTask { (success) in
if success {
// do second task if success
secondTask()
}
}
You can have a completion handler which will notify when a function finishes, also you could pass any value through it. In your case, you need to know when a function finishes successfully.
Here is how you can do it:
func linkZusammenfuegen (completion: #escaping (_ successful: Bool) -> ()) {
let urlString = fussballUrl + String(Bundesliga1.number)
perfromRequest(urlString: urlString, completion: completion)
}
func perfromRequest(urlString: String, completion: #escaping (_ successful: Bool) -> ()) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
guard error == nil else {
print("Error: ", error!)
completion(false)
return
}
guard let safeFile = gettingInfo else {
print("Error: Getting Info is nil")
completion(false)
return
}
self.parseJSON(datenEintragen: safeFile)
completion(true)
}
task.resume()
} else {
//can't create URL
completion(false)
}
}
Now, in your second view controller, call this func like this:
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.linkZusammenfuegen { [weak self] successful in
guard let self = self else { return }
DispatchQueue.main.async {
if successful {
self.werteEintragen()
} else {
//do something else
}
}
}
}
I highly recommend Google's Promises Framework:
https://github.com/google/promises/blob/master/g3doc/index.md
It is well explained and documented. The basic concept works like this:
import Foundation
import Promises
struct DataFromServer {
var name: String
//.. and more data fields
}
func fetchDataFromServer() -> Promise <DataFromServer> {
return Promise { fulfill, reject in
//Perform work
//This block will be executed asynchronously
//call fulfill() if your value is ready
//call reject() if an error occurred
fulfill(data)
}
}
func visualizeData(data: DataFromServer) {
// do something with data
}
func start() {
fetchDataFromServer
.then { dataFromServer in
visualizeData(data: dataFromServer)
}
}
The closure after "then" will always be executed after the previous Promise has been resolved, making it easy to fulfill asynchronous tasks in order.
This is especially helpful to avoid nested closures (pyramid of death), as you can chain promises instead.

Thread Sanitizer does not show all issues in Xcode 8

Thread Sanitizer does not show all issues. for example
private func observeConversationUsers(_ isObserve: Bool, conversationID: String, updated: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
guard isObserveConversationUsers != isObserve else { return }
isObserveConversationUsers = isObserve
DispatchQueue.global(qos: .background).async {
let conversationUserRef = Database.database().reference().child(MainGateways.chat.description).child(MainGateways.conversationUsers.description).child(conversationID)
if !isObserve {
conversationUserRef.removeAllObservers()
return
}
if !self.references.contains(conversationUserRef) { // for example this issue
self.references.append(conversationUserRef)
}
conversationUserRef.observe(.childAdded, with: { (snap) in
if snap.value is NSNull {
return
}
guard let dict = snap.value as? [String : Any] else { return }
guard let chatUserActivityModel = Mapper<ChatUserActivityModel>().map(JSON: dict) else { return }
self.downloadImageProfile(chatUserActivityModel.userID, conversationID: conversationID)
}, withCancel: { (error) in
// TODO: - it
})
conversationUserRef.observe(.childRemoved, with: { (snap) in
}, withCancel: { (error) in
// TODO: - it
})
}
}
My diagnostics settings
How can I fix it?
Update: I made a very simple example but Xcode does not show an error here, although the isPushSettingsFormVC property is called in another thread
class MainTabBarController: UITabBarController {
var isPushSettingsFormVC = false
override func viewDidLoad() {
super.viewDidLoad()
// I think here is a data race
DispatchQueue.global(qos: .background).async { [weak self] in
self?.isPushSettingsFormVC = false
}
}
}