Firebase Async Callback in NSOperation not returning - swift

I have a AsyncOperation class defined as such
import Foundation
class ASyncOperation: NSOperation {
enum State: String {
case Ready, Executing, Finished
private var keyPath: String {
return "is" + rawValue
}
}
var state = State.Ready {
willSet {
willChangeValueForKey(newValue.keyPath)
willChangeValueForKey(state.keyPath)
}
didSet {
didChangeValueForKey(oldValue.keyPath)
didChangeValueForKey(state.keyPath)
}
}
override var ready: Bool {
return super.ready && state == .Ready
}
override var executing: Bool {
return super.ready && state == .Executing
}
override var finished: Bool {
return super.ready && state == .Finished
}
override var asynchronous: Bool {
return true
}
override func start() {
if cancelled {
state = .Finished
return
}
main()
state = .Executing
}
override func cancel() {
state = .Finished
}
}
and a subclass of it ImageLoadOperation.
import Foundation
import UIKit
import Firebase
class ImageLoadOperation: ASyncOperation {
var imagePath: String?
var image: UIImage?
override func main(){
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://salisbury-zoo- 91751.appspot.com")
if let path = imagePath {
let imageReference = storageRef.child(path)
imageReference.dataWithMaxSize(3 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
self.image = nil
} else {
self.image = UIImage(data: data!)
self.state = .Finished
}
}
}
}
}
So I go to call the Operation in a Queue
let queue = NSOperationQueue()
let imageLoad = ImageLoadOperation()
queue.addOperation(imageLoad)
let img:UIImage? = imageLoad.image
But it always returns nil. When I put a print statement in the callback of ImageLoadOperation the image is there and state is set to finished. When I add
queue.waitUntilAllOperationsAreFinished()
Inbetween queue.addOperation and let img:UIImage? = imageLoad.load then the entire application stalls as the main thread is blocked. Any other ideas on how I could get the image to be there outside the scope of the callback? I have also tried doing it without a NSOperationQueue and just as an NSOperation with no luck.

The queue.addOperation function adds the operation, and it starts executing in a background thread. It therefore returns well before the background thread is finished, which is why the image is nil.
And as the documentation states, waitUntilAllOperationsAreFinished will block the thread until the operations are finished. This is very undesirable on the main thread.
imageReference.dataWithMaxSize is an asynchronous operation that has a completion handler (where you are currently setting self.image). You need something in there to trigger code to run that will allow you to use imageLoad.image. How you do this will depend on the architecture of your app.
If your image is to be displayed in a UITableViewCell, for example, you will need to store the image in an array of images, possibly where the index matches the table row, and then reload at least that row of the tableView. This is because by the time the image has been received, the cell may no longer exist for that row. Obviously you would not want this code sitting inside your ImageLoadOperation class. Instead it should be passed into main() as a completion handler.

Related

MainActor and async await when reading and writing

I understand the new async syntax in Swift in the sense that if I call it, then it will handle a pool of asynchronous queues / threads (whatever) to do the work. What I don't understand is how we return to the main thread once it's all over.
// On main thread now
let manager = StorageManager()
let items = await manager.fetch // returns on main thread?
struct StorageManager {
private func read() throws -> [Item] {
let data = try file.read()
if data.isEmpty { return [] }
return try JSONDecoder().decode([Item].self, from: data)
}
func fetch() async {
fetchAndWait()
}
func fetchAndWait() {
if isPreview { return }
let items = try? read()
fetchedItems = items ?? []
}
func save() throws {
let data = try JSONEncoder().encode(fetchedItems)
try file.write(data)
}
}
I want to make sure that I read and write from/to disk in the correct way i.e. is thread safe when necessary and concurrent where possible. Is it best to declare this struct as a #MainActor ?
There is nothing in the code you've given that uses async or await meaningfully, and there is nothing in the code you've given that goes onto a "background thread", so the question as posed is more or less meaningless. If the question did have meaning, the answer would be: to guarantee that code doesn't run on the main thread, put that code into an actor. To guarantee that code does run on the main thread, put that code into a #MainActor object (or call MainActor.run).
The async methods do not return automatically to the main thread, they either:
complete in the background whatever they are doing
or
explicitly pass at a certain moment the execution to the main thread through a #MainActor function/ class. (edited following #matt's comment)
In the code above you can start by correcting the fact that fetch() does not return any value (items will receive nothing based on your code).
Example of your code for case 1 above:
let manager = StorageManager()
let items = await manager.fetch // not on the main thread, the value will be stored in the background
struct StorageManager {
private func read() throws -> [Item] {
let data = try file.read()
if data.isEmpty { return [] }
return try JSONDecoder().decode([Item].self, from: data)
}
func fetch() async -> [Item] {
if isPreview { return }
let items = try? read()
return items ?? []
}
func save() throws {
let data = try JSONEncoder().encode(fetchedItems)
try file.write(data)
}
}
Example for case 2 above (I created an #Published var, which should only be written on the main thread, to give you the example):
class ViewModel: ObservableObject {
let manager = StorageManager()
#Published var items = [Item]() // should change value only on main thread
func updateItems() {
Task { // Enter background thread
let fetchedItems = await self.manager.fetch()
// Back to main thread
updateItemsWith(fetchedItems)
}
}
#MainActor private func updateItemsWith(newItems: [Item]) {
self.items = newItems
}
}
struct StorageManager {
private func read() throws -> [Item] {
let data = try file.read()
if data.isEmpty { return [] }
return try JSONDecoder().decode([Item].self, from: data)
}
func fetch() async -> [Item] {
if isPreview { return }
let items = try? read()
return items ?? []
}
func save() throws {
let data = try JSONEncoder().encode(fetchedItems)
try file.write(data)
}
}

