Global Alamofire request (AppDelegate) - ios

My app needs to perform multiple URL requests via Alamofire but I would like to perform these tasks independently on views or what user does in UI. This basically means "on background" so the handlers which actually performing the tasks would not be deinitialized until they are done.
My idea would be to call these requests from shared AppDelegate via some kind of class methods. I have only one question now:
What would be the best way to implement this scenario?
My actual knowledge would create something like this:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
class func someKindOfClassRequest() {
// ...
}
func someKindOfRequest() {
// ...
}
// ...
}
And I would call the method this way:
AppDelegate.someKindOfClassRequest()
or with not-class func, which of course will not solve the issue:
let sharedDelegate = UIApplication.shared.delegate as! AppDelegate
AppDelegate.someKindOfRequest()

As mentioned in the comments, Alamofire uses closure-based completions so they will not get deinitialized even if the calling object is deinitialized. For the sake of keeping your code well organized, why not just create a class that does these things instead of throwing it into your AppDelegate? For instance, create a class called BankgroundRequestController:
class BackgroundRequestController {
static let sharedInstance = BackgroundRequestController()
class func someKindOfClassRequest() {
// ...
}
}
Then you can call these functions like:
BackgroundRequestController.sharedInstance.someKindOfClassRequest()

Related

How can I access arrays from other classes inside AppDelegate in Swift?

I have an array inside a class called Anacii and obviously an AppDelegate class. I'm trying to save two arrays, both located inside the Anacii class, to UserDefaults when the application terminates. Everything works fine except getting the two arrays from the Anacii class from the AppDelegate class. Both arrays have multiple values inside them (I tested that with some print statements) and I can access them from my root view controller just fine with all the values inside of them but they return as empty arrays when I get them from the AppDelegate class.
Here are the two arrays defined in the Anacii class:
class Anacii {
// MARK: - Anacs / Rarities
var anacs = [String]()
var rarities = [Int]()
...
}
Here's where I set the actual values inside my root view controller:
class HomeController: UITableViewController, CLLocationManagerDelegate {
private let a = Anacii()
...
override func viewDidLoad() {
super.viewDidLoad()
for _ in 1...10 {
collectAnac()
}
...
}
...
// MARK: - Other functions
func collectAnac() {
let rarity = a.generateRarity()
let anac = a.findAnac(rarity: rarity)
a.anacs.append(anac)
a.rarities.append(rarity)
...
}
}
And finally, here is where I try to access the variables from the AppDelegate class:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
let a = Anacii()
let anacs = a.anacs // Comes out as []
let rarities = a.rarities // Comes out as []
...
}
}
The two values at the bottom of the AppDelegate class (anacs and rarities) both equal [] (tested by using print statements).
Sorry if this is a duplicate question, I looked at a lot of other posts like this but could find no answer that worked for me. Thanks!
TL;DR: You should read the whole thing but... Trying to access two arrays from the Anacii class from the AppDelegate class returns empty list, even though those two arrays are NOT empty (other classes see them with all the values they have). See the code above. Sorry if this is a duplicate post.
Putting it simply, you don't do this from the AppDelegate class. You do it from the class where the data actually is. In HomeController, register for the appropriate notification (e.g. the app is being backgrounded) from UIApplication and respond to it.
By the way, that notification should not be applicationWillTerminate, as it is never called.
(The actual reason for the phenomenon you're seeing is that Anacii() in AppDelegate is the wrong object. But it's best to do this the right way.)

Is there a way to have a custom class act on a UIView without having the ViewController passed (as reference) upon its initialization?

