Can I override generic properties declared with a protocol in Swift? - ios

Would such an approach be fine in Swift or is it a bad idea:
protocol Serializable {
func serealize()
}
class SomeBaseVC: UIViewController {
var serialisableObject: Serializable?
override func viewDidLoad() {
super.viewDidLoad()
serialisableObject?.serealize()
//Do some other generic stuff with serialisableObject
}
}
class JSONObject: Serializable {
func serealize() {
//serealize
}
}
class SomeChildVCWhichHasSomeGenericBehaviour: SomeBaseVC {
override var serialisableObject: JSONObject
override func viewDidLoad() {
super.viewDidLoad() //Now this would do serealisation of JSONObject
//And now do something specific to just this VC
}
}
So the point is to have some generic behavior that is shared by many of my view controllers implemented in my superclass using protocols. Say I have a lot of view controllers that need to serialize some object and save it in viewDidLoad method (this is obviously a hypothetical example). This could be a some JSON data or some XML data. Now I could everytime implement a different viewDidLoad method, depending on whether controller is working with XML or JSON, but as shown above I think I can encapsulate it in the base class, then I could just inherit from this base VC and just call super.viewDidLoad(). The only part that bothers me is:
override var serialisableObject: JSONObject
Am I allowed to do that? Is that a good idea?

You cannot change the type of serialisableObject since that would violate the LSP
You can use a setter/getter to effectively alias another property in your subclass to the superclass serialisableObject property. This would allow you to use the specific type in your view controller subclass whilst the superclass would use the alias:
class SomeChildVCWhichHasSomeGenericBehaviour: SomeBaseVC {
var jsonThing: JSONObject?
override var serialisableObject: Serializable? {
get {
return jsonThing
}
set {
self.jsonThing = newValue as? JSONObject
}
}
override func viewDidLoad() {
super.viewDidLoad() //Now this would do de-serealisation of JSONObject
print(self.jsonThing)
}
}

Related

Accessing a Running Instance of a Class Swift

I have class A in file A and class B in file B. In class B, I want to access the running instance of class A, in order to run a function located in it. Both classes are connected to view controllers. I do not want to create a new instance of class A as in classAInstance = classA(), but rather access the instance of class A that my app is already running. Any help would be appreciated.
Here is part of my code in class A:
Class A {
func reloadTableView() {
self.CardsTableView.reloadData()
}
}
And here is part of my code in class B:
Class B {
#IBAction func saveButton(_ sender: UIButton) {
// here is where I want to call reloadTableView() from class A
}
}
The quick and dirty method I might use would be where you have some singleton where you can store the current instances of your classes.
Example:
class EnvironmentUtility {
private static var instance: EnvironmentUtility?
internal class func shared() -> EnvironmentUtility {
guard let currentInstance = instance else {
instance = EnvironmentUtility()
return instance!
}
return currentInstance
}
var myClassA: ClassA? = nil
var myClassB: ClassB? = nil
}
Then in the viewDidLoad (Or wherever else you like that a new instance is being made) of the those ViewControllers/Classes:
class ClassA: UIViewController {
…
override func viewDidLoad() {
…
EnvironmentUtility.shared().myClassA = self
}
…
}
Later in ClassB you could then:
class ClassB: UIViewController {
…
#IBAction func saveButton(_ sender: UIButton) {
EnvironmentUtility.shared().myClassA.reloadTable()
}
…
}
This isn’t the prettiest or Swifty-est way of doing it, but quick and dirty.
If you want to write a better solution I would suggest looking at the MVVM-C Swift architectural pattern (I use this pattern myself). In this architecture you will have access to a Coordinator that overseas viewController transitions and you can also track current instances of your ViewControllers/Classes in a much more elegant way.
Here is a crash course in MVVM-C: https://marcosantadev.com/mvvmc-with-swift/

RxSwift subclassing best practices

I have a view model that's being used in two flows and has gotten to the stage where it should really be split out into a super class and two subclasses. However, I'm getting confused as the best way to go about performing some subclassing.
On creation of the view model, I pass in all the interactions that could happen from the view like so:
View
class SomeViewController: UIViewController {
#IBOutlet private weak var nextButton: UIButton!
private var presenter: SomeViewModel!
override func viewDidLoad() {
super.viewDidLoad()
presenter.configure(nextButtonTapped: nextButton.rx.tap.asDriver())
}
}
Then I can handle these actions within my view model like so:
ViewModel
class SomeViewModel {
private let normalFlow: Bool
private let diposeBag = DisposeBag()
init(normalFlow: Bool) {
self.normalFlow = normalFlow
}
func configure(nextButtonTapped: Driver<Void>) {
handle(nextButtonTapped: nextButtonTapped)
// call to any other input handlers here...
}
func handle(nextButtonTapped: Driver<Void>) {
nextButtonTapped.drive(onNext: { [unowned self] in
guard self.safetyCheckOnePasses(), safetyCheckTwoPasses() else {
return
}
if normalFlow {
// do some set of actions
} else {
// do another set of actions
}
}).disposed(by: disposeBag)
}
func safetyCheckOnePasses() -> Bool {
// perform some sanity check...
return true
}
func safetyCheckTwoPasses() -> Bool {
// perform another sanity check...
return true
}
}
I'm getting confused as to what the best way to override the handle(nextButtonTapped: Driver<Void>) is because I still want those sanity checks to happen at the start of the onNext for every subclass, but I want the body after that to be different for the different subclasses. What would be the best way to go about this without duplicating code?
Rx is part of the functional paradigm and as such, subclassing is not appropriate.
Move your safetyCheckOnePasses() and safetyCheckTwoPasses() functions out of the class (or at least make them static.) That way they can be reused without needing an instance.

