Need technical advice about passing data through UINavigationController - ios

First of all a small introduction, im relatively new to Swift and to programming in general, been doing it for the last year and loving every and each new thing of this vast world.
My post is about some technical advices and to know if the decisions that are being made in my company make some sense.
I will first address the design that is being suggested and next my conclusions.
The design that's being implemented;
We are working in a big app, this app has some flows where they follow a sequence of 5 to 8 controllers, our team leader insists in this design pattern;

Let’s call the first controller a holder and the holder(black border) is responsible to have a container, this container has a proper navigation controller(red border), also, the holder hold all the data(orange) that those secondary controllers are generating.
Diagram of what this pattern is trying to achieve
To do this every red border controller has a method:

private func getParent() -> HolderViewController? {
if let parent = navigationController?.parent as? HolderViewController {
return parent
}
return nil
}
And to write in holder we call the method;
getParent().someModelInstance.someModelProperty = "some data”
Conclusion;
Passing data through navigation controller seems to go against to the single responsibility principle.
Creating strong references in every controller, even if I ensure that the navigationController is properly deinit when flow ends, does not seem a good option, this could lead to memory leaks and retain cycles.
I cannot ensure that, for some hod reason, two controllers try to access the same property at the same time.
This seems the Singleton Design pattern but with a limited “scope”
Resolutions;
Since I am new and I’m working in a company, and, every company has a hierarchy my objective above all is to learn if my conclusions are wrong and have better and more concise explanation about this.
First of all, to address the problem of memory leaks I created a concurrentQueue.
Instead of accessing the model directly to write in it I tried to address it through a method that will use a keyPath instead of the model directly, this is the method I’m using to write in the model;
In holder:
class HolderViewController: UIViewController {
#IBOutlet weak var bottomNavigationContainer: UIView!
private var bottomNavigationController: UINavigationController!
private var someModel: SomeModel!
private let concurrentQueue: DispatchQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
override func viewDidLoad() {
super.viewDidLoad()
setupBottomNavigation()
}
private func setupBottomNavigation() {
rootController = SecondayViewController()
if let root = rootController {
bottomNavigationController = UINavigationController(rootViewController: root)
addChild(bottomNavigationController)
bottomNavigationController.view.frame = bottomNavigationContainer.bounds
bottomNavigationContainer.addSubview(bottomNavigationController.view)
}
}
}
extension HolderViewController {
public func setValueInModel<Value>(_ value: Value, forKey path: WritableKeyPath<SomeModel, Value>) {
concurrentQueue.async(flags: .barrier) { [weak someModelInstance] in
if var obj = someModelInstance {
obj[keyPath: path] = value
}
}
}
public func readFromHolder() -> SomeModel {
concurrentQueue.sync {
return self.someModelInstance
}
}
}
This methods will be called like this;
class SecondayViewController: UIViewController {
var someString: String = "some data"
private func getParent() -> HolderViewController? {
if let parent = navigationController?.parent as? HolderViewController {
return parent
}
return nil
}
private func setValueInHolder(string: String) {
getParent().setValueInModel(string, forKey: \.someModelProperty)
}
private func readFromHolder() -> String {
return getParent().readFromHolder().someModelProperty
}
}
This look like some messy code to do a simple thing, we could use closures, delegates, segues etc… but my team leader does not like the other, simpler and more common solutions. Forgot to mention, every of our controllers has a xib and we do not use storyboards.
I know the basics of how to use the other options, what I’m trying is to understand if this is or it isn’t a good solution and why, and if my way of thinking and my methods make any sense.
Remember, every conclusion I took or every solution I've implemented could be wrong, that’s why I’m sharing with you my thoughts in order to learn from your advices and experience
Thanks in advance. :)
Stay safe!

As a preface: this type of question may be more fitting for the code review community
I can tell just by looking at the first diagram where your concern starts. Seeing the data flow as a graph you notice that there is a cycle. There is a time an a place where this may be use full (for performance more than anything) and memory management is extremely important to keep in mind in that case.
You may notice that after the call to addChild(_:) adds the contained controller to children: [UIViewController] and sets its parent property. But this done for you by the library.
Similar to the concept of the ViewController where views are dumb and the logic is contained in the view controller. I would similarly decouple the children from the parent view controllers moving most of the logic away from the children and coupling using the appropriate mechanism.
With that said I rarely find much value in using KVO with swift there are other mechanism that accomplish the same thing.
It really does depend on what the relationships are between the controllers are and what functions they have. There isn't much there to go off of. I'll leave that up to you to discover what solution you really need and the best guidance I found for this was on NSHipster's blog describing the communication mechanism to use for loose/strong coupling vs one-to-one and one-to-many relationships.
Also should point out that:
if let root = rootController {
bottomNavigationController =
UINavigationController(rootViewController: root)
addChild(bottomNavigationController)
bottomNavigationController.view.frame =
bottomNavigationContainer.bounds
bottomNavigationContainer.addSubview(bottomNavigationController.view)
}
You should be using willMove and didMove and move any set up there.