Example: I have a SpeechSynthesizer class that needs to update something in my UIView when it’s done uttering a piece of text. Since the SpeechSynthesizer class conforms to protocol AVSpeechSynthesizerDelegate, it is the one that receives the didFinish signal when the uttering has been completed. The idea here is to keep the ViewController from having too many delegate methods and a long list of protocols to conform to. The workaround I found was to have the ViewController passed in as a SpeechSynthesizer initialization parameter. This way I get to access the ViewController connected to the UIView I want to update from inside the SpeechSynthesizer class. The thing I don’t like about it is that it looks kind of ugly to have the ViewController passed in as a parameter to every single class that needs to use it. So I wonder, which other way I could accomplish this.
I suppose another way to ask the question is: How can I make the function
private func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance)
return something to a ViewController since it's not "called" by it?
I added a reply on Quora. Copying it here:
After doing some research and testing on code of my own here are 2 solutions to this problem.
Solution 1: The Delegate Pattern:
Create a custom delegate protocol in the ViewController
protocol ViewControllerDelegate:class {
func getViewLayer() -> CALayer
}
The ViewController must conform to this newly created protocol and therefore implement all the functions defined by it, so somewhere in the class ViewController you add:
public func getViewLayer() -> CALayer {
return self.view.layer
}
Then on my custom class, ReadTextMachine, I added a variable of the ViewControllerDelegate type
private weak var viewControllerDelegate: ViewControllerDelegate?
The variable must be weak and protocol must be of type class in order to solve a “retain cycle” problem (since both the custom class and the ViewController will point to each other)
You’ll notice now that the function call inside the ViewController is already “callable” from the custom class, so in my ReadTextMachine I added:
let viewLayer = self.viewControllerDelegate?.getViewLayer()
self.cameraPreview = CameraPreview(session: self.camera.getSession(), container: viewLayer!)
self.cameraPreview?.addPreview()
In the above case, my CameraPreview (yes, a 3rd class in this example) simply adds a camera preview layer on the UIView. For that it needed access to the main View’s layer.
The above code still doesn’t work because our original viewController’s instance hasn’t been passed as reference anywhere in our code. For that we add the following function in ReadTextMachine:
public func setViewControllerDelegate(viewController: ViewController) { // call this from the ViewController so that ViewController can be accessed from here.
self.viewControllerDelegate = viewController
}
The above piece of code will have to be called from the ViewController, after we instantiate our custom class (ReadTextMachine), so that the viewControllerDelegate inside it points to the ViewController. So in our ViewController.swift:
operatingMode = ReadTextMachine()
operatingMode.setViewControllerDelegate(viewController: self)
Another example and explanation can be found in this video from LetsBuildThatApp. I derived my solution mostly from it.
My current app in development applying the above solution can be found here: agu3rra/World-Aloud
Solution 2: Notifications and Observers pattern
This one is a bit easier to understand and follow. The general idea is to have your custom class broadcast a message which triggers a function call on your ViewController since it has an observer setup, waiting to hear that message.
So to give an example, in the context I used it, I have a CameraCapture class which uses AVFoundation to capture a photo. The capture photo trigger cannot immediately return an image, since iOS has a set of steps to execute before actually generating an image. I wanted my ReadTextMachine to resume an activity after CameraCapture had a photo available. (To apply this in the context of the CustomClass triggers ViewController event is basically the same, since both are actual classes in an iOS app as well).
So the 1st thing I did was create a broadcast function since I would use it in many places in my app. I simply placed it in a Utilities.swift file in the Xcode project.
public func broadcastNotification(name: String) {
let notification = Notification.Name(rawValue: name)
NotificationCenter.default.post(name: notification, object: nil)
}
The above function takes a string, which must be a unique notification identifier, and broadcasts it thru NotificationCenter.
In my CameraCapture class, I added a static constant to reference the unique identifier of the message:
static let NOTIFY_PHOTO_CAPTURED = "agu3rra.worldAloud.photo.captured"
For those who know AVFoundation, a photo is available when event didFinishProcessingPhoto gets executed, so at the end of that I added:
broadcastNotification(name: CameraCapture.NOTIFY_PHOTO_CAPTURED)
The above is a call to my previously defined utility function.
For my ReadTextMachine class to be able to catch that notification, I added the following on its init() and deinit routines:
override init() {
super.init()
// Setup event observers
let notification1 = Notification.Name(rawValue: CameraCapture.NOTIFY_PHOTO_CAPTURED)
NotificationCenter.default.addObserver(self,
selector: #selector(self.processingDoneTakingPhoto),
name: notification1,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self) // cleanup observer once instance no longer exists
}
Removing the observer is important at deinit so that when your object is deallocated from memory, the observer isn’t left lingering around. The above configured observer triggers a function call inside ReadTextMachine:
#IBAction private func processingDoneTakingPhoto() {
// does my stuff
}
That’s it! Again, the entire Xcode project can be downloaded from my project’s Git repository: agu3rra/World-Aloud
Hope this can be of use to others.
Cheers!