Update UI after async await call

I load books from API, show activity indicator while loading, update label after server response.
activityView.isHidden = false
let task = detach {
do {
let books = try await self.bookService.fetchBooks()
DispatchQueue.main.async {
self.show(books: books)
}
} catch {
DispatchQueue.main.async {
self.resultLabel.text = error.localizedDescription
}
}
DispatchQueue.main.async {
self.activityView.isHidden = true
}
}
//...
My question is what is better approach to update UI on the main queue? DispatchQueue.main.async look ugly and I guess there is a better approach to do the same.
I must use it, because all UI updates should be on the main thread and I get compiler errors without DispatchQueue.main.async something like
Property 'text' isolated to global actor 'MainActor' can not be mutated from a non-isolated context
or
Property 'isHidden' isolated to global actor 'MainActor' can not be mutated from a non-isolated context
P.S. Use Xcode 13.0b2
Use #MainActor like this -
self.updateAcitivityIndicator(isHidden: false)
let task = detach {
do {
let books = try await self.bookService.fetchBooks()
self.showBooks(books)
} catch {
self.showError(error)
}
self.updateAcitivityIndicator(isHidden: true)
}
#MainActor
private func showBooks(_ books: [Book]) {
}
#MainActor
private func showError(_ error: Error) {
self.resultLabel.text = error.localizedDescription
}
#MainActor
private func updateAcitivityIndicator(isHidden: Bool) {
self.activityView.isHidden = isHidden
}

Is there a battery level did change notification equivalent for kIOPSCurrentCapacityKey on macOS?

