Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
Hi everyone i have a question about a problem that i would like to do in swift and to see the best possible way to do that. I currently have an approach that i don't like and would be good if i can get some ideas about it.
Here it is the problem:
DispatchQ serial which is started
Then another Thread starts and waits for a completion block that returns 1 element per run
That thread will run multiple times and it will give 1 element each time until it returns all elements
The code shouldn't proceed forward and has to wait for all the elements to be collected in a array
After the array is full then the code can proceed forward.
Example with LocationManager:
Location manager starts fetching location
It output the location each time it finds a new location
At the end i have to collect all the locations i have gotten in 10 sec for example
Then proceed the work of the current thread
Example code:
var queue = DispatchQueue("q")
queue.async {
var locationCollected = Array<Location>()
// this will output multiple times
getLocation { location in
// add location to the collection
locationCollected.append(location)
}
// the current Q need to wait until all results are collected
return locationCollected
}
func getLocation() -> Location {
// this is the function that will return location
self.manager?.location(completion: { location in
// will provide location and update
completion(location)
}
}
Thank you much appreciated the help
If you want to adopt a linear flow of code approach then you can adopt the new async/await construct but that will limit your iOS versions.
In general blocking threads is best avoided as it can cause deadlocks. You should use completion handlers to get your asynchronous result rather than blocking.
For the purposes of this answer I will stick with the approach in your question.
Based on the requirements in your question, let's create a function that:
Fetches locations for a specified duration
Only fetches one location at a time
Provides the fetched location in an array
Is, itself, an asynchronous function
We need to ensure only a single location fetch is active at a time, which we can do with a DispatchSemaphore
func getLocations(for duration: TimeInterval, completion: (([Location])->Void)) {
var queue = DispatchQueue("q")
queue.async {
let semaphore = DispatchSemaphore(0)
var locationCollected = [Location]()
let startTime = Date()
while (startTime.timeIntervalSinceNow > -duration) {
getLocation { location in
locationCollected.append(location)
semaphore.signal()
}
semaphore.wait()
}
completion(result)
}
}
Related
I want to measure how long (in seconds) it takes users to do certain things in my app. Some examples are logging in, pressing a button on a certain page, etc.
I am using an NSTimer for that. I am starting it in the viewDidLoad of a specific page, and stopping it at the point that I want to measure.
I also want to measure cumulative time for certain things. I would like to start the timer on the log-in screen, and then continue the timer until the user gets to the next view controller and clicks on a certain button.
I'm not sure how to do this. Should create a global variable in my app delegate? Or is there some other better way?
No need for an NSTimer, you just need to record the start times and compare them to the stop times. Try using a little helper class such as:
class MyTimer {
static let shared = MyTimer()
var startTimes = [String : Date]()
func start(withKey key: String) {
startTimes[key] = Date()
}
func measure(key: String) -> TimeInterval? {
if let start = startTimes[key] {
return Date().timeIntervalSince(start)
}
return nil
}
}
To use this, just call start(withKey:) right before you start a long-running task.
MyTimer.shared.start(withKey: "login")
Do something that takes a while and then call measure(key:) when you're done. Because MyTimer is a singleton, it can be called from anywhere in your code.
if let interval = MyTimer.shared.measure("login") {
print("Logging in time: \(interval)")
}
If you're using multiple threads, you may to to add some thread safety to this, but it should work as is in simple scenarios.
Here is my situation. (Using Swift 2.2)
I have a list of coordinates (CLLocation). I need to call the reverseGeocodeLocation to fetch the corresponding Place/City. If I try to loop through the elements there is a chance for some calls to fails as Apple suggest to send one call in a second. So I need to add a delay between each calls as well.
Is there any way to achieve this? Any help is appreciated.
(If we have multiple items with same lat, long we only call the api once)
This code declares a set of locations and looks them up one by one, with at least 1 second between requests:
var locations = Set<CLLocation>()
func reverseGeocodeLocation() {
guard let location = locations.popFirst() else {
geocodingDone()
return
}
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
//Do stuff here
//Dispatch the next request in 1 second
_ = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
self.reverseGeocodeLocation()
}
}
}
func geocodingDone() {
//Put your finish logic in here
}
FYI I used the block syntax for the Timer, but that only works on iOS 10. If you are using iOS 9 or earlier just use the selector version and it works the same way.
I'm subclassing a GroupOperation and would like to run it again if it fails and the amount of attempts is less than 5. I also have a delayOperation to delay it by 3 seconds. I can't get this to work however, this is what I have so far:
self.conversation is an Object.
produceConversationOperation() returns a backgroundOperation
override func operationDidFinish(operation: NSOperation, withErrors errors: [NSError]) {
if let _ = errors.first {
if let _ = operation as? BackgroundOperation {
context.performBlockAndWait({ () -> Void in
self.conversation.retryCountValue++
if self.conversation.retryCountValue < 5 {
let postConversationOperation = self.produceConversationOperation()
let delayOperation = DelayOperation(interval: 3)
postConversationOperation.addDependency(delayOperation)
self.produceOperation(delayOperation)
self.produceOperation(postConversationOperation)
}
else {
self.conversation.retryCountValue = 0
}
self.saveContext()
})
}
}
}
For some reason the operation isn't running again after the first failure. I have a feeling the issue is with the self.produceOperation method but I have no idea what.
There have been a few solutions to similar questions but I haven't found anything that has helped.
Thanks
Bit late to answer this, but you might want to check out using RetryOperation from http://github.com/danthorpe/Operations which supports many of these issues:
max count of retries
customisable delay (with a wait strategy if you need random/exponential/etc)
it accepts a GeneratorType of the operation
it accepts a "retry handler" trailing closure which allows the consumer to "tweak" the next instance of the operation which will be retried.
To answer this question a bit more concretely though, and without knowing for sure what produceOperation() is doing, I would hazard a guess that it is not actually creating a fresh new instance, but in some way is returning the original operation.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
My swift 2 app have a very hight battery usage and a min. cpu of 105%
but first not the whole time.
I have an table view controller.
From there you can go to an camera controller - there you have to scan a barcode.
If the barcode was detected, a new view controller send the barcode via http post request to my mysql database and get informations.
this informations I will send to the last view controller. (this is my problem view controller, because there my cpu goes to min. 105% and the battery usage will be very high. from now in my complete app my cpu will be the whole time on min 105% - no matter which view controller will be the next.)
This is only a simple static table view, where the informations (2 Strings) will be shown. there you can click on "Save" and your entry will shown in the first table view controller.
I have delete all my code for the last view controller to make it blank.
no functions will be active in this controller.
But the cpu will goes up, too.
Any idea how can i check why the cpu goes up?
UPDATE
in my http post request controller (3)
is this code:
override func viewWillAppear(animated: Bool) {
self.LoadingCircle.rotate360Degrees(completionDelegate: self)
self.isRotating = true
}
rotate360Degress Extension
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 2.5, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI * 2.0)
rotateAnimation.duration = duration
if let delegate: AnyObject = completionDelegate {
rotateAnimation.delegate = delegate
}
self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}
if i delete the code in my viewWillAppear part, the cpu will stay "normal"
The delegate in your case is unnecessary, actually it will cause this code to run in multiple instances, what will rise your CPU usage.
If you want to repeat the event, you can set the .repeatCount = .infinity
I am using a Particle Core to get the temperature from my room. The temperature is accessed through the cloud, which is being constantly updated in a variable. This is how I access the variable and display it:
func updateTemp(){
let seconds = 3.0
let delay = seconds * Double(NSEC_PER_SEC) // nanoseconds per seconds
let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.myPhoton?.getVariable("tempF", completion: { (result:AnyObject!, error:NSError!) -> Void in
if let _ = error {
print("Failed reading temperature from device")
}
else {
if let larry = result as? Int {
self.temp.text="\(larry)˚"
self.truth++ //Once a value has been found, update the count.
}
}
})
})
}
override func viewDidLoad() {
sparkStart()
}
override func viewDidLayoutSubviews() {
updateTemp()
NSTimer.scheduledTimerWithTimeInterval(100.0, target: self, selector: "updateTemp", userInfo: nil, repeats: true) //Gaurantees that the app is updated every 100 seconds. That way we have a fresh temperature often.
//Stop the spinning once a value has been found
if truth == 1{
activity.stopAnimating()
activity.removeFromSuperview()
}
}
Since this is my Particle Core detecting the temperature from environment, the temperature variable is constantly changing. However, when I use NSTimer, the code does not get updated in the time specified. Instead, it begins by updating based on the specified time, but then the time starts decreases exponentially and the variable is updated every 0.001 seconds or so. Any thoughts?
Im assuming what we see is not the full code. In your viewDidLayoutSubviews function, you call updateTemp twice. Once explicitly and once via timer callback.
Your updateTemp function schedules the network call in the main run loop, that's where the timer is also running. The dispatch_after function queues the execution of the readout updates one after the other. I am now assuming, that something in your display code causes repeated triggers of viewDidLayoutSubviews, each of which schedules two new updates etc. Even if the assumption is false (there are a couple of other possibilities due to network code being slow and the timer also running in the main run loop), I am guessing if you drop the explicit call to updateTemp you'll lose the "exponential" and should be fine.
In general, as the web call is largely asynchronous, you could just use the timer and call your sensor directly or if you feel GCD has an important performance advantage switch to dispatch_async and apply for the next available queue with each call via calling dispatch_get_global_queue