How to get references for ViewController

Community!
I have a question regarding References in Swift 3 and Xcode.
My Plan is this:
At the moment my App has 4 Controllers. A MapViewController, a PositionController, a MarkerController and a APIDataController. Those of course handle the respective Models. My problem now comes in, when I want the controllers to communicate. Look at this code (Please ignore that most functions aren't implemented here):
First my MarkerController:
import Foundation
protocol MarkerControllerDelegate: class{
func willGetMarkerArray()
func didGetMarkerArray(_ newMarkerArray: Marker)
}
class MarkerController: NSObject{
var markerArray = [Marker]()
weak var delegate: MarkerControllerDelegate?
func createMarkerArray(){
}
func getMarkerArray() -> [Marker]{
return markerArray
}
}
extension MarkerController : APIDataControllerDelegate{
func didRetriveAPIData(_ APIDataModelArray: APIDataModel) {
createMarkerArray()
}
}
And now my APIDataController:
import Foundation
protocol APIDataControllerDelegate: class{
func didRetriveAPIData(_ APIDataModelArray: [APIDataModel])
}
class APIDataController: NSObject {
var APIDataModelArray = [APIDataModel]()
weak var delegate: APIDataControllerDelegate?
func retriveAPIData(){
//get the data
//.....
//finished getting data
delegate?.didRetriveAPIData(APIDataModelArray)
}
func getAPIDataModelArray() -> [APIDataModel] {
return APIDataModelArray
}
}
And finally the beginning of my MapViewController:
import UIKit
import GoogleMaps
import GoogleMaps
import CoreLocation
class MapViewController: UIViewController{
let positionController = PositionController()
let apiDataController = APIDataController()
let markerController = MarkerController()
override func viewDidLoad() {
super.viewDidLoad()
apiDataController.retriveAPIData()
//blablabla
}
//blablablabla
}
Now, this might be a lot of code at once, so let me explain you what is ought to happen:
At the beginning (or wherever in the code) MapViewController calls the retrieveAPIData method. Once the function is finished, APIDataController is planned to notify the MarkerController via the APIDataControllerDelegate-Protocol to start his work (run his createMarkerArray-Method).
Now, to do this, I have to declare a markerController-Instance as a Delegate to APIDataController. Now, since I have already created instances of all my controllers in mapViewController, I want to give those references to my other controllers where needed so that they all refer to the same Instances, kinda like a Singleton.
But, how do I create a Reference to my MapViewController IN my MapViewController, to pass it to my other Controllers so that they can from there go like:
let positionController = mapViewController.positionController?
Nothing I tried seem to work. So any help would be appreciated.
Also if there are better ways to let my Controllers communicate I am open to here them, I am sure there is a better way to do it than I currently try to do.
Using Delegates for the communication between the controllers is the recommended way to do it. Setting the delegates depends on the way these controllers interact in your app. Often you will have a segue from one app to another, that is a good chance to set delegates. You can also do it in code that is putting together your views

class instance to all ViewControllers

I wish to avoid writing let APIHelper = API() in every UIViewController, instead I did this:
extension UIViewController {
func APIHelper() -> API {
let api = API()
return api
}
}
and now it is working like self.APIHelper().callMethod(), but I'm not really sure if it is the way to do it. Any tips on best practice?
Your extension useless, since it just same as calling API() everytime:
self.APIHelper().callMethod()
self.APIHelper().callSecondMethod() //here you created another API instance
same as
API().callMethod()
API().callSecondMethod()
If API is singletone, idea looks ok, but in swift you usually create singletone with static constant:
class API {
static let sharedAPI = API()
//...
}
and access to it like this:
API.sharedAPI.callMethod()
API.sharedAPI.callSecondMethod() //now called in same API instance
If you don't want to write API.sharedAPI everytime, then you can use:
Your extension
extension UIViewController {
var apiHelper: API {return API.sharedAPI}
}
Not recommended as #NickCatib explained.
Base view controller
as #NickCatib suggested (easier with variable):
class BaseViewController: UIViewController {
// some of the code you might need
let apiHelper = API.sharedAPI
}
Protocol
If you use API in view controllers time to time, can be better declare protocol
protocol APIHelper {
var apiHelper: API {get}
}
with default implementation
extension APIHelper {
var apiHelper: API {return API.sharedAPI}
}
and connect it to your viewController only when needed
class ViewControllerThatNeedsAPI: UIViewController, APIHelper {
// apiHelper avalible here
}
With all three ways you access your API like this:
apiHelper.callMethod()
How about some kind of base view controller where you can extend with that declaration? This way ALL of your view controllers have that function, even when you don't need it.
This would go like
class BaseViewController: UIViewController {
// some of the code you might need
func APIHelper() -> API {
let api = API()
return api
}
}
And later:
class ViewControllerThatNeedsAPI : BaseViewController {
// You have it here
}
Another approach that I actually use is to have service/manager for API calls that handles that, and send all the data needed via delegate/NSNotification/completion handler. That way your code will be cleaner and easier to test ( if you practice tests ). If you keep everything in the view controller you will break the SRP. This managers are PONSO - Plain old ns objects. You could use the same way as for view controllers and have some BaseService with API URL, basic stuff that needs to be overriden etc. After that you just implement service and call it when needed - depending on the implementation have some function to reflect data to UI.

Passing class Object refrence to another class then trying to use it is not working

I am trying to pass a reference of my current NSObject Class through two other object classes so I can access the current initialization of the original NSObject class I called from.
I will try to outline why I am doing this in as simply as possible. I have 3 NSObject Classes and an appDelegate.
AppDelegate
RemoteSites
EngineRequest
EngineReasponse
This is the logical flow of the app as it stands
Appdelegate.m
calls RemoteSites method "GetRemoteSites" this method reutrns a BOOL for confirmation
RemoteSites.m
-(BOOL)GetRemoteSites {
// calls EngineRequests method like so
EngineRequests *engineRequests = [[EngineRequests alloc] init];
[engineRequests GetRemoteSites:self];
//..
}
EngineRequests.m
- (void)GetRemoteSites:(NSObject *)myObjectClass {
// get everything ready to send off request
}
send off request then return recived data + NSObject refrence to EngineReasponse
EngineReasponse.m
- (void)GetRemoteSites:(NSData *)receivedData Object:(NSObject *)requestingClass
{
// pass requestingClass to a NSObject var that will later be used to pass the data back to the original class that started the request
requestingClassObject = requestingClass
}
//..
[requestingClassObject GetRemoteSitesNow:reducedDataPacket]; // GetremoteSitesNow is a method inside RemoteSites class, however using requestingClassObject I cannot see any of the classMethods my class has in it
//..
So thats the overall flow of the process I am trying to complete, the whole point is to try and get -(BOOL)GetRemoteSites to return Yes to the AppDelegate.
In summery my question stands as this. Why can I not access RemoteSites methods from EngineReasponse's, I have passed the class Object refrence correctly I think but for some reason I cannot access the methods.
Any help solving my issue would be greatly appreciated.
EngineRequests.m
- (void)GetRemoteSites:(id)remoteSites {
// create your class object here or globally.
RemoteSites *remotesite = (RemoteSites*)remoteSites
}
EngineReasponse.m
- (void)GetRemoteSites:(NSData *)receivedData Object:(id)requestingClass
{
RemoteSites *requestingClassObject = (RemoteSites*)requestingClass
}
//
[requestingClassObject GetRemoteSitesNow:reducedDataPacket];
//
Sorry for the typo. Hope it will help.

Resources