I am trying to build an app which is dependent on having some items in CoreData. I have it syncing with an external data source, which all works fine.
my app uses three methods, and is a single view app:
syncData()
createSpinner()
showResult()
now createSpinner is dependent on having some data in CoreData - and only needs to run once
showResult is dependent on the 'Spinner' having been created, and is called once on creation to initialize itself, as well as each time my spinner has been spun
I currently have SyncData in the viewDidLoad(), and the createSpinner() in viewDidAppear() (as it changes size depending on screen size)
The problem is on first launch the data does not load in time for the createSpinner(), and thus the app looks useless. How can I 'wait' for that first sync, or set up something to check there is some data?
The solution is to force syncData() & createSpinner() to run in same thread
you can do this by creating a serial queue and dispatch both methods asynchronously into it
let serialQueue = dispatch_queue_create("com.mycompany.myview", DISPATCH_QUEUE_SERIAL);
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(serialQueue) {
syncData()
}
}
override func viewDidAppear() {
super.viewDidAppear()
dispatch_async(serialQueue) {
createSpinner()
}
}
Related
I was trying to implement viper architecture on my Xcode. I am following an article https://medium.com/cr8resume/viper-architecture-for-ios-project-with-simple-demo-example-7a07321dbd29.
also, I downloaded the article source code, and run well also UI is changing. but when I created a new project with swift 5 and copy all methods and classes. after running UI is not updating but both codes are the same. please check below
This is a single-page sample code
please check this Github for my project https://github.com/Faizulkarim/movieHut/tree/main/MovieHutWithViper
This is my protocol
protocol PresenterToViewProtocol: AnyObject{
func showMovieList(MovieList: Array<movieModel>)
}
Here o call showMovieListMethod
extension MoviePresenter : InteractorToPresenterProtocol {
func movieFetchSuccess(movieModelArray: Array<movieModel>) {
view?.showMovieList(MovieList: movieModelArray)
}
}
// on MovieListViewController confirmed delegate
extension MovieListViewController:PresenterToViewProtocol{
func showMovieList(MovieList: Array<movieModel>) {
self.Movies = MovieList
self.cover.backgroundColor = UIColor.red
print(self.movies.count)
self.trandingTableView.reloadData()
}
}
on showMovieList function call, it's printing the total count of movies. But when I reloadtableview() table view is not interacting is showing any data. Even when I set to change the background of a view it's not changing. when I set breakpoint is' stop. So protocol is called properly but UI is not updating. i search in google didn't find relevant answer.
You might be in a background thread. You can only update UI from main thread.
Try to update like this:
DispatchQueue.main.async {
self.trandingTableView.reloadData()
}
this Main Menu VC will be opened when the app launched for the first time or after the user back to the app (the app become active after enter the background state).
every time this main menu VC is opened, ideally I need to update the time that the date time data comes from the server. in this main menu vc class I call getDateTimeFromServer() after that I updateUI().
but to update the data after the app enter the background and back to the foreground, the getDateTimeFromServer() and updateUI() shall be activated from Appdelegate using function.
func applicationWillEnterForeground(application: UIApplication) {
}
so how do I activate a method that are exist in Main Menu VC from AppDelegate
You don’t need to call the view controller method in app delegate. Observe foreground event in your controller and call your method from there itself.
Observe for the UIApplicationWillEnterForeground notification in your viewController viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.yourMethod), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)
Implement this to receive callback when user enters foreground
#objc func yourMethod() {
// Call getDateTimeFromServer()
}
These types of messaging are in most cases done with static context. As it was already mentioned you could alternatively use notification center within the within the view controller to be notified of your application entering foreground. I discourage you creating custom notifications for this though (but is a possible solution as well).
Anyway for your specific case I suggest you have a model that contains your data. Then create a shared instance of it.
class MyDataModel {
static var shared: MyDataModel = {
let model = MyDataModel()
model.reloadData()
return model
}()
var myObjects: [MyObject]?
func reloadData() {
// load data asynchronously
}
}
Now when your view controller needs to reload it simply uses MyDataModel.shared.myObjects as data source.
In app delegate all you do is reload it when app comes back to foreground using MyDataModel.shared.reloadData().
So now a delegate is still missing so we add
protocol MyDataModelDelegate: class {
func myDataModel(_ sender: MyDataModel, updatedObjects objects: [MyObject]?)
}
class MyDataModel {
weak var delegate: MyDataModelDelegate?
static var shared: MyDataModel = {
Now when your view controller appears it needs to assign itself as a delegate MyDataModel.shared.delegate = self. And implement the protocol in which a reload on the view must be made.
A callout to the delegate can simply be done in a model setter:
}()
var myObjects: [MyObject]? {
didSet {
delegate.myDataModel(self, updatedObjects: myObjects)
}
}
func reloadData() {
You can do something like that, using a technique called Key-Value Observation:
class CommonObservableData: NSObject {
// Use #objc and dynamic to ensure enabling Key-Value Observation
#objc dynamic var dateTime: Date?
static let shared = CommonObservableData()
func updateFromWeb() {
// callWebThen is a function you will define that calls your Web API, then
// calls a completion handler you define, passing new value to your handler
callWeb(then: { self.dateTime = $0 })
}
}
Then you observe on it using Swift 4 's new NSKeyValueObservation.
class SomeViewController: UIViewController {
var kvo: NSKeyValueObservation?
func viewDidLoad() {
...
kvo = CommonObservableData.shared.observe(
\CommonObservableData.dateTime, { model, change in
self.label.text = "\(model.dateTime)"
})
}
}
Key-Value Observation is originally an Objective-C technique that is "somewhat revived" by Swift 4, this technique allows you to observe changes on a property (called a Key in Objective-C) of any object.
So, in the previous code snippets, we made a class, and made it a singleton, this singleton has an observable property called dateTime, where we could observe on change of this property, and make any change in this property automatically calls a method where we could update the UI.
Read about KVO here:
Key-Value Observation Apple Programming Guide
Key-Value Observation using Swift 4
Also, if you like Rx and RFP (Reactive Functional Programming), you can use RxSwift and do the observation in a cleaner way using it.
In swift 4 and 5, the notification name is changed the below code working for both.
notifyCenter.addObserver(self, selector: #selector(new), name:UIApplication.willEnterForegroundNotification, object: nil)
#objc func new(){}
I've read that the UI should always be updated on the main thread. However, I'm a little confused when it comes to the preferred method to implement these updates.
I have various functions that perform some conditional checks then the result is used to determine how to update the UI. My question is should the entire function run on the main thread? Should just the UI update? Can / should I run the conditional checks on another thread? Does it depend on what the function does or how fast you want it done?
Example a function that changes the image inside an ImageView without threading:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
topImageView.image = lastDrawing
}
else {
// empty
topImageView.image = nil
}
}
}
Should I be setting topImageView.image on the main thread? Like this:
#IBAction func undoPressed(_ sender: Any) {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
Should I be using a background thread for the conditional checks? Like this:
#IBAction func undoPressed(_ sender: Any) {
DispatchQueue.global(qos: .utility).async {
if !previousDrawings.isEmpty {
previousDrawings.remove(at: previousDrawings.count - 1)
if let lastDrawing = previousDrawings.last {
DispatchQueue.main.async {
self.topImageView.image = lastDrawing
}
}
else {
DispatchQueue.main.async {
self.topImageView.image = nil
}
}
}
}
}
If someone could explain what method is preferred and why that would be really helpful.
Back up. Except in special circumstances, all your code is run on the main thread. UIAction methods, for example, are ALWAYS executed on the main thread, as are all the methods defined by UIViewController and it's various subclasses. In fact, you can safely say that UIKit methods are performed on the main thread. Again, your methods will only be called on a background thread in very special circumstances, which are well documented.
You can use GCD to run blocks of code on background threads. In that case, the code is being run on a background thread because you explicitly asked for that to happen.
Some system functions (like URLSession) call their delegate methods/run their completion handlers on background threads by default. Those are well documented. For third party libraries like AlamoFire or FireBase, you'll have to read the documentation, but any code that's called on a background thread should be very well documented because you have to take special precautions for code that runs on a background thread.
The usual reason to use a background thread is so that a long-running task can run to completion without freezing the user interface until it's done.
A common pattern for, example, is using URLSession to read some JSON data from a remote server. The completion handler is called on a background thread since it might take time to parse the data you get back. Once you are done parsing it, though, you'd wrap a call to update the UI in a GCD call to the main thread, since UI changes must be performed on the main thread.
First off, your undoPressed method will be called on the main queue.
In the first set of code, everything will be on the main queue.
In the second set of code, using DispatchQueue.main.async is pointless since the rest of the code is already on the main queue.
So really your only two sensible options are 1 and 3.
Given your code, option 1 is fine. You would only want to use option 3 if the code being run in the background took more than a trivial amount of time to execute. Since the code you have here is trivial and will take virtually no time to execute, there is no point in option 3 here.
So simply use your first set of code and you'll be fine.
Worry about moving code to the background when it need to perform a big loop or calculate a complicated algorithm or perform any sort of network access.
To make it simple, make the calculation and then everything related to that updated calculation that needs to be reflected in the UI should be done from:
DispatchQueue.main.async{ //code }
that is using main thread.
I feel like I'm missing something and this should not be too hard.
I'm reading in some data in the initial scene in my app.
I've got a singleton and I make the call in viewDidLoad to singleton.getData().
This initial scene is part of a tab controller. And while I thought viewDidLoad would only get called once for each scene I'm pretty sure it's being called a few times during the lifecycle of my app.
So just wondering if there is a way to ensure a function call to retrieve some data only happens once.
viewDidLoad will be called when selected tab is changed, you can change the place you call getData.
If you want to call getData in viewDidLoad and be sure it won't be called multiple times you can create a flag and check, if it is previously called or not.
class Singleton {
static let sharedInstance = Singleton()
private static var getDataCalled = false
func getData() {
if Singleton.getDataCalled {
return
}
Singleton.getDataCalled = true
// request data
print("data requested")
}
}
Singleton.sharedInstance.getData()
Singleton.sharedInstance.getData()
Calling getData multiple times print data requested only once.
so here is the problem:
I've got a few heavy views with many subviews, which I need to load and then to display. I want to do it asynchronously so that I don't block my main thread. When I tried to do it asynchronously I encountered the following dilemma:
After all of the heavy lifting job has been done and I return to the main queue to actually display that stuff, I get problems. First of all even though everything is done it takes 30-60 seconds for all the views to become visible. Sometimes they get misplaced. What could I be doing wrong and what should I be looking for ?
private func loadScrollViews() {
let qos = Int(QOS_CLASS_USER_INTERACTIVE.value)
dispatch_async(dispatch_get_global_queue(qos, 0)) { () -> Void in
// Creating many UIViews
for var i = 0; i < 100; i++ {
let view = UIView(frame: someFrame)
self.viewCollection.append(view)
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.displayViews()
})
}
}
private func displayViews() {
for view in self.viewCollection {
self.contentView.addSubview(view)
}
self.activityIndicator.stopAnimating()
self.contentView.hidden = false
}
After displayViews gets executed as I said views take almost a minute to appear on the screen.
UIView manipulation should be done on main thread
from doc
Threading Considerations
Manipulations to your application’s user interface must occur on the
main thread. Thus, you should always call the methods of the UIView
class from code running in the main thread of your application. The
only time this may not be strictly necessary is when creating the view
object itself but all other manipulations should occur on the main
thread.
if you have to create many UIView object then do it like this
dispatch_async() but use main_queue for this purpose. And one more thing if you want use background thread then think about using CALayer we can do most of CALayer work on background thread