alternative to QOS_CLASS_UTILITY on ios7 using swift - ios

I'm using QOS_CLASS_UTILITY in dispatch_async to perform some async operations.Sample code is below :
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)) {
var ip = "163.289.2." + "\(i)"
let foundThisOne = Ping.getIPAddress(ip)
dispatch_async(serialQueue) {
if !resultFound {
resultFound = foundThisOne
numProcessed++
if resultFound {
completion(existingIP:ip)
} else if numProcessed == 256 {
completion(existingIP: nil)
}
}
}
}
}
But "QOS_CLASS_UTILITY" is available on iOS8 onwards and now i need to support the same logic on iOS7 also.So what is better alternative to
QOS_CLASS_UTILITY which works on iOS7 using swift.

According to documentation (see Global Concurrent Queues) I'd say you can replace it with:
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)

Related

Problems with list manipulation in Swift after iOS update 14.4.2

For some years, I use this simple code to read and manipulate a list in Swift:
if (Helper.hasSubscription()) {
self.allVisitedTrackHandles = Database.getAllCompletedTrackHandles(withDeleted: false, userid: self.userid)
if (self.allVisitedTrackHandles.count > 0) {
var counter = 0
for th: TrackHandle in self.allTrackHandles {
if self.allVisitedTrackHandles.contains(where: { $0.trackid == th.trackid }) {
for thv: TrackHandle in self.allVisitedTrackHandles {
if thv.trackid == th.trackid {
self.allTrackHandles[counter].date_in_milliseconds = thv.date_in_milliseconds
break
}
}
self.allTrackHandles[counter].visited = 1
}
counter += 1
}
}
}
After updating my iOS device to iOS 14.4.2, app is crashing on this line:
for th: TrackHandle in self.allTrackHandles
Only feedback Xcode gives me is this: Thread 79: EXC_BAD_ACCESS (code=1, address=0x12f100010)
I don't know why that line would suddenly start crashing; There is nothing in the code shown that would cause the crash. Are you updating the array somewhere else? Is this, perhaps, a threading issue?
You can refactor this code to make it simpler, clearer and more efficient. This may help;
if Helper.hasSubscription() {
self.allVisitedTrackHandles = Database.getAllCompletedTrackHandles(withDeleted: false, userid: self.userid)
if !self.allVisitedTrackHandles.isEmpty {
for index in 0..<self.allTrackHandles.count {
if let visited = self.allVisitedTrackHandles.first { $0.track id == self.allTrackHandles[index].track id } {
self.allTrackHandles[index].date_in_milliseconds = visited.date_in_milliseconds
}
self.allTrackHandles[index].visited = 1
}
}
}

Kotlin coroutines delay do not work on IOS queue dispatcher

I have a KMM app, and there is code:
fun getWeather(callback: (WeatherInfo) -> Unit) {
println("Start loading")
GlobalScope.launch(ApplicationDispatcher) {
while (true) {
val response = httpClient.get<String>(API_URL) {
url.parameters.apply {
set("q", "Moscow")
set("units", "metric")
set("appid", weatherApiKey())
}
println(url.build())
}
val result = Json {
ignoreUnknownKeys = true
}.decodeFromString<WeatherApiResponse>(response).main
callback(result)
// because ApplicationDispatcher on IOS do not support delay
withContext(Dispatchers.Default) { delay(DELAY_TIME) }
}
}
}
And if I replace withContext(Dispatchers.Default) { delay(DELAY_TIME) } with delay(DELAY_TIME) execution is never returned to while cycle and it will have only one iteration.
And ApplicationDispatcher for IOS looks like:
internal actual val ApplicationDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue())
internal class NsQueueDispatcher(
private val dispatchQueue: dispatch_queue_t
) : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatch_async(dispatchQueue) {
block.run()
}
}
}
And from delay source code I can guess, that DefaultDelay should be returned and there is should be similar behaviour with/without withContext(Dispatchers.Default)
/** Returns [Delay] implementation of the given context */
internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay
Thanks!
P.S. I got ApplicationDispatcher from ktor-samples.
Probably ApplicationDispatcher is some old stuff, you don't need to use it anymore:
CoroutineScope(Dispatchers.Default).launch {
}
or
MainScope().launch {
}
And don't forget to use -native-mt version of coroutines, more info in this issue