I am building a Swift app that monitors the battery percentage, as well as the charging state, of a Mac laptop's battery. On iOS, there is a batteryLevelDidChange notification that is sent when the device's battery percentage changes, as well as a batteryStateDidChange notification that is sent when the device is plugged in, unplugged, and fully charged.
What is the macOS equivalent of those two notifications in Swift, or more specifically, for kIOPSCurrentCapacityKey and kIOPSIsChargingKey? I read through the notification documentation and didn't see any notifications for either. Here is the code I have for fetching the current battery charge level and charging status:
import Cocoa
import IOKit.ps
class MainViewController: NSViewController {
enum BatteryError: Error { case error }
func getMacBatteryPercent() {
do {
guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue()
else { throw BatteryError.error }
guard let sources: NSArray = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue()
else { throw BatteryError.error }
for powerSource in sources {
guard let info: NSDictionary = IOPSGetPowerSourceDescription(snapshot, ps as CFTypeRef)?.takeUnretainedValue()
else { throw BatteryError.error }
if let name = info[kIOPSNameKey] as? String,
let state = info[kIOPSIsChargingKey] as? Bool,
let capacity = info[kIOPSCurrentCapacityKey] as? Int,
let max = info[kIOPSMaxCapacityKey] as? Int {
print("\(name): \(capacity) of \(max), \(state)")
}
}
} catch {
print("Unable to get mac battery percent.")
}
}
override func viewDidLoad() {
super.viewDidLoad()
getMacBatteryPercent()
}
}
(I'm replying to this almost 3-year-old question as it is the third result that comes up on the Google search "swift iokit notification".)
The functions you're looking for are IOPSNotificationCreateRunLoopSource and IOPSCreateLimitedPowerNotification.
Simplest usage of IOPSNotificationCreateRunLoopSource:
import IOKit
let loop = IOPSNotificationCreateRunLoopSource({ _ in
// Perform usual battery status fetching
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, .defaultMode)
Note that the second parameter context is passed as the only parameter in the callback function, which can be used to pass the instance as a pointer to the closure since C functions do not capture context. (See the link below for actual implementation.)
Here is my code that converts the C-style API into a more Swift-friendly one using the observer pattern: (don't know how much performance benefit it will has for removing run loops)
import Cocoa
import IOKit
// Swift doesn't support nested protocol(?!)
protocol BatteryInfoObserverProtocol: AnyObject {
func batteryInfo(didChange info: BatteryInfo)
}
class BatteryInfo {
typealias ObserverProtocol = BatteryInfoObserverProtocol
struct Observation {
weak var observer: ObserverProtocol?
}
static let shared = BatteryInfo()
private init() {}
private var notificationSource: CFRunLoopSource?
var observers = [ObjectIdentifier: Observation]()
private func startNotificationSource() {
if notificationSource != nil {
stopNotificationSource()
}
notificationSource = IOPSNotificationCreateRunLoopSource({ _ in
BatteryInfo.shared.observers.forEach { (_, value) in
value.observer?.batteryInfo(didChange: BatteryInfo.shared)
}
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationSource, .defaultMode)
}
private func stopNotificationSource() {
guard let loop = notificationSource else { return }
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), loop, .defaultMode)
}
func addObserver(_ observer: ObserverProtocol) {
if observers.count == 0 {
startNotificationSource()
}
observers[ObjectIdentifier(observer)] = Observation(observer: observer)
}
func removeObserver(_ observer: ObserverProtocol) {
observers.removeValue(forKey: ObjectIdentifier(observer))
if observers.count == 0 {
stopNotificationSource()
}
}
// Functions for retrieving different properties in the battery description...
}
Usage:
class MyBatteryObserver: BatteryInfo.ObserverProtocol {
init() {
BatteryInfo.shared.addObserver(self)
}
deinit {
BatteryInfo.shared.removeObserver(self)
}
func batteryInfo(didChange info: BatteryInfo) {
print("Changed")
}
}
Credits to this post and Koen.'s answer.
I'd Use this link to get the percentage (looks cleaner)
Fetch the battery status of my MacBook with Swift
And to find changes in the state, use a timer to re-declare your battery state every 5 seconds and then set it as a new variable var OldBattery:Int re-declare it once again and set it as NewBattery, then, write this code:
if (OldBattery =! NewBattery) {
print("battery changed!")
// write the function you want to happen here
}

Swift completion handler in class and function

I have a Class with a function that connect to a firestoreDB and get some data:
import UIKit
import CoreLocation
import Firebase
private let _singletonInstance = GetBottlesFromDB()
class GetBottlesFromDB: NSObject {
class var sharedInstance: GetBottlesFromDB { return _singletonInstance }
var Pins = [LayoutBottlesFromDB]()
// MARK: - init
override init() {
super.init()
populatePinList(completion: { pin in self.Pins } )
//print("GET ALL PINS: \(Pins)")
}
func populatePinList(completion: #escaping ([LayoutBottlesFromDB]) -> ()) {
Pins = []
AppDelegate.ADglobalVar.db.collection("Bottles").whereField("pickupuser", isEqualTo: NSNull()).getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
print("start getting documents:")
for document in querySnapshot!.documents {
//print("\(document.documentID) => \(document.data())")
//print("\(document.documentID)")
let bottleID:String = document.documentID
let bottlekind:Int = document.data()["bottle"] as! Int
var bottletitel:String
var bottlesub:String
var bottleurl:String = (document.data()["pic"] as? String)!
let pin = LayoutBottlesFromDB(document.data()["lat"] as! CLLocationDegrees, document.data()["long"] as! CLLocationDegrees, ID: bottleID, title: bottletitel, subtitle: bottlesub, type: bottlekind, url:bottleurl)
//print("GET DAATA from DB: \(pin)")
self.Pins.append(pin)
} //for
completion(self.Pins)
} //else
} //querysnap
}//function
}//class
in my ViewController I call this function.
for pin in GetBottlesFromDB.sharedInstance.Pins{
print("Add Pin : \(pin)")
}
My pProblem is that the function will called but the print is empty.
The function doesn't wait for a completion. What did I do wrong?
You are calling directly GetBottlesFromDB.sharedInstance.Pins and this will not wait for completion of populatePinList method so that's why you are getting blank So You need to wait for completion or you can check if data is not available in pins variable the you need to call completion method like this way:
GetBottlesFromDB.sharedInstance.populatePinList { (pins) in
for pin in pins{
print("Add Pin : \(pin)")
}
}
Nothing in your code waits for the execution of the asynchronous method, so that's no surprise. Also, it would be a terrible design because it would block your app. In addition, your singleton implementaion is overly verbose and doesn't guarantee that it stays a singleton, so I'd recommend to change it to
class GetBottlesFromDB {
private(set) var Pins = [LayoutBottlesFromDB]()
static let shared = GetBottlesFromDB()
private init() {}
// populatePinList as before
}
and in your view controller, e.g. in viewDidLoad do:
override func viewDidLoad() {
GetBottlesFromDB.shared.populatePinList { pins in
pins.forEach { print("Add Pin: \(pin)") }
}
}