I'm passing data from UITableViewController to UIViewController through protocols and instead of int value I'm getting nil. Why?

I have two UITableViewController, the first one:
protocol FetchUserProfileData {
func getNumberOfRequests()
}
class ListEvents: UITableViewController{
var fetchInfo:FetchUserProfileData?
func getNumberOfRequests() -> Int{
return 12
}
and the UIViewController:
class UserProfileDetails:UIViewController, FetchUserProfileData {
var listEvents: UserListEvents?
func getNumberOfRequests(){
}
override func viewDidLoad(){
listEvents?.fetchInfo = self
print(listEvents?.getNumberOfRequests())
and this line: print(listEvents?.getNumberOfRequests()) gives me a nil value instead of 12... What's wrong here?
---- edit
Ok, now I see that listEvents is empty... So my question is how can I pass that data from ListEvents to UserProfileDetails?
In this code, listEvents is probably nil.
But, the way you use the protocol looks odd to me. I would expect:
getNumberOfRequests in the protocol to return Int
ListEvents should be implementing the protocol, not UserProfileDetails
The empty getNumberOfRequests() in UserProfileDetails should be deleted
You did not set listEvents. When you are using story boards then you should set the fetchInfo not earlier than in (overwriting) prepareForSegue. Google for examples, the web is full of them. When you segue programmatically then you can set the property not before you actually instanticated the new view controller. You are better of using listEvents!.fetchInfo = self because in that case you'll get an exception when listEvents is nil.
I made some change your code and this will pass data from ListEvents to UserProfileDetails.
protocol FetchUserProfileDelegate {
func getNumberOfRequests()->Int
}
class ListEvents: UITableViewController,FetchUserProfileDelegate{
var userProfile: UserProfileDetails?
override func viewDidLoad() {
userProfile = UserProfileDetails()
userProfile?.delegate = self
}
// MARK: FetchUserProfileDelegate
func getNumberOfRequests() -> Int{
return 12 // return as your target Int
}
}
class UserProfileDetails:UIViewController {
var delegate:FetchUserProfileDelegate?
override func viewDidLoad() {
if let _ = delegate{
let resultInt = delegate?.getNumberOfRequests() // get the Int form ListEvents
print(resultInt)
}
}
}
The idea of moving data from one controller to another is very common. Most of the time this is done using a segue. A controller can have a function called prepareForSegue. This function gets called before the transition happens. Inside the prepareForSegue function, the system gives you destination controller object. You take that object and set your data in it. When the transition happens, and your destination controller comes up, it already has the data you want to give to it.
Use Xcode and make a new project. Choose "Master-Detail Application". This will generate the code for you and it is a good example of how to pass data between controllers.

Working with Model async data and TableView

I'm creating an app and I have all the logic done, but I want to do a Code refactoring and create MVC pattern. But I dealing with some asynchronous informations, that came from API.
/MenuViewController
Alamofire.request(.GET, Urls.menu).responseJSON { request in
if let json = request.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let data = JSON(json)
var product: [Product] = []
for (_, subJson): (String, JSON) in data {
product += [Product(id: subJson["id"].int!, name: subJson["name"].string!, description: subJson["description"].string!, price: subJson["price"].doubleValue)]
}
dispatch_async(dispatch_get_main_queue()) {
self.products += product
self.tableView.reloadData()
}
}
}
}
This is my code, already working. But I want to create a Model that will handle this and just return the array of Products to my MenuViewController.
Model/Menu
class Menu {
var products: [Product] = []
init() {
Alamofire.request(.GET, Urls.menu).responseJSON { request in
if let json = request.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let data = JSON(json)
var product: [Product] = []
for (_, subJson): (String, JSON) in data {
product += [Product(id: subJson["id"].int!, name: subJson["name"].string!, description: subJson["description"].string!, price: subJson["price"].doubleValue)]
}
dispatch_async(dispatch_get_main_queue()) {
self.products += product
}
}
}
}
}
func totalOfProducts() -> Int {
return self.products.count
}
func getProducts() -> [Product]? {
return self.products
}
func getProductFromIndex(index: Int) -> Product {
return self.products[index]
}
}
But I got my self thinking, how I gonna get the main_queue to another class?
So I tried something like this:
class MenuViewControlvar: UITableViewController {
var products: [Product] = []
let menu: Menu = Menu()
// MARK: View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
if let products = menu.getProducts() {
self.tableView.reloadData()
}
// rest of the code
But didn't worked. My TableView is never updated.
I was wondering if I can do this, or I've to keep my Alamofire code in my viewDidLoad() from my MenuViewController
Thank you.
I am just giving you a direction with the step I would follow (Not writing the code thinking you can work it out):
First, write a networking class that accepts network request along with a competition block. Completion block shall be executed as soon as networking is done. This is a wrapper class and can be used across classes.
Second, write a model class that has all the parameters necessary for view controller's functionalities/view drawing.
Third, from view controller, call the networking class. In completion block, pass the model setting, table reload code and any code to remove loading overlay/indicator. This block should get executed on main queue.
Fourth, add code to show loading overlay/indicator before you trigger networking.
Delegation is an ideal solution for this problem of updating your model data and your view based on an asynchronous network call and it’s pretty much the same technique that is implemented throughout the iOS SDK to solve the same problem. There are many benefits of delegation over observation, another viable solution.
First, move your networking code to a separate class
class NetworkingController {
Create a protocol that view controllers can conform to. This provides the loose coupling between your network operations and your views to effectively maintain separation between the MVC layers.
#protocol NetworkingControllerDelegate: class {
func menuDataDidUpdate()
}
Have the networking controller support a property for its delegate
weak var delegate: NetworkingControllerDelegate?
In summary your networking class now looks something like this:
#protocol NetworkingControllerDelegate: class {
func menuDataDidUpdate()
}
class NetworkingController {
weak var delegate: NetworkingControllerDelegate?
// Insert networking functions here.
}
Then, have your view controller conform to this protocol like so
class MenuViewController: NetworkingControllerDelegate {
and create a new network controller in your view controller
var myNetworkController = NetworkController()
and set the delegate of your network controller instance to be your view controller
myNetworkController.delegate = self
Then in your networking code, when the network request has completed and your model has been updated, make a call to the networking controller's delegate.
delegate.menuDidUpdate()
Create the implementation for this method in your view controller since it is now the delegate for your networking code.
func menuDidUpdate() {
// Update your menu.
}
This makes your view controller look something like:
class MenuViewController: NetworkingControllerDelegate {
var myNetworkController = NetworkController()
override func viewDidLoad() {
myNetworkController.delegate = self
}
// MARK: NetworkingControllerDelegate
func menuDidUpdate() {
// Update your menu.
}
}
This is just the outline of the implementation to give you the necessary information about how to proceed. Fully adapting this to your problem is up to you.

Typhoon Storyboard: Inject an IBOutlet View to a Controller dependency

I have a storyboard that has a view in it connected to his controller using an outlet.
In the same controller I want to inject an object that needs access to that view. Instead of passing that view manually to the object I would like to inject it automatically but I don't know how and If I can achieve that with the current code structure.
class LoadingViewController: UIViewController {
#IBOutlet weak var loadingView: UIActivityIndicatorView!
private(set) var loadingViewModel: LoadingViewModel! // Dependency Injection
}
// Assembly
dynamic func loadingViewController() -> AnyObject {
return TyphoonDefinition.withClass(LoadingViewController.self) {
(definition) in
definition.injectProperty("loadingViewModel", with:self.loadingViewModel())
}
}
dynamic func loadingViewModel() -> AnyObject {
return TyphoonDefinition.withClass(LoadingViewModel.self) {
(definition) in
definition.injectProperty("loadingView", with:???) // I want loadingViewController.loadingView
}
}
I think it has something to do with run-time arguments and circular dependency
That's a good one. We have to consider the life-cycle between the Storyboard created objects and Typhoon.
Have you tried something like:
//The view controller
dynamic func loadingViewController() -> AnyObject {
return TyphoonDefinition.withClass(LoadingViewController.self) {
(definition) in
definition.injectProperty("loadingViewModel",
with:self.loadingViewModel())
definition.performAfterInjections("setLoadingViewModel", arguments: ) {
(TyphoonMethod) in
method.injectParameterWith(self.loadingViewModel())
}
}
}
dynamic func view() -> AnyObject {
return TyphoonDefinition.withFactory(self.loadingViewController(),
selector:"view")
}
dynamic func loadingViewModel() -> {
return TyphoonDefinition.withClass(SomeClass.class) {
(definition) in
definition.injectProperty("view", with:self.view())
}
}
Creates a definition for the view, instructing Typhoon that it will be emitted from the loadingViewController
Creates a definition for the loadingViewModel that has view injected.
After the loadingViewController, and therefore view has been created, inject the loadingViewModel as the last step.
I don't recall if the scope pool is cleared before calling performAfterInjections. If is is you might need to set the scope of loadingViewController to TyphoonScopeWeakSingleton instead of the default TyphoonScopeObjectGraph.
Because of the interplay between Typhoon and Storyboards it might be just simpler to manually provide the instance in eg viewDidLoad. But can you give the above a try and get back to me?

Resources