Related

Memory Leak Kotlin Native library in iOS

I'm building a Kotlin library to use in my iOS app using Kotlin/Native. After I call some methods in the library from Swift, which works, I also want to call methods in Swift from the library. To accomplish this I implemented an interface in the library:
class Outbound {
interface HostInterfaceForTracking {
fun calcFeatureVector(bitmap: Any?): Array<Array<FloatArray>>?
}
var hostInterface: HostInterfaceForTracking? = null
fun registerInterface(hostInterface: HostInterfaceForTracking) {
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
}
}
This is implemented on the Swift side like this:
class HostInterfaceForTracking : OutboundHostInterfaceForTracking {
var t : Outbound? = nil
init() {
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
func calcFeatureVector(bitmap: Any?) -> KotlinArray<KotlinArray<KotlinFloatArray>>? {
do {
var test : Any? = (bitmap as! Bitmap).bitmap
return nil
} catch {
return nil
}
}
}
The TrackingWrapper looks like this:
class TrackingWrapper : NSObject {
static var instance: TrackingWrapper? = nil
var inbound: Inbound? = nil
var worker: Worker
override init() {
self.worker = Worker()
super.init()
initInboundInterface()
}
func initInboundInterface() {
runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
HostInterfaceForTracking()
}
}
func runOnMatchingLibraryThread(block: #escaping() -> Void) {
worker.enqueue {
block()
}
}
}
The function runOnMatchingLibraryThread is needed because every call to the TrackingLibrary needs to be called from the exact same thread, so the Worker class initializes a thread and enqueues every method to that thread.
The Bitmap in this case is simply a wrapper for an UIImage, which I already accessed with the .bitmap call, so I've tried to access the wrapped UIImage and save it in the test variable. The library gets the current camera frame from the Swift side every few frames and sends the current image wrapped as a Bitmap to the method calcFeatureVector depicted here.
Problem: My memory load starts increasing as soon as the app starts until the point it crashes. This is not the case if I don't access the wrapped UIImage (var test : Any? = (bitmap as! Bitmap)). So there is a huge memory leak, just by accessing the wrapped variable on the Swift side. Is there anything I've missed or is there any way to release the memory?
Looks like you have a circular dependency here:
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
You are asking a property inside HostInterfaceForTracking to maintain a strong reference to the same instance of HostInterfaceForTracking. You should be using [weak self] to avoid the circular reference.
EDIT:
Ok after seeing the rest of you code theres a lot to unpack. There is a lot of unnecessary bouncing back and forth between classes, functions and threads.
There is no need to use runOnMatchingLibraryThread to just create an instance of something. You only need to use that for the code processing the image itself (I would assume, I haven't seen anything so far that requires being split off into another thread). Inside TrackingWrapper, you can create a singleton more easily, and matching the swift pattern by simply doing this as the first line:
static let shared = TrackingWrapper()
And everywhere you want to use it, you can just call TrackingWrapper.shared. This is more common and will avoid one of the levels of indirection in the code.
I'm not sure what Worker or Inbound are, but again these can and should be created inside the TrackingWrapper init, rather than branching Inbound's init, to use another thread.
Inside initInboundInterface you are creating an instance of HostInterfaceForTracking() which doesn't get stored anywhere. The only reason HostInterfaceForTracking is continuing to stay in memory after its creation, is because of the internal circular dependency inside it. This is 100% causing some form of a memory issue for you. This should probably also be a property on TrackingWrapper, and again, its Init should not be called inside runOnMatchingLibraryThread.
Having HostInterfaceForTracking's init, also using runOnMatchingLibraryThread is problematic. If we inline all the code whats happening is this:
TrackingWrapper
init() {
self.runOnMatchingLibraryThread {
TrackingWrapper.instance = self
self.inbound = Inbound()
TrackingWrapper.instance?.runOnMatchingLibraryThread {
self.t = Outbound()
self.t!.registerInterface(hostInterface: self)
}
}
}
Having all these classes unnecessarily keep coming back to TrackingWrapper is going to cause issues.
Inside HostInterfaceForTracking 's init, no need to be creating Outbound on a separate thread. First line in this class can simply be:
var t : Outbound = OutBound()
Or do it in the init if you prefer. Either way will also remove the issue of needing to unwrap Outbound before using it.
Inside Outbound you are storing 2 references to the hostInterface instance:
this.hostInterface = hostInterface
instance.hostInterface = hostInterface
I would have imagined there should only be 1. If there are now multiple copies of a class that has a circular dependency, which has multiple calls to separate threads. This again will cause issues.
I'm still not sure on the differences between Swift and Kotlin. In Swift when passing self into a function to be stored, the class storing it would mark the property as weak, like so:
weak var hostInterface: ......
Which will avoid any circular dependency from forming. A quick google says this isn't how things work in Kotlin. It might be better to look into the swift side passing in a closure (lambda on kotlin) and the kotlin side executing that. This might avoid the need to store a strong reference. Otherwise you need to be looking into some part of your code setting hostInterface back to null. Again its a bit hard to say only seeing some of the code and not knowing how its working.
In short, it looks like the code is very over complicated, and needs to be simplified, so that all these moving pieces can be tracked easier.

Singleton variable not updating

The value of the variable 'switcheroo' in the view controller below is always the same when I attempt to access it via a singleton. I am trying to access its value from a custom label class that prints the characters of the label one by one. When the label is set, I try to get the updated value of switcheroo in the Viewcontroller singleton. However it always returns the initial value of switcheroo, not the updated value (which I can trace in the viewcontroller). Am I doing something wrong?
class TheViewController: UITableViewController, UIGestureRecognizerDelegate, UITabBarControllerDelegate {
static let shared = TheViewController()
var switcheroo = 0
... various operations that change the value of switcheroo...
}
class CustomLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var attributedText: NSAttributedString? {
didSet {
DispatchQueue.main.async {
let characterDelay = TimeInterval(0.01 + Float(arc4random()) / Float(UInt32.max)) / 100
for (index, _) in attributedText.string.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
print("switcheroo value in TheViewController is now: \(TheViewController.shared.switcheroo)")
super.attributedText = attributedText.attributedSubstring(from: NSRange(location: 0, length: index+1))
}
}
}
}
I would not suggest making a view controller a singleton solely for the purpose of some shared state or model property. View controllers have their own life cycle patterns (e.g. instantiated from storyboard scenes, released when they are dismissed, recreated when presented again, etc.), and you’re likely to have issues arising from dealing with separate instances of your view controller(s).
Instead, don’t try to fight the standard view controller life cycle, but rather just move this property into a separate object, e.g.
final class StateManager {
static let shared = StateManager()
var switcheroo = 0
private init() { }
}
Then your view controllers can use that:
class ViewController: UIViewController {
...
func examineSwitcheroo() {
print(StateManager.shared.switcheroo)
}
func updateSwitcheroo(to value: Int) {
StateManager.shared.switcheroo = value
}
}
This way, you enjoy this shared state, without entangling normal view controller life cycles in this process.
Now, what the right name for this singleton, StateManager in my example, depends entirely upon what this shared property is. But there’s not enough information about what this switcheroo object really is to offer better counsel on this matter.
And, probably needless to say, it’s a separate question as to whether you really should be using singletons at all for state variables and model objects, but that’s beyond the scope of this question.
If you have determined that having a ViewController singleton is the right decision, the likely answer is that you are not using that shared instance every time, instead accidentally calling the initializer at some point in your project (possibly Xcode is doing it automatically through interfaces).
To search through your entire project, you can use cmd + shift + F and then type TheViewController(). There should only be one occurrence (the shared instance). Be sure to also check for TheViewController.init(). That will find any time you do it.
If the issue persists, perhaps try setting the shared instance to self in the viewDidLoad method of TheViewController?
Hope this helps!
Don't manage your application's data in your view controller(s). The Cocoa and Cocoa Touch frameworks use the MVC paradigm, where the M is meant to stand for model, i.e. the application's data model. Any data that needs to be preserved, or that's relevant beyond the scope of the view controller, should be stored and managed in a model object. If you give your view controller's a reference to the model when you create them, you never need to worry about passing data from one view controller to another; instead, they each operate on the model, and any data they need comes from the model.