Swift: Weak referenced stored & nested blocks / closures

I'm looking to nest a block / closure whilst another process completes off of the main thread like so
typealias FirstBlock = (jsonDictionary:NSDictionary?,errorCode:NSString?) -> Void
typealias SecondBlock = (complete:Bool?,errorCode:NSString?,dictionary:NSDictionary?) -> Void
Controller
func startPoint {
SomeNetworkManager.sharedInstance.firstProcess(self.someDictionary) { (complete, errorCode, dictionary) -> Void in
// I want to get here with a strong reference to these objects in this class only
print(complete,errorCode,dictionary)
}
}
SomeNetworkManager
func firstProcess(dictionary:NSDictionary?, completion:SecondBlock?) {
let request = HTTPRequest.init(requestWithPath:"path", httpMethod: .post) { (jsonDictionary, errorCode) -> Void in
let organisedDictionary:NSMutableDictionary = NSMutableDictionary()
// Some processing of the json into a new dictionary
dispatch_async(dispatch_get_main_queue()) {
if errorCode == nil {
completion!(complete:true,errorCode:nil,dictionary:organisedDictionary)
}
else {
completion!(complete:false,errorCode:errorCode,dictionary:nil)
}
}
}
request.postDataDictionary = refinementsDictionary as! NSMutableDictionary
request.request()
}
HTTPRequest
var processBlock:FirstBlock?
init(requestWithPath path:NSString, httpMethod method:HTTPMethod, andProcessBlock block:FirstBlock) {
super.init()
self.requestURL = NSURL(string:path as String);
self.responseData = NSMutableData()
self.processBlock = block
switch (method) {
case .post:
self.httpMethod = kPost
break;
case .put:
self.httpMethod = kPut
break;
default:
self.httpMethod = kGet
break;
}
}
// An NSURLConnection goes off, completes, I serialise the json and then...
func completeWithJSONDictionary(jsonDictionary:NSDictionary) {
self.processBlock!(jsonDictionary:jsonDictionary,errorCode:nil)
self.processBlock = nil
}
I'm missing something fundamental regarding ARC retain cycles because every time one of these is called I'm getting a memory leak.. I've had a look at
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html
with no joy.. I think Defining a Capture List is the right area, but as for storing a block and how to define it I have no idea what I'm doing wrong.
In all likelihood, you're getting retain cycles because the completion block references the HttpRequest (probably via the calling object), references the completion block, something like:
class HttpReference {
let completion : ()->()
init(completion:()->()) {
self.completion = completion
}
}
class Owner {
var httpReference : HttpReference?
func someFunction() {
httpReference = HttpReference() {
print(self.httpReference)
}
}
}
There are two ways to break the cycle, either by using an unowned reference or a by using a weak reference, both are fairly similar, in this case, the norm would be to use an unowned reference to self by changing:
func someFunction() {
httpReference = HttpReference() { [unowned self] in
print(self.httpReference)
}
}
Now, self isn't retained, thus breaking the retain cycle.