SocketScan Getting the Battery Level in Swift

Whatever I seem to try I cannot currently get back the Battery level from the iOS/SocketScan API. I am using version 10.3.36, here is my code so far:
func onDeviceArrival(result: SKTRESULT, device deviceInfo: DeviceInfo!) {
print("onDeviceArrival:\(deviceInfo.getName())")
scanApiHelper.postGetBattery(deviceInfo, target: self, response: #selector(onGetBatteryInfo))
}
func onGetBatteryInfo(scanObj: ISktScanObject) {
let result:SKTRESULT = scanObj.Msg().Result()
print("GetBatteryInfo status:\(result)")
if (result == ESKT_NOERROR) {
let batterylevel = scanObj.Property().getUlong()
print("Battery is:\(batterylevel)")
} else {
print("Error GetBatteryInfo status:\(result)")
}
However, the values I get back are:
GetBatteryInfo status:0
Battery is:1677741312
If my code is correct then how do I make the Battery result I get back a meaningful result, like a percentage? If I'm way off then how do I get back info like the battery level, firmware version etc?
Thanks
David
EDIT: SKTBATTERY_GETCURLEVEL isn't supported in Swift. However, the docs explain that the battery level response includes the min, current and max levels encoded in the first, second and third bytes, respectively.
The following is equivalent to using SKTBATTERY_GETCURLEVEL
Swift
func onGetBatteryInfo(scanObj: ISktScanObject) {
let result:SKTRESULT = scanObj.Msg().Result()
if(SKTSUCCESS(result)){
let batteryInfo = scanObj.Property().getUlong();
let batteryMin = ((batteryInfo >> 4) & 0xff);
let batteryCurrent = ((batteryInfo >> 8) & 0xff);
let batteryMax = ((batteryInfo >> 12) & 0xff);
let batteryPercentage = batteryCurrent / (batteryMax - batteryMin);
print("Battery is:\(batteryPercentage)")
self.setBatteryLevel = batteryPercentage
self.tableView.reloadData
} else {
print("Error GetBatteryInfo status:\(result)")
}
}
Objective-C
-(void) onGetBatteryInfo:(ISktScanObject*)scanObj {
SKTRESULT result=[[scanObj Msg]Result];
if(SKTSUCCESS(result)){
long batteryLevel = SKTBATTERY_GETCURLEVEL([[scanObj Property] getUlong]);
NSLog(#"BatteryInfo %ld", batteryLevel);
[self setBatteryLevel:batteryLevel];
[self.tableView reloadData];
} else {
NSLog(#"Error GetBatteryInfo status: %ld",result);
}
}
Here's code I use. Theres a variable defined in appDelegate for the batteryPercentage, and that is read when the v value is needed. The value is updated each 120 seconds by a timer, this way actions can occur as the level drops etc.
func onBatteryLevel (scanObj: ISktScanObject) {
let result: SKTRESULT = scanObj.Msg().Result()
if (SKTRESULT(result) > -1) {
let property: ISktScanProperty = scanObj.Property()
var batteryLevel = property.getUlong()
#if arch(x86_64) || arch(arm64)
batteryLevel = (batteryLevel<<(48))>>(56)
#else
batteryLevel = (batteryLevel<<(48-32))>>(56-32)
#endif
batteryPercentage = Int(batteryLevel)
} else {
debug ("data error \(result)")
}
}
For Swift 4 I just came across this problem and came up with the following solution.
var lastDeviceConnected : CaptureHelperDevice? {
didSet {
guard let lastDevice = self.lastDeviceConnected else { return }
lastDevice.getBatteryLevelWithCompletionHandler { result, batteryLevel in
guard result == SKTResult.E_NOERROR, let batteryLevel = batteryLevel else { return }
let minimum = SKTHelper.getMinimumLevel(fromBatteryLevel: Int(batteryLevel))
let maximum = SKTHelper.getMaximumLevel(fromBatteryLevel: Int(batteryLevel))
let current = SKTHelper.getCurrentLevel(fromBatteryLevel: Int(batteryLevel))
print("minimum: \(minimum)")
print("maximum: \(maximum)")
print("current: \(current)")
// current is out battery level in %
// minimum and maximum could for example be used for
// for a slider or something that visually presents
// the battery status
}
}
}
In my example I'm not handling the case that there could be no device or that the battery status might not have been retrieved as expected. I simply guard / return. In your example you might want to handle the issue.

objc_sync_enter / objc_sync_exit not working with DISPATCH_QUEUE_PRIORITY_LOW

I need a read\write lock for my application. I've read https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
and wrote my own class, cause there are no read/write lock in swift
class ReadWriteLock {
var logging = true
var b = 0
let r = "vdsbsdbs" // string1 for locking
let g = "VSDBVSDBSDBNSDN" // string2 for locking
func waitAndStartWriting() {
log("wait Writing")
objc_sync_enter(g)
log("enter writing")
}
func finishWriting() {
objc_sync_exit(g)
log("exit writing")
}
// ждет пока все чтение завершится чтобы начать чтение
// и захватить мютекс
func waitAndStartReading() {
log("wait reading")
objc_sync_enter(r)
log("enter reading")
b++
if b == 1 {
objc_sync_enter(g)
log("read lock writing")
}
print("b = \(b)")
objc_sync_exit(r)
}
func finishReading() {
objc_sync_enter(r)
b--
if b == 0 {
objc_sync_exit(g)
log("read unlock writing")
}
print("b = \(b)")
objc_sync_exit(r)
}
private func log(s: String) {
if logging {
print(s)
}
}
}
It works good, until i try to use it from GCD threads.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
When i try to use this class from different async blocks at some moment it allows to write when write is locked
here is sample log:
wait reading
enter reading
read lock writing
b = 1
wait reading
enter reading
b = 2
wait reading
enter reading
b = 3
wait reading
enter reading
b = 4
wait reading
enter reading
b = 5
wait reading
enter reading
b = 6
wait reading
enter reading
b = 7
wait reading
enter reading
b = 8
wait reading
enter reading
b = 9
b = 8
b = 7
b = 6
b = 5
wait Writing
enter writing
exit writing
wait Writing
enter writing
So, as you can see g was locked, but objc_sync_enter(g) allows to continue.
Why could this happen ?
BTW i checked how many times ReadWriteLock constructed, and it's 1.
Why objc_sync_exit not working and allowing to objc_sync_enter(g) when it's not freed ?
PS Readwirtelock defined as
class UserData {
static let lock = ReadWriteLock()
Thanks.
objc_sync_enter is an extremely low-level primitive, and isn't intended to be used directly. It's an implementation detail of the old #synchronized system in ObjC. Even that is extremely out-dated and should generally be avoided.
Synchronized access in Cocoa is best achieved with GCD queues. For example, this is a common approach that achieves a reader/writer lock (concurrent reading, exclusive writing).
public class UserData {
private let myPropertyQueue = dispatch_queue_create("com.example.mygreatapp.property", DISPATCH_QUEUE_CONCURRENT)
private var _myProperty = "" // Backing storage
public var myProperty: String {
get {
var result = ""
dispatch_sync(myPropertyQueue) {
result = self._myProperty
}
return result
}
set {
dispatch_barrier_async(myPropertyQueue) {
self._myProperty = newValue
}
}
}
}
All your concurrent properties can share a single queue, or you can give each property its own queue. It depends on how much contention you expect (a writer will lock the entire queue).
The "barrier" in "dispatch_barrier_async" means that it is the only thing allowed to run on the queue at that time, so all previous reads will have completed, and all future reads will be prevented until it completes. This scheme means that you can have as many concurrent readers as you want without starving writers (since writers will always be serviced), and writes are never blocking. On reads are blocking, and only if there is actual contention. In the normal, uncontested case, this is extremely very fast.
Are you 100% sure your blocks are actually executing on different threads?
objc_sync_enter() / objc_sync_exit() are guarding you only from object being accessed from different threads. They use a recursive mutex under the hood, so they won't either deadlock or prevent you from repeatedly accessing object from the same thread.
So if you lock in one async block and unlock in another one, the third block executed in-between can have access to the guarded object.
This is one of those very subtle nuances that is easy to miss.
Locks in Swift
You have to really careful what you use as a Lock. In Swift, String is a struct, meaning it's pass-by-value.
Whenever you call objc_sync_enter(g), you are not giving it g, but a copy of g. So each thread is essentially creating its own lock, which in effect, is like having no locking at all.
Use NSObject
Instead of using a String or Int, use a plain NSObject.
let lock = NSObject()
func waitAndStartWriting() {
log("wait Writing")
objc_sync_enter(lock)
log("enter writing")
}
func finishWriting() {
objc_sync_exit(lock)
log("exit writing")
}
That should take care of it!
In addition to #rob-napier's solution. I've updated this to Swift 5.1, added generic typing and a couple of convenient append methods. Note that only methods that access resultArray via get/set or append are thread safe, so I added a concurrent append also for my practical use case where the result data is updated over many result calls from instances of Operation.
public class ConcurrentResultData<E> {
private let resultPropertyQueue = dispatch_queue_concurrent_t.init(label: UUID().uuidString)
private var _resultArray = [E]() // Backing storage
public var resultArray: [E] {
get {
var result = [E]()
resultPropertyQueue.sync {
result = self._resultArray
}
return result
}
set {
resultPropertyQueue.async(group: nil, qos: .default, flags: .barrier) {
self._resultArray = newValue
}
}
}
public func append(element : E) {
resultPropertyQueue.async(group: nil, qos: .default, flags: .barrier) {
self._resultArray.append(element)
}
}
public func appendAll(array : [E]) {
resultPropertyQueue.async(group: nil, qos: .default, flags: .barrier) {
self._resultArray.append(contentsOf: array)
}
}
}
For an example running in a playground add this
//MARK:- helpers
var count:Int = 0
let numberOfOperations = 50
func operationCompleted(d:ConcurrentResultData<Dictionary<AnyHashable, AnyObject>>) {
if count + 1 < numberOfOperations {
count += 1
}
else {
print("All operations complete \(d.resultArray.count)")
print(d.resultArray)
}
}
func runOperationAndAddResult(queue:OperationQueue, result:ConcurrentResultData<Dictionary<AnyHashable, AnyObject>> ) {
queue.addOperation {
let id = UUID().uuidString
print("\(id) running")
let delay:Int = Int(arc4random_uniform(2) + 1)
for _ in 0..<delay {
sleep(1)
}
let dict:[Dictionary<AnyHashable, AnyObject>] = [[ "uuid" : NSString(string: id), "delay" : NSString(string:"\(delay)") ]]
result.appendAll(array:dict)
DispatchQueue.main.async {
print("\(id) complete")
operationCompleted(d:result)
}
}
}
let q = OperationQueue()
let d = ConcurrentResultData<Dictionary<AnyHashable, AnyObject>>()
for _ in 0..<10 {
runOperationAndAddResult(queue: q, result: d)
}
I had the same problem using queues in background. The synchronization is not working all the time in queues with "background" (low) priority.
One fix I found was to use semaphores instead of "obj_sync":
static private var syncSemaphores: [String: DispatchSemaphore] = [:]
static func synced(_ lock: String, closure: () -> ()) {
//get the semaphore or create it
var semaphore = syncSemaphores[lock]
if semaphore == nil {
semaphore = DispatchSemaphore(value: 1)
syncSemaphores[lock] = semaphore
}
//lock semaphore
semaphore!.wait()
//execute closure
closure()
//unlock semaphore
semaphore!.signal()
}
The function idea comes from What is the Swift equivalent to Objective-C's "#synchronized"?, an answer of #bryan-mclemore.

iOS swift while loop freeze my app

I'm developing an app where a user choose his accommodation and pick a time from date picker so the user can know what time the buss will arrive to his accommodation , on the simulator and iPad I'm having the appropriate results however I'm facing this issue on iPhone real device, this is the code :
mTimeString = "5:07 AM"
it will be searched in the array if not found , minus seconds until it matches results in the array . but while loop is freezing my app , i tried to surround it by :
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT ,0)
dispatch_async(queue){}
its not freezing anymore but the I'm not getting the results according to the array
if(defaults.stringForKey(accommodationChoiceKey)?.toInt() == 40) {
var array = ["12:05 AM","12:25 AM","12:45 AM","1:05 AM","1:25 AM","1:45 AM","2:05 AM","2:25 AM","2:45 AM","3:05 AM","3:25 AM","3:45 AM","4:05 AM","4:25 AM","4:45 AM","5:05 AM","5:25 AM","5:45 AM","6:05 AM","6:25 AM","6:45 AM","7:05 AM","7:25 AM","7:45 AM","8:05 AM","8:25 AM","8:45 AM","9:05 AM","9:25 AM","9:45 AM","10:05 AM","10:25 AM","10:45 AM","11:05 AM","11:25 AM","11:45 AM","12:05 PM","12:25 PM","12:45 PM","1:05 PM","1:25 PM","1:45 PM","2:05 PM","2:25 PM","2:45 PM","3:05 PM","3:25 PM","3:45 PM","4:05 PM","4:25 PM","4:45 PM","5:05 PM","5:25 PM","5:45 PM","6:05 PM","6:25 PM","6:45 PM","7:05 PM","7:25 PM","7:45 PM","8:05 PM","8:25 PM","8:45 PM","9:05 PM","9:25 PM","9:45 PM","10:05 PM","10:25 PM","10:45 PM","11:05 PM","11:25 PM","11:45 PM"]
while (find(array, mTimeString) == nil) {
choiceToSeconds--
var newmTime = x.dateByAddingTimeInterval(Double(choiceToSeconds))
var mTimeString = dateFormatter.stringFromDate(newmTime)
if (find(array, mTimeString) != nil) {
transportationLabel.text = mTimeString
break
}
getReady(newmTime)
}
}
You are calculating on Background thread which is okay , but you should NEVER update UI element from background thread ( transportationLabel.text = mTimeString ) , this should be done from main Thread , for example
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
if(defaults.stringForKey(accommodationChoiceKey)?.toInt() == 40) {
var array = ["12:05 AM","12:25 AM","12:45 AM","1:05 AM","1:25 AM","1:45 AM","2:05 AM","2:25 AM","2:45 AM","3:05 AM","3:25 AM","3:45 AM","4:05 AM","4:25 AM","4:45 AM","5:05 AM","5:25 AM","5:45 AM","6:05 AM","6:25 AM","6:45 AM","7:05 AM","7:25 AM","7:45 AM","8:05 AM","8:25 AM","8:45 AM","9:05 AM","9:25 AM","9:45 AM","10:05 AM","10:25 AM","10:45 AM","11:05 AM","11:25 AM","11:45 AM","12:05 PM","12:25 PM","12:45 PM","1:05 PM","1:25 PM","1:45 PM","2:05 PM","2:25 PM","2:45 PM","3:05 PM","3:25 PM","3:45 PM","4:05 PM","4:25 PM","4:45 PM","5:05 PM","5:25 PM","5:45 PM","6:05 PM","6:25 PM","6:45 PM","7:05 PM","7:25 PM","7:45 PM","8:05 PM","8:25 PM","8:45 PM","9:05 PM","9:25 PM","9:45 PM","10:05 PM","10:25 PM","10:45 PM","11:05 PM","11:25 PM","11:45 PM"]
while (find(array, mTimeString) == nil) {
choiceToSeconds--
var newmTime = x.dateByAddingTimeInterval(Double(choiceToSeconds))
var mTimeString = dateFormatter.stringFromDate(newmTime)
if (find(array, mTimeString) != nil) {
// This should be done on Main Thread
dispatch_async(dispatch_get_main_queue()) {
transportationLabel.text = mTimeString
}
break
}
getReady(newmTime)
}
}
}
Try replacing
transportationLabel.text = mTimeString
by
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
transportationLabel.text = mTimeString
}];
UI operations must be executed from main thread
EDIT:
In Swift:
NSOperationQueue.mainQueue().addOperationWithBlock({
transportationLabel.text = mTimeString
})

Resources