Method to update properties in ios swift code while keeping the fuction pure as possible and testable?

Long time IOS developer/tinkerer here. I mostly taught myself programming, OBJ-C back in the day and now Swift. So apologies in advance if things I ask are too basic, its partly because I may not be well versed on some fundamentals.
I am currently working on an app. Alongside it I have been reading a fair bit on writing testable code and testing in general. I am not talking about purely TDD but I would like the libraries that I am creating for the app to have a good testset available. Partly because its good practice and partly because its soemthing I want to learn to do better.
So here goes, in my app class(es) I have a number of functions that take in parameters and give an output (as you do!). However, a number of these functions also make changes to class properties as data in these properties will be used in other class functions. For example:
class SomeClass() {
var someArrayProperty: [String] = []
var someInputParameter: String
init(input: String) {
//some initialisation code
self.someInputParameter = input
//Call function to build object
let object = self.buildObject(inputParameter: self.someInputParameter)
}
func buildObject(inputParameter: String) -> SomeObject {
let objectToReturn = SomeObject(withInputParameter: inputParameter)
let stringToAddToArray = "Object 1 created"
self.someArrayProperty.append(stringToAddToArray)
return objectToReturn
}
}
From what I have read about the testing, the code should ideally be such that it should do one job and not change something outside of the code as it becomes untestable for complex code. Here, the issue I have is that I am directly changing the someArrayProperty from within the method, i.e. changing something outside of the method.
Although its not an issue for the code and everything works fine, I would like to understand what do you guys feel about things like this from a testing point of view in your own code? And what pattern/changes you generally follow in your own code to avoid it?
Again, apologies if its too basic a question but I think it will help me fill in gaps in my knowledge to be able to write more beautiful code rather than something that just works and breaks next time a minor update is done somwhere. :)
Thanks
So if your function is called buildObject, it should do job inside it and have no return value. But if you call it buildedObject, it should return constructed object. You can read more about in Apple Naming Methods documentation.
And so your code should look like this:
class SomeClass() {
var someArrayProperty: [String] = []
var someInputParameter: String
init(input: String) {
//some initialisation code
self.someInputParameter = input
//Call function to build object
let object = self.buildedObject(inputParameter: self.someInputParameter)
// Other code which modifies data
let stringToAddToArray = "Object 1 created"
self.someArrayProperty.append(stringToAddToArray)
}
func buildedObject(inputParameter: String) -> SomeObject {
let objectToReturn = SomeObject(withInputParameter: inputParameter)
return objectToReturn
}
}

Implementing VIPER architecture in iOS

I am implementing my project as per VIPER for the first time and I have some doubts regarding its implementation.This is what I have done so far:
1)Implement Login page
STEPS
i)User taps login button(on view controller).
ii)I have a request model where I store 'username' and 'password'.This is the structure of model:
struct Login{
struct Request{
var txt_email:String!
var txt_password:String!
}
struct Response {
var homeData:Dictionary<String,Any>
}
}
So I pass this Request object to the Interactor.
iii)In Interactor,I have different Workers(Worker class object methods) assigned to perform different tasks such as email validation,empty textFields validation etc.If all is well,the worker api method hits login API and passes the response back to Interactor via delegation.
iv)Update the 'Response' model in the above structure.
v)Now that I have the response in Interactor,I pass this response to the Presenter to do some manipulations according to what controller needs to display to the user.
vi)Pass the data to the Controller and it presents it to user.
Question 1:Am I doing everything right.If no , please show me the right way.If yes , please tell me if there is some room for improvement.
Question 2:I need to implement UITableView and UICollectionView on the home page and I think extensions is the way to go for them.I will follow the same strategy for Home page as well.But suppose , in 'didSelectRowAtIndexPath' , I need to show a pop up to user and I think it will be better for ViewController to ask Presenter directly about the data.But is it the correct way?If not what is the correct way?
Question 3:should I pass data from cellForRowAtIndexPath: to actual cell(MyCell:UITableViewCell) class methods and then assign values to UIElements?Yes or no?
Reference: https://medium.com/#javedmultani16/viper-architecture-viper-64f6cd91e6ec
We developer basically uses the MVC,MVP or MVVM architecture for the development as per the requirement. It is mattering which architecture you are choose to develop the application. Many factors affecting for selection of the software architecture like system designs, requirements, time-lines etc.
In Viper architecture, each block corresponds to an object with specific tasks, inputs and outputs. It is very similar to workers in an assembly line: once the worker completes its job on an object, the object is passed along to the next worker, until the product is finished.
V (View): View is responsible for the UI updates and show whatever the presenter tells it.
I (Interactor) : The Interactor is responsible for fetching data from the model layer, and its implementation is totally independent of the user interface.All the business logic written inside the Interactor. E.g. Get User Data API call written in the Interactor.
P (Presenter): Presenter performing role as intermediator it gets data from interaction and passes to View. (It may be data or any user action)
E (Entity): Basically it is contains the Object Model which is used by Interactor. E.g. Student,Friend,College etc.
R (Router): It contains navigation logic for the application. E.g. Next button action shows second screen.
Morever, I’ve use the PROTOCOL, which contains all the rules and work-flow for the particular module of the application. In iOS all the protocols written in the separate protocol swift file for each module.
Morever, I’ve use the PROTOCOL, which contains all the rules and work-flow for the particular module of the application. In iOS all the protocols written in the separate protocol swift file for each module.
Let’s see the file structure of it:
enter image description here
Benefits:
-All the modules are independent so VIPER is really good for large teams.
-It makes the source code cleaner, more compact and reusable
-It easier to adopt TDD (Test Driven Development)
-You can add easily new features to the existing application without changing other modules possibly.
-It can be applies SOLID principles.
-Reduced number of merge conflicts.
-It Makes it easy to write automated tests since your UI logic is separated from the business logic
struct Person { // Entity (usually more complex e.g. NSManagedObject)
let firstName: String
let lastName: String
}
struct GreetingData { // Transport data structure (not Entity)
let greeting: String
let subject: String
}
protocol GreetingProvider {
func provideGreetingData()
}
protocol GreetingOutput: class {
func receiveGreetingData(greetingData: GreetingData)
}
class GreetingInteractor : GreetingProvider {
weak var output: GreetingOutput!
func provideGreetingData() {
let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
let subject = person.firstName + " " + person.lastName
let greeting = GreetingData(greeting: "Hello", subject: subject)
self.output.receiveGreetingData(greeting)
}
}
protocol GreetingViewEventHandler {
func didTapShowGreetingButton()
}
protocol GreetingView: class {
func setGreeting(greeting: String)
}
class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
weak var view: GreetingView!
var greetingProvider: GreetingProvider!
func didTapShowGreetingButton() {
self.greetingProvider.provideGreetingData()
}
func receiveGreetingData(greetingData: GreetingData) {
let greeting = greetingData.greeting + " " + greetingData.subject
self.view.setGreeting(greeting)
}
}
class GreetingViewController : UIViewController, GreetingView {
var eventHandler: GreetingViewEventHandler!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents:.TouchUpInside)
}
func didTapButton(button: UIButton) {
self.eventHandler.didTapShowGreetingButton()
}
func setGreeting(greeting: String) {
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter
Regarding Question 2 and 3, here's answer with my simple example of Viper app using UITableView: https://stackoverflow.com/a/45709546/3990005.
The ViewController does not speak to the Interactor, but to presenter, so on cellForRowAtIndexPath: you should call the Presenter with information, which IndexPath was selected.
Regarding cell creation, what I like to do is have a setup method in UITableViewCell class, and in ViewController ask Presenter for data and pass it to the cell. Some of it is visible in the above example, and here's cell's setupCell method:
func setupCell(withSong: song) {
titleLabel.text = song.title
descriptionLabel.text = song.description
}

Sharing data between VIewControllers - iOS

For any object created I generally use two two scopes 1) Singleton 2) {local scope}. I am looking for something in between.
Say I have one object that 5 view controllers are editing. I want to share an object between view controllers without having to pass it between view controllers. But it should not also live throughout application since once I am done editing the object i don't need it anymore.
I don't want to inherit all view controller from another class an create a variable there. Since view controller are reusable for different objects. I want to create an object that comes to life before launch of first view controller, lives throughout the scope of 5 view controllers and then dies after I have saved it someway. Is there anyways I could do this in iOS.
An alternative is to use your AppDelegate. Within it you can declare a global var than 2 functions, a first one to get the current value and another one to set the value.
It might give something like this:
// Get AppDelegate instance
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate;
// Use your getter to get the current value
var something = appDelegate.getYourStuff();
// Or use a setter to set it, a modifier to modify oit
appDelegate.setYourStuff(yourStuff);
appDelegate.modifiyYourStuffAttribute(newAttributeValue);
Don't realize if such a method is a bad practice or not, but it works for me.
Open to other suggestions!
As Mat said you can do is in that what. For me better is to create specific class for that that will do one particular job.
class EditingSession {
class Factory {
private static let session = EditingSession() //do it lazy
static func create() -> EditingSession {
return session
}
}
func openSession() {
}
func endSession {
}
func getData () -> AnyObject {
...
}
}
In editing session create private initializer. Factory should give the shared instance.
You can pass this class to your ViewControllers and manipulate with the data. You can inject it or just set as property in your VC or VM.
If you are done with editing you should end session and clear data or other stuff.

Resources