Friday 28 June 2013

iOS App Architecture and TDD #1: The UIViewController Spaghetti Monster

We made it, Trinity.
You said we would.
It's unbelievable Trin...
We finally made another blog post.

Agenda

I want to talk about iOS app architecture and Test Driven Development.

For the past couple of years I’ve been developing the feeling that there’s something seriously wrong with how a lot of people are approaching iOS development. Apple has made certain decisions with their framework design and tools which I believe contributes to this problem.

Specifically I’m talking about where the application and business logic for an app belongs, and how the overall architecture of an app should be constructed. I feel I need to slap some people across the face and bring them to their senses (including myself).

This is going to be the first in a series of posts. Today we’re going to find out why Apple has created a total abomination of design with UIViewController. Should be fun.

In future posts I will describe an Uncle Bob style “Clean Architecture” for iOS apps, driven by TDD. I haven’t seen anything like this from other writers/developers, so be sure to subscribe (or follow me on twitter) if that sounds interesting.

The Humble View Controller?

Way back in 2002, Michael Feathers (of “Working Effectively with Legacy Code” fame) wrote a piece titled The Humble Dialog Box. In it, he describes a particular approach to separating GUI-specific code from the actual application logic behind a simple dialog box.

We’ll revisit this piece sometime in the future, but for now I want to focus on the introductory problems he describes. Remember he is talking about building desktop applications here:

Let’s take a look at our friend the dialog box. […]

Tool vendors have made it easy to create dialog boxes. Nearly every IDE on the market has a GUI builder. You can drag and drop all sorts of components on dialogs and drill down to wire them up. […]

It is easy to just override an event from a component and drop your interaction logic right there in the dialog box class.

He goes on to describe the problems that typically arise with these dialog box classes:

  • It’s difficult to get these UI classes under a test harness because they are so tied to UI-specific frameworks. There is no separation between the UI and interaction/application logic.
  • The UI classes have a mixture of low level component API code as well as actual decision making code, making it hard to see what the code is doing or to share code between objects with similar behaviour.

These statements perfectly describe a typical iOS view controller. It’s easy to drop code into an IBAction method or a delegate call on the view controller, and do everything right there and then. This can lead to a mixture of “component API” (i.e. UIKit) and important decision making code.

Apple has made UIViewController the center of the iOS universe. When you add a new screen to your app, the only thing that Xcode gives you is a new UIViewController subclass. And Apple makes it so easy to do everything from this view controller. If you aren’t really careful, you can end up with an application consisting entirely of view controller classes and little else.

"iOS architecture, where MVC stands for Massive View Controller"

Now of course there are good developers out there who are creating nicely decoupled classes, but if we list out some of the responsibilities that a view controller typically ends up dealing with, we’ll see just how pervasive this problem is, even in code written by experienced developers.

Spotted in the wild

View setup and layout code

By default, when you hook up a new control to your view controller’s XIB or storyboard (for example a UIButton or UILabel), the IBOutlet connection is going to go to a property on your view controller. The whole assistant-editor workflow is designed to do this. All of the sample code, tutorials and books you will ever read will do this as well.

If you need to tweak that control’s settings in code, your first stop will probably be the trusty -viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.myButton.titleLabel.font = myCustomFont;
    UIImage *stretchableImage = /* ... blurgh ... */
    [self.myButton setBackgroundImage:stretchableImage
                      forControlState:UIControlStateNormal];
    /* ... more 'blurgh' ... */
}

Over time, your -viewDidLoad will likely grow. You may even create new controls in here that don’t exist in your XIB file. Even if you break things out into multiple methods, a significant part of your view controller’s code can end up dealing with view setup and creation. The situation is worse if you forgo XIB files entirely.

And what if you have custom layout requirements which can’t be solved in IB?

Well… you could create a UIView subclass and implement -layoutSubviews. But you already have all your IBOutlets going to the view controller. You would have to move all the IBOutlet connections and properties to this new view class — and why bother with all that when it’s so easy to add some code to -viewWillAppear::

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.myLabel.frame = /* ... */
    self.myButton.frame = /* ... */
}

Even though using a UIView subclass and overriding -layoutSubviews is a far superior way to layout your controls (and is in fact the correct way), people end up putting this stuff in their view controllers because that’s the easiest thing to do.

And then they have to deal with view rotation issues, and not being told when the view’s frame changes. Even worse, because this stuff is so hard to do properly from the view controller, people often end up hard coding interface sizes and frames rather than implementing truly dynamic view layouts.

I’ve seen people doing layout in the -viewDidLoad method, before the view is even part of the view hierarchy. And they wonder why things mess up. You can’t even blame them. After all, it is Apple who is encouraging people to connect their IBOutlets to the view controller.

It all becomes a hideous mess, and could easily be solved with UIView subclasses. But Apple doesn’t teach us to do this. I doubt many novice or intermediate iOS developers have even heard of -layoutSubviews or -sizeThatFits:. All Apple gives us by default is a view controller subclass.

Even with Autolayout the problem doesn’t go away. The view controller still ends up being the place to create custom constraints, for the same reasons as above. That’s just how people do it. Even Apple engineers do it this way in their WWDC videos.

Apple engineers teaching us bad habits.

So the Xcode/Apple-approved default is to have all of your view setup and layout code in the view controller. Why is this a bad thing? Quite simply, because view layout and setup code should (in my opinion) always go in UIView subclasses. That’s where it belongs, and life is a lot easier when you do it this way. Putting this stuff in the view controller is just contributing to an already bloated and confused class.

Core Data and NSFetchedResultsController code

All of the Core Data sample code that I’ve seen from Apple creates and sets up the NSFetchedResultsController in a -fetchedResultsController method on the view controller. It’s usually a huge long method which sets up the fetch request, configures the fetchBatchSize, predicate, sort descriptors etc.

Xcode screenshot showing Apple's CoreDataBooks sample project creating and setting up the fetched results controller in the view controller.

Unfortunately I’ve seen a lot of people who have copy/pasted this kind of code into their view controllers. As time goes on, the view controller ends up with a dozen methods just dealing with Core Data - fetching results, updating records, performing background operations, merging contexts, etc.

None of this stuff belongs in the view controller. You have business logic like sorting and filtering rules mixed in with the details of the persistence framework (Core Data), and all of this inside the view controller. A “view controller” should not be dealing with this stuff, and Apple unfortunately does not show new-comers how to do this properly.

Be the delegate for everything

Even if you pull your Core Data code out into a nice class or interface, your view controller will probably end up being the delegate and datasource for the table view displaying that data. It’ll contain the code to create and configure table view cells for a given item, define section titles and row heights, handle insertion and deletion operations, and respond to any of the dozen or more delegate/datasource callbacks that are available.

If your app is making use of CLLocationManager, your view controller will probably end up responding to and handling location events. It might even be the one that creates the location manager in the first place. Another of Apple’s tutorials does just this.

If your view controller makes use of MPMoviePlayerController or QLPreviewController or MFMailComposeViewController you’ll probably be the delegate for those as well. You can also throw in Twitter and Facebook API callbacks (and sharing in general), quick one-off AFNetworking or NSURLConnection web service requests, NSTimers doing periodic background work, SCNetworkReachability checks, and the list goes on.

Because UIViewControllers are such a central part of an iOS app, they end up being the delegate for everything. They end up accumulating all this crap. If the view controller was purely forwarding information and acting as an intermediary, this might not be so bad. But we all know that’s rarely the case.

You really have to be on the ball to keep this stuff nicely separated, and I’ve seen a lot of codebases which fail to do this.

Accessing global state

Because it’s so easy to drop code into a view controller, and because Xcode doesn’t give you any other place to start adding your code, inexperienced developers have trouble accessing the information they need.

They end up using hideous hacks like ((IHaveNoIdeaWhatImDoing*)[[UIApplication sharedApplicaton] delegate]).objectThatINeed, or singletons, or notifications, to access everything they need. Stackoverflow and internet forums get inundated with questions of the form “How to I pass information from one view controller to another”?

The reason this is so difficult is that they haven’t been taught to write higher-level objects which deal with the interactions and flow of information in their app.

So not only do many view controllers have dozens of delegate calls coming in to them, they also have hideous clawed tentacles reaching out all over the application, accessing global state and further coupling the view controller to other parts of the application.

Some people have tried to contain the UIViewController...
This usually doesn't end well.

You can argue that this is just a problem of inexperienced developers, but Apple really does not help people to develop good coding practices. I’ve seen Apple sample code using [[UIApplication sharedApplicaton] delegate] to access global state, and it’s really a terrible practice to encourage. In fact I already have a blog post half-written which deals precisely with this topic.

Navigation logic and other “decision making” code

When the user selects a cell from your table view, you are going to handle the tableView:didSelectRowAtIndexPath: method.

Your app now has to decide what to do. In a typical drill-down style of navigation, you want to push some sort of “details” screen for the selected item.

Apple makes this really easy with convenience methods on UIViewController like -navigationController, -tabBarController, -splitViewController, etc. Your view controller can directly grab a reference to the container that holds it.

You can then do something like this:

- (void)tableView:(UITableView *)tableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    id item = [self itemAtIndexPath:indexPath];
    DetailsViewController *detailsVC
        = [[DetailsViewController alloc] initWithItem:item];
    [self.navigationController pushViewController:detailsVC
                                         animated:YES];
}

Perfect! The navigation logic can be dropped right there in the view controller!

But what if you later make your app universal, and the details screen needs to appear in a split view controller on iPad devices?

Well, no problem. We’ll add a little runtime check:

DetailsViewController *detailsVC = /* ... */;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    // display details in the split view controller's details pane
} else {
    // push details to the current navigation controller
}

We also need to keep the cell selected on the iPad, but deselect it in viewWillAppear: on iPhone devices (this is what UITableViewController does by default). This adds a few more if statements to the controller.

But what if you now want to present this view controller modally at some point in your application so the user can select one item for a task? In that case you need to dismiss the view controller and somehow notify the parent about which item was selected.

We can’t keep adding more and more if statements to our view controller. It’s pretty clear that this kind of navigation logic doesn’t belong in the view controller.

A view controller should not know about the high level work flow of your app. It shouldn’t know about its surroundings. It shouldn’t know that it’s being presented modally, or that it’s contained by a navigation controller, or a split view controller, or a tab bar controller. It shouldn’t even have references to these things, even though Apple decided to “help” us out by tacking on all these category methods. It shouldn’t even know about the DetailsViewController. That’s an entirely different screen of our app.

If our view controller is displaying a list of items (think a typical table view controller), that’s all it should be concerned with. It should provide some delegate callbacks for when a given cell or item is selected, and perhaps a few ways to customize the display or behaviour of the cells, for example to enable a check-multiple-items style of selection.

The same view controller being presented in different contexts, with different navigation items.

The view controller should only be concerned with the behaviour of the view that it presents. By putting navigation logic in the view controller, you are coupling it to the specifics of your app’s UI and navigation. If you want to change the workflow later (e.g. for other devices), or reuse that view controller in a different context (or even a different app), you are going to encounter a lot of pain.

So where should this navigation logic go? Well… somewhere else. We’ll get into that in a later post. All that matters for now is that the view controller should delegate UI actions like this to somebody at a higher level, who has access to and knowledge of the specifics of your app’s UI workflow.

This is also why I have a big problem with Storyboards. When you implement a Storyboard app, you are forced to put application-specific navigation logic in the view controllers by implementing the various UIViewController segue-related methods. There’s no other way to do it. And delegating this stuff out to somewhere else really confuses the matter because you have this extra level of indirection between the storyboard XIB and the segue code. Reusing view controllers in other apps or screens suddenly becomes impossible to do (or at least very difficult) without creating a total mess of things.

Official UIKit responsibilities

So far we’ve mostly looked at logic and code that could theoretically be pulled out of the view controller and put somewhere else. You’ll probably be doing this already to a greater or lesser extent.

But what about the stuff that a UIViewController must deal with? What about it’s official UIKit responsibilities?

In fact, what the hell is a “view controller” anyway? Does anybody actually know? Why do we now have view controller containment hierarchies as well as regular view hierarchies? How does it differ from a UIView in this case? What does this all have to do with MVC?

What is a view controller?

To figure this out, we are going to start pulling code out of some hypothetical view controller and see what’s left at the end.

First we’ll move all that horrible view setup and layout code into a UIView subclass. Done.

Then we’ll move all the core data and model code into some nice protocols/classes, which are injected into the view controller through public properties or an -init method. Done.

We’ll move the table view delegate/datasource code into dedicated classes. Done.

Any miscellaneous framework code, url requests, etc. will also be pulled out behind nice interfaces, and our view controller will simply funnel data back and forth between the view and these interfaces. Done.

Finally, let’s delegate navigation events like certain button presses and cell taps to somebody else, so the view controller’s behaviour can be customized easily for use in different contexts. Done.

What are we left with at the end of all this?

We’re left with a class that:

  • Lazily loads a view (by calling -loadView when the -view property is accessed for the first time)
  • Can load its view from a NIB file in a convenient manner.
  • Provides access to various events like -viewDidAppear:, -didRotateFromInterfaceOrientation:, and -didReceiveMemoryWarning.
  • Deals with state restoration to restore state between launches of your application.
  • Handles storyboard segues to pass data to the view controller that is being transitioned to.
  • Can provide accessibility information about the content it displays.
  • Allows you to define which interface orientations are allowed when its view is at the top of the view hierarchy.
  • Can be inserted into UIKit’s standard containers like UINavigationController and UITabBarController (also UIWindow), with a bunch of properties like navigationItem and tabBarItem to customise its appearance in these.
  • Can be nested inside another view controller in a UIKit-safe way.
  • Happens to be part of the responder chain, so can respond to and handle certain types of input events.

Wow. That’s a lot of stuff, and I’ve probably forgotten some things. It also sounds a hell of a lot like a view to me. The lines are starting to blur. It certainly doesn’t sound like a place for important business or application logic.

If you look at the actual interface for UIViewController — the methods and properties it declares, it’s almost all view/presentation related. Go on, pop open Dash and take a look right now. I can only count 5 methods out of 86 that are not presentation related. These are -didReceiveMemoryWarning and the 4 state restoration methods that came with iOS 6. Everything else is related to the view. And that’s not even including all the category methods that get added on by other UIKit headers.

An identity crisis

Who *are* you, UIViewController?

What if we continue to pull stuff out of UIViewController?

-viewDidAppear: and all the other view-related events would be much happier sitting on the UIView class instead, for instance.

Lazy loading also seems to be a detail that belongs elsewhere. Don’t we just want something like this?

UILazyLoadedView *lazy = [[UILazyLoadedView alloc]
    initWithLoadViewBlock:^{
        MyView *v = [[MyView alloc] init];
        // restore view state
        return v;
}];

Then we just change the UINavigationController and other container classes to accept a UILazyLoadedView where applicable. When our view needs to be displayed or loaded, UIKit can invoke the block and get a view returned to it. Any view state can be captured/restored in the block, making it work well when the view is unloaded/reloaded (prior to iOS 6 anyway).

Let’s look at the navigationItem/tabBarItem/etc. properties now.

These properties are used to customise the way the view controller appears in navigation controllers, tab bar controllers, etc. These customizations are always going to vary depending on the context in which the view controller is presented.

So do these properties really belong on the view controller? They’re actually part of the interfaces for the various container controllers that Apple gives us. But they’ve been “helpfully” tacked on to UIViewController via categories. Is this really a good idea? Didn’t we already establish that a view controller should only know about the content it displays and manages?

If a view controller represents a screen of your app, why does it need to know the title of its back button when another screen is being displayed? Why does it need to have access to the icon image in its tab bar button? Shouldn’t that be managed by the class that set up the tab bar controller, or that pushed it onto the navigation controller?

When we consider the multitude of responsibilities a view controller already has, does it really need to deal with the specifics of all these container controllers as well?

Couldn’t we remove these category methods entirely and provide the information when we do the actual push? Something like this:

UILazyLoadedView *lazyView = /*...*/;
UINavigationControllerItem *item = [[UINavigationControllerItem alloc]
          initWithLazyView:lazyView
                     title:NSLocalizedString(@"foobar", nil)
                     backButtonTitle:/*...*/
                     /* etc */];
                                 
[navController pushItem:item];

I don’t know. We’re getting dangerously close to the realm of pure fantasy here. But we need to think about these things to figure out exactly what a view controller is, and what it’s main roles should be. Everything we’ve seen so far seems to belong somewhere else.

The other thing view controllers can do is “view controller containment”. This can only be described as a complete (and unnecessary) mess.

Because a view controller is just a wrapper around a view, people ended up needing to nest or compose view controller views in a single screen. But things like rotation events, -viewDidAppear: etc. get all screwed up if you do this by manipulating the view hierarchy directly. This wouldn’t even be a problem if these methods were part of UIView instead.

So to make this work nicely, Apple has given us the view controller containment APIs, with weird-ass requirements in the order you call the various willMoveToViewController/didMoveToViewController/addChildViewController/etc. methods.

And now that we have this view controller containment, a view controller no longer represents an entire screen of content. It represents a portion of the screen and can be nested arbitrarily with other view controllers. So… doesn’t that just make it a view? A view with some additional UIKit details hacked on?

Somehow this all feels like a big mess to me. I’ve only covered a few items from UIViewController’s interface, but it’s already starting to look like this weird “miscellaneous crap that UIKit needs” class with a lazy loaded view inside it. It’s a confused mess of responsibilities.

It also happens to contain the majority of our application code. This is a problem.

A black hole of bad design.

Let’s summarise the key points so far.

UIViewController is the centre of the iOS world. Apple’s tools like Xcode and Interface Builder, and the workflow they encourage, put the view controller at the centre of everything. The default place to start when you’re adding new code or features is the view controller. When you add a new screen to your app, all you’re given is a UIViewController subclass and a lot of helpful methods to shoot yourself in the foot.

Because of this, code and logic tend to gravitate towards the view controller. The view controller ends up being the delegate for a large number of different objects. It ends up doing view setup and layout, it ends up dealing with core data, table view datasource/delegate methods, and a huge number of other responsibilities.

Apple has tacked on a large number of “convenience” methods which let you directly access the enclosing containers like navigation controllers or tab bar controllers. This makes it very easy to couple the view controller to the specifics of your app’s navigation, and to the other view controllers it interacts with. Storyboards also contribute to this problem. This makes it difficult to reuse the view controller in other contexts or applications.

Even if you manage to keep your own interfaces/APIs nicely decoupled and separated into other classes, UIViewController is the only place to deal with certain UIKit details, like: lazy-loading of views, configuring the display of tab bar items and navigation controller elements, view events like viewDidAppear, rotation events, and view controller containment when you need to nest multiple view controllers inside one another.

If you have any kind of decision making logic in your view controller, it is completely coupled to the UIKit framework. As we’ll see in my next post, trying to get these things into a test harness is a disaster waiting to happen.

UIViewController is just a detail of UIKit’s implementation. It’s more of a view than a controller, and certainly isn’t anywhere close to being a Controller in the classical MVC sense, despite what Apple tries to tell us:

WWDC Slide: View controllers are just controllers - the "C" in MVC.

“Controllers” are supposed to be nice thin objects funnelling data and events between the model and the view. They shouldn’t contain details of a specific UI framework. iOS’ View controllers are nothing like this. They sit firmly in the presentation layer.

So why are we putting so much of our important application and workflow logic into our view controllers? I feel like the iOS developer community has forgotten everything we have learnt about good OOP design in the last 20 years or so. Remember the Single Responsibility Principle? One Reason To Change? Mixing application logic in amongst the spaghetti of UIKit APIs is just plain wrong.

Isn’t there a better way? Isn’t there an escape from this madness?

I’m happy to tell you that there is a better way.

I’m going to take you on a journey, Dear Reader. A journey of self-discovery and enlightenment. Or… something like that. We’ll figure it out as we go.

We’re going to develop a Clean iOS Architecture together, driven by TDD. We’re going to find out why so many people are doing iOS TDD wrong, and how to do it right. And we’re going to see some really nice benefits from this new approach.

Won’t you join me?


PS: I’m currently looking for new job opportunities, preferably in the UK. London would be ideal. Please contact me using the email address at the top of this page if you have any offers.

210 comments:

  1. Interesting and enlightening article :) I must confess that I am one of the people who overburdens the view controller like you pointed out. Most of the tutorials that I looked at when I began iOS development used that method and so I went with it without pausing to think about it. But I see the reasonableness of your arguments and am looking forward to the rest of the articles in the series so that I can see how you go about this from beginning to end.

    If it's a workable solution, I'd definitely want to switch to what you propose since that sounds more logical :)

    ReplyDelete
  2. Food for thought. I agree on some points (Core Data absolutely does not belong in a VC class. Storing globals in the App Delegate - punishable by death) but Apple is clearly pushing a design that you don't like. Whether or not you agree with it, it's the way it is. If you build your own road, nobody but you will be able to work on your iOS projects because it will be incomprehensible to anyone schooled in The Apple Way. To paraphrase Douglas Adams, if it's wrong, at least it's definitively wrong.

    For what it's worth, I find the concept of view controller code reuse to be completely overrated. How often have you been able to reuse code without any modifications? How often have you wanted to?

    ReplyDelete
  3. @jsd The kind of thing I've been doing doesn't actually depart from "The Apple Way" that much. There are still views and view controllers, we just move some logic into real controller objects (NSObject subclasses) which can be unit tested without pulling in entire UI frameworks and requiring an iOS simulator to run. As for view controller reuse, I think it depends on the kind of work one does. Doing Contract/Agency work, we have a huge number of projects and there are always things we can reuse between projects. This may not be relevant to others.

    ReplyDelete
  4. Thanks a lot for this blog post. Really made me think about myself and how I develop applications. I also found out the those pesky ViewControllers really slow you down because you aren't flexible at all regarding your navigation and ViewController hierarchy. Also unit testing is really hard to do up to impossible due to wrong/non existing encapsulation. It really becomes a huge moloch. I can't wait for the next article in the series :)

    ReplyDelete
  5. I agree with most of your points, but I don't see a sane way out. I wrote an iPhone application for some friends and am very happy with it. I have been going crazy trying to convert it to an universal application. The interface that seems most natural to the end-user has a search bar, dynamic table and webview on the same screen (not to mention a custom button bar at the top). How do you let each of these components update the others? The architecture that Apple presents, ends up with a mess of cross messaging. Everything got hopelessly coupled. I'm very much looking forward to see how you clean up the ViewControllers.

    ReplyDelete
    Replies
    1. Bill, you should be able to do it without cross messaging if you implement proper MVC separation. In this case, the model is application state rather than traditional user data. The way I do it is with an ApplicationState singleton (you can make this a normal object that is pushed into all the view controllers if you are opposed to singletons on philosophical grounds). Usually you just need a bunch of state properties. Using KVO your view controllers can respond to changes in the app state without having to wire them all up as delegates of each other. This decouples the view controllers from each other and makes it trivial to add new ones. The other bonus is you can make your ApplicationState class save itself on any state change, and restore when the app restarts. Makes it a lot easier to remember user preferences and is much cleaner than just jamming everything into NSUserDefaults (although you could do that behind the scenes).

      So let's say I have button that changes the sort order of a tableview. All I have to do is something like [[ApplicationState sharedState] setSortMode:AppSortModeByAuthor] from the button view, and the tableview's controller which is observing sortMode gets the message via KVO. It can then re-sort its rows and reloadData.

      Delete
    2. @jsd I actually considered something along those lines a couple of times. I'm going to first try NSNotificationCenter. If the GUI response times are poor, then I will implement a singleton with global state and probably KVO. My data model is fairly well developed and only needed a few changes for the iPad. But I spent a lot of time trying to follow Apple's SplitView controller approach to migrating iPhone apps to the iPad - doesn't work for me. The good news is that I have something that works; just ugly dependencies everywhere and too many differences between the "controllers" for iPhone and iPad. Thanks for the useful suggestions.

      Delete
    3. Interesting, my feeling is that it is largely due to the delegation structure - the view controllers handle features by implementing other view controllers thus introducing more unrelated methods to the controllers. But then the models should be outside which will at least allow seperation of the data. But yes another layer for business logic sounds sensible

      Delete
  6. More more more!

    It's so frustrating hearing from fellow iOS developers the phrase "I usually don't Unit Test my UIViewControllers, it's just too much effort" over and over again while them keeping 300+ lines of untested code in these classes. We need a way out of this mess!

    Motivated by Uncle Bob Martin and his great book "Clean Code", I've increasingly looked into clean ways to decouple business logic and UIViewController business.
    Some general rant from Bob Martin: http://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Architecture.html
    Ron Lisle attempts to do so with a thing he calls "VIP" architecture: http://iosunittesting.com/category/architectures/


    @Mike Weller: You're looking for a job in London, you wrote. Incidentally I've read about a job offer in London today - no idea if there still is an open position, as the blog post was from March.
    Source (at the bottom): http://www.merowing.info/2013/03/overlaying-application-version-on-top-of-your-icon/

    ReplyDelete
  7. iOS development is not a piece of cake! It requires a lot of creativity and new ideas. Many developers have tried their hands in development but there are only a few who have been successful in the field. ~keep posting~

    ReplyDelete
  8. Even Apple states that a view controller is not a pure controller as in MVC:

    "One can merge the MVC roles played by an object, making an object, for example, fulfill both the controller and view roles—in which case, it would be called a view controller. In the same way, you can also have model-controller objects. For some applications, combining roles like this is an acceptable design."

    http://developer.apple.com/library/ios/#documentation/general/conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html

    ReplyDelete
  9. Cocoa for OSX has bindings which allow to tie a model to a view and thus encourages better separation of the model from the controller.

    Searching for an alternative in the iOS world, I came across BlocksKit. One of its categories, NSObject(BlockObservation), could potentially result in a much thinner controller which observes its model and notifies its view.

    ReplyDelete
  10. MVVM with ReactiveCocoa also looks delicious for separating concerns:
    http://cocoasamurai.blogspot.co.at/2013/03/basic-mvvm-with-reactivecocoa.html

    ReplyDelete
  11. At last I'm not the only one thinking that iOS architecture is a big mess which tends to be really difficult to maintain because even Apple does not show best practises to developpers :)
    Thanks for this great article !

    ReplyDelete
  12. Waiting for new posts!

    ReplyDelete
  13. PLEASE hurry and write the next post.

    ReplyDelete
  14. Sorry guys. I start a new job back in London in a month, so things are a little hectic at the moment. I will try and find the time to finish the next post in the coming weeks.

    ReplyDelete
  15. This comment has been removed by the author.

    ReplyDelete
  16. You're killing us! I got all the way to the end to find out the next post isn't ready yet :(

    You did an outstanding job expressing the flaws in the UIViewController madness that I'd always felt but couldn't properly articulate.

    Great work.

    ReplyDelete
  17. Please tell me if you'll publish the next post about this problem?

    ReplyDelete
  18. Great article. I am somewhat new to iOS so I wasn't sure if my apprehension about the "view controllers do everything" pattern was just a matter of me not fully drinking the Kool-Aid. Good to hear I'm not the only one looking for a better way. Looking forward to the follow-up article.

    ReplyDelete
  19. Interesting stuff! Please release the next post soon!

    ReplyDelete
  20. Hey! Great article. I've been thinkin the same way for 2 years of my iOS developer practice. What about next part?

    ReplyDelete
  21. Here in my company, there're 2 (and more to come) exact cases,
    hijacked by a self-proclaimed iOS guru who happens to be a Senior Programmer,
    so you can imagine my disappointment when I reached the end
    and realized part 2 has yet to come.

    Really anticipating the follow up!

    ReplyDelete
  22. Very nice post. Looking forward for the follow-up article.

    ReplyDelete
  23. This post is a mind blow, please add some comment to briefly explain how to solve this. I think it would be very useful to many many developers out there.

    ReplyDelete
  24. Thanks a lot, changes my way to implement VC.

    ReplyDelete
  25. Where is the second part, we want it!

    ReplyDelete
  26. Coming from the java world, I always thought there was a bit of a problem with this. Never thought it was great in terms of separation of concern. I'm just facing problem regarding the others who have been taught the "messy" way. I'm trying to be an evangelist but it's not an easy one. Carry on the good work.

    ReplyDelete
  27. Loved your post. But I too am anxious to see this explained more fully. However, I would certainly settle for a sample project using tables, data, business logic- simple, but with enough code to see how it's done. Do you have a sample project? Or could you whip one up fairly quickly as a show and tell? Would be pretty cool to see this in action.

    ReplyDelete
  28. "So where should this navigation logic go? Well… somewhere else. We’ll get into that in a later post. All that matters for now is that the view controller should delegate UI actions like this to somebody at a higher level, who has access to and knowledge of the specifics of your app’s UI workflow."

    really interested to see how achieve this

    ReplyDelete
  29. When is the next post due to come out? Really eager to gain some insight into the "right way"!

    ReplyDelete
  30. After six month, where is the next post?

    ReplyDelete
  31. I also would like to see a modest app done the right way.
    I already knew shoving everything in a View Controller was wrong...

    ReplyDelete
    Replies
    1. I would like to see a modest app done this way, too. However, I would say that it would have to have at least 3 different screens, and perhaps some network access as well. A tableview would be nice. Maybe something like the BrowseOverflow app done in Test Driven iOS Development.

      Too many of these examples have only the one screen, and so they really fall flat on demonstrating that they're usable for more complex apps.

      Delete
  32. OK, sounds good. I totally agree. We're all awaiting the next post!

    ReplyDelete
  33. Interesting. I did a talk on the exact same subject here: https://speakerdeck.com/smorel/how-to-avoid-the-spaghetti-code-monster

    I manage an open source framework that helps dealing with this kind of problems. you can have a look at it here: https://github.com/wherecloud/AppCoreKit

    ReplyDelete
  34. Found a similar article in german about the whole stuff... brought me to this post

    www.ir-technology.net/blog/2014/01/uiviewcontroller-problem-ios-architektur

    ReplyDelete
  35. This comment has been removed by the author.

    ReplyDelete
  36. You can't just leave us hanging, makes me wonder if you have any valid answers to your valid questions, or are just blowing off steam! Come on, next post or fess up that you have no answers!

    ReplyDelete
  37. im grateful for your ideas too and have often thought and discussed the same things. The way forward is clear tho people, you needn't be bugging the author for more material, what do you want him to write your projects too? Its simple, stop thinking UIViewController is the 'C' in 'MVC' and start thinking of it as the top of the food chain in the 'V' hierarchy.... And go write some 'C' classes!! (which I would say need to be fired off from -(BOOL)applicationFinishLaunchWithOptions...... i expect)

    ReplyDelete
  38. we have some world class mobile experts. if you wanna develop your iOS Apps, please visit http://www.taoteapps.com, By visiting the website you can develop your iOS Apps.

    ReplyDelete
  39. I think you see all these problems because most people (including you) have fundamentally misunderstood what MVC means in the Apple world. I recommend reading "MVC as a Compound Design Pattern" in the Apple documentation to clear up how Cocoa MVC is different from classical MVC.

    The clearest indication IMO that you have misunderstood the purpose of controllers is that you state:

    "Controllers are supposed to be nice thin objects funnelling data and events between the model and the view. They shouldn’t contain details of a specific UI framework. iOS’ View controllers are nothing like this. They sit firmly in the presentation layer."

    But this is wrong. Cocoa Controllers ARE supposed to know the details of a specific UI framework. In fact that is the whole point of a view controller. The philosophy of Cocoa is that you are supposed to be able to buy an UIView and a Model object from two different vendors who have nothing to do with each other. In other words model and view objects are supposed to be completely reusable. But somehow you need to make both of these classes who know nothing about each other talk to each other. You need some glue. This glue needs to know about both the model and and the view. This glue is the controller. The UIViewController is not a reusable component. The fact that you write so much about the inability to reuse UIViewController if you do this or that, indicates that you have missed this important part of Cocoa design philosophy. Controllers is where you dump all your non reusable, application specific code.

    But this does not mean that you need to make every UIViewController a huge bloated mess. You should split it into multiple controllers. A controller can be made up of multiple controllers. Your business logic should be moved into model classes. If you got a table view create a specific controller just for implementing the DataSource protocol.

    I think the real problem here is that a lot of people have only ever done iOS development and UIViewController is pretty nifty. But if you come from OS X Cocoa development you would be used to making controllers from scratch. Back in the day a controller was usually just a subclass of NSObject. Don't forget this is still valid to do. Not every controller needs to subclass some specific class like UIViewController.

    I see your point with the horrible Apple example code though. That is a pitty. I never thought of example code as something to look at for the purpose of good application architecture. The Apple documentation does in fact give quite good explanations for how to structure your application. But nowadays it seems people learns almost exclusively by example. I am not sure what the solution to this is. If example applications showed good software design that would increase the size and complexity of the example and take focus away from the specific API being taught.

    Perhaps Apple should have specific example programs just to show good Cocoa Software design.

    ReplyDelete
    Replies
    1. Totally agree with you, controllers are the glue and should now about the model and the UI so that UI can be reusable and model is reusable.

      The business logic should not go into the controllers of course. Business logic only deals with the model layer and should be hosted in a service layer.

      To sum up, the model contains the state, the service manipulates the model, and the controllers watch the model to propagate changes to the UI and listens to the UI to modify model state or call service methods.

      Delete
    2. Can not agree more. Using tranditional MVC you can still create good architecture. The point is making viewcontroller small and simple. Experienced developers know how to separate functions into different models which could be viewcontrollers or regular controllers.

      Delete
  40. We have lived with VC the monster for years. Would love an alternative suggestion.

    ReplyDelete
  41. This comment has been removed by the author.

    ReplyDelete
  42. This comment has been removed by the author.

    ReplyDelete
  43. This is great stuff, Mike. Growing up as a developer through Smalltalk, all of this is part of my DNA, but I, too, have succumbed to Apple's pressures and muddied my code by bloating ViewControllers (I hate VCs... and don't even get me started on ModelControllers...). Reading this has reminded me of my roots and inspired me to refactor my current project, and for that I thank you. Keep posting in this series, please!!

    For those advocating the virtues of VCs, while I agree that we need to constantly be evolving as developers and scientists, this experiment has proven more disastrous than advantageous, as evidenced by the proliferation of outright bad codebases written by so-called experience OO developers, and the number of apps that crash and fail for, apparently, no reason whatever.

    MVC as a design archetype was created when things were much simpler (it was forged in the fire that gave us Smalltalk), admittedly, however, it has persisted for its simplicity, and most of the so-called enhancements or evolutions of it (MVVC, VCs, MCs, etc.) are nothing more (IMHO) than the hackings of folks who are not getting the essence and beauty of true MVC.

    As a technology consultant who has interviewed over 800 technology applicants I can attest to the sparse few who could articulate the actual responsibilities of the constituents of the MVC pattern, and many of these folks had Master's degrees from prestigious universities. The teachings now are based on faulty thinking and shallow understanding of OO fundamentals, as they have become dilute over the generations of developers such that the collective thinking is flawed at its heart, yet is propagated as correct and accepted.



    So, thank you Mike, for casting a harsh light on a dark shadow.

    That's just my opinion... I could be wrong. ;)

    ReplyDelete
    Replies
    1. ** steps off soapbox ** and runs from tomato-hurlers...

      Delete
  44. Where is the continuation post, did you get a chance to write it?

    ReplyDelete
  45. Great article, much appreciated as I thought I was just being mad. One concise question for people out there - IBOutlets and IBActions: I can wire them in to my view or my controller (or both). Logic says they should be in the view but then one must implement a protocol/delegate to interact with the controller/model. This seems like a lot of effort for nothing much. Alternative is wiring all the IBOutlets/IBActions into the viewcontroller which seems a problem if you need different views for iPad/iPhone ... thoughts/opinions?

    ReplyDelete
  46. Hey, this is a good article you wrote there. Since I am one of these devs (1 year exp.) I am looking forward to reading your articles about solving these problems. But when will you write them?

    ReplyDelete
  47. Mike! I really wanted to read the rest of the series you decided to begin a year ago (but didn't). I can glean your intent from reading the post, but it would have been cool to see where it actually leads.

    I'm guessing you'd replace all of the business logic common to View Controllers with bite size objects that can be unit tested? I have done this before when implementing UITableView Datasource protocols that started to get out of hand when everything was tossed into a single View Controller. Totally makes sense.

    Anyway, please post more articles. I want to see your unique solution to the problem.

    ReplyDelete
  48. Thanks for this great article. I am very disappointed that you didn't make the next one with your solutions. I'd be glad to read it.

    ReplyDelete
  49. Please finish the series! I was nodding the whole way through your post. I'd love to see the next one. It's never to late to resurrect the series!

    ReplyDelete
  50. The first issue of objc guides you on how to make your view controllers cleaner and lighter: http://www.objc.io/issue-1/

    ReplyDelete
  51. 2015 is here, and we're still waiting. Please teach us the right way!

    ReplyDelete
  52. Hey! Couldn't you at least post a few more pics of kittens?

    ReplyDelete
  53. Mike, all we are looking forward for you to continue!
    Are you alive?

    ReplyDelete
  54. What's the alternative? It's easy to criticism.

    ReplyDelete
  55. Here is the good solution

    http://www.teehanlax.com/blog/model-view-viewmodel-for-ios/

    ReplyDelete
  56. Nice post! Please keep working on new articles!

    ReplyDelete
  57. This is awesome article. It made me actualize many past errors and i do still have some I'm working on to reduce. Definitely a wonderful article on why View Controller go beyond the point of frustration and you don't want to touch it again.

    ReplyDelete
  58. Excellent post by Mike on the problem with massive view controllers.

    I've studied different iOS architectures such as MVC, MVVM, ReactiveCocoa, for the last two years. I found Uncle Bob's Clean Architecture the best way to decouple classes and make your dependency graph very simple. I've organized our code into 3 main components - view controller, interactor, and presenter, and abstract away model data by creating request, response, and view model objects to pass through their boundaries.

    The result is amazing. With multiple developers on the project, we are able to know exactly which file and method to look when finding things. The other day, I had to fix a bug on a profile screen that was developed by another developer. I didn't write one line of this code but I know exactly where to find it. The methods are also very short as a result of apply Clean Architecture. I also added a router component to it and now we can use multiple storyboards with ease. This also allows us to write tests much easier as we only need to test methods at the boundaries.

    I wrote about my experiences with Clean Architecture at http://clean-swift.com/clean-swift-ios-architecture/

    ReplyDelete
  59. This comment has been removed by a blog administrator.

    ReplyDelete
  60. This is an excellent post. I am still holding out hope for a continuation. Thanks, hope you are doing well.

    ReplyDelete
  61. I would love a follow-up post! This was a great read.

    ReplyDelete
  62. This comment has been removed by a blog administrator.

    ReplyDelete
  63. This comment has been removed by a blog administrator.

    ReplyDelete
  64. This comment has been removed by a blog administrator.

    ReplyDelete
  65. Apple has made certain decisions with their framework design and tools which I believe contributes to this problem.click here

    ReplyDelete
  66. Though cool and hip, these cell phones are extremely sensitive. They have internal elements very delicate that a single drop might render the phone dead. So with this, having a cellular phone is synonymous to having cellphone cases.
    How to fix a dead phone

    ReplyDelete
  67. If you have your mind set on an iPhone app, then I urge you to also consider what happens when it is a success? Yes, I am assuming that your iPhone application will be a success because they are growing so rapidly.
    Mac Repair Bangor Maine

    ReplyDelete
  68. he clients can exploit the component to flip between light foundation and dull foundation consistently. my review here

    ReplyDelete
  69. Good article knowledge gaining article. This post is really the best on this valuable topic. Mobile App Developers

    ReplyDelete
  70. Excellent website you have here: so much cool information!.. 24 hour truck tire repair

    ReplyDelete
  71. Excellent website you have here: so much cool information!.. cell phone deals

    ReplyDelete
  72. I think that thanks for the valuabe information and insights you have so provided here. TweakBox

    ReplyDelete
  73. I truly adored perusing your online journal. It was exceptionally all around wrote and simple to undertand. Not at all like extra online journals I have perused which are truly not tht great. I likewise discovered your posts extremely fascinating. Truth be told subsequent to understanding, I needed to go demonstrat to it to my companion and he ejoyed it also! AirShou Screen Recorder

    ReplyDelete
  74. i am surprisingly here. I discovered this board and I in discovering It really accommodating and it helped me out a great deal. I would like to present something back and help other people, for example, you helped me. FtiOS iOS 11

    ReplyDelete
  75. This was a really great contest and hopefully I can attend the next one. It was alot of fun and I really enjoyed myself..
    IT managed Services Chicago

    ReplyDelete
  76. Great site, Distinguished criticism that I can handle. Im pushing ahead and may apply to my present place of employment as a pet sitter, which is exceptionally charming, yet I have to extra grow. Respects. Tinyumbrella Download

    ReplyDelete
  77. Fabulous web journal! Do you have any tips and indications for trying authors? I'm wanting to begin my own particular site soon however I'm somewhat lost on everything. Would you propose beginning with a free stage like WordPress or go for a paid alternative? There are such a large number of alternatives out there that I'm totally overpowered .. Any proposals? Much obliged! Tutu Helper

    ReplyDelete
  78. Great post yet I was thinking about whether you could compose a litte more on this subject? I'd be extremely grateful on the off chance that you could expound a smidgen further. Welcome it! Panda Helper

    ReplyDelete
  79. Thanks for the precious information. I m really grateful to you.
    imessage on pc

    ReplyDelete
  80. Really thanks to you because valuable information. Track Easily throught blue dart courier tracking

    ReplyDelete
  81. Numerous mixture versatile apps today use Apache Cordova, a basic stage that comprises of an arrangement of JavaScript APIs for getting to cell phone capacities through modules that are worked with local code. clash royals

    ReplyDelete
  82. It's interesting that many of the bloggers to helped clarify a few things for me as well as giving. Most of ideas can be nice content. The people to give them a good shake to get your point and across the command.
    offshore safety course in chennai

    ReplyDelete
  83. Looking for File Sharing Application? Here is the Best File Sharing Application of 2019 to Transfer Files from PC to Android/iPhone Mobile. Written by Meet Patel

    ReplyDelete
  84. Awesome post, I found this article very interesting check out my blog android app developers.

    ReplyDelete
  85. who don't want to download a paid app for free? Well, in this post we are going to introduce such app that will allow you to download any app at free of cost. We are talking about Panda Helper APK for Android.
    Panda Helper VIP

    ReplyDelete
  86. Emoji icons are cute little images that represents something in our daily lives. You could, for example substitute "I love you" with a simple emoji icon that represents a heart. It is commonly used and supported by most cell phones in Japan.
    emoji

    ReplyDelete
  87. When I initially left a comment I appear to have clicked the -Notify me when new comments are added- checkbox and now each time a comment is added I receive four emails with the exact same comment. There has to be an easy method you can remove me from that service? Kudos! get it now

    ReplyDelete
  88. Thank you for sharing some cool information regarding wordpress website design. Here I found one company in Brisbane area and it is very popular in Australia. They provide many service like seo birsbane, website design, digital marketing. Thank you!

    ReplyDelete
  89. If you are intrested in the thai lottery result tips and updates and thai lottery result get it from the Link

    ReplyDelete
  90. If you're an employer and you want to keep an eye on your new employees and their activities on the Internet, this page will be perfect for you - spy-apps-software.com Top lists of the best spy apps!

    ReplyDelete
  91. TutuApp is a trending app nowadays because it contains many new and good features of games, music, etc. Tutuapp APK
    TutuApp APK
    Tutu App
    Tutu App APK
    TutuApp Free
    TutuApp ios

    ReplyDelete

  92. Tutu Helper is the one of the best ios,android App store to get the tons of free app and game. Here the latest version of TutuApp of free.
    Tutu Helper
    Tutu App

    ReplyDelete
  93. TutuApp is a trending app nowadays because it contains many new and good features of games, music, etc. TutuApp APK
    Tutu App

    ReplyDelete





  94. Showbox Apk is a meta-search engine scraper that scrapes data from the world wide web and provides you with the best for your movies and TV shows.
    It works directly as well as p2p i.e peer to peer.
    if you are looking for free movies and tv shows you can find them on showbox app its easy to download and install
    you can stream movies and tv shows on tvtap for free its great app for all types of live stream
    if you wanna watch free movies and tv shows you can tune in to beetv app for free

    whatsapp status

    RedBox TV App has introduced a lot more features than any other TV Channels Streaming App. It does have categories, depending on Counties as well.
    To find out your native language you can swipe to Country based categories, where you’ll find the channels which are highly native to your region and language. That’s an extra of RedBox TV App.
    Apart from this, RedBox is available for almost every possible platform. Unlike the other apps, Redbox tv don’t claims any fake hoax, all the stuff where are features in this post will be served.















    ReplyDelete
  95. I feel very grateful that I read this. It is very helpful and very informative and I really learned a lot from it.
    business analytics course
    data science interview questions

    ReplyDelete
  96. I finally found great post here.I will get back here. I just added your blog to my bookmark sites. thanks.Quality posts is the crucial to invite the visitors to visit the web page, that's what this web page is providing.
    data science course Mumbai
    data science interview questions
    data analytics course in mumbai

    ReplyDelete
  97. Allinfodesk: Jobs in Pakistran 2020 Latest Vacancies
    https://allinfodesk.com/category/jobs-in-pakistan/
    Jobs in Pakistan
    https://allinfodesk.com/category/jobs-in-pakistan/

    ReplyDelete
  98. Impressive! I finally found great post here. Nice article on data science . It's really a nice experience to read your post. Thanks for sharing your innovative ideas to our vision.
    Data Science Course Training in Bangalore

    ReplyDelete

  99. Thank you for taking the time to provide us with your valuable information. We strive to provide our candidates with excellent care
    http://chennaitraining.in/creo-training-in-chennai/
    http://chennaitraining.in/building-estimation-and-costing-training-in-chennai/
    http://chennaitraining.in/machine-learning-training-in-chennai/
    http://chennaitraining.in/data-science-training-in-chennai/
    http://chennaitraining.in/rpa-training-in-chennai/
    http://chennaitraining.in/blueprism-training-in-chennai/

    ReplyDelete
  100. Great blog !It is best institute.Top Training institute In chennai
    http://chennaitraining.in/openspan-training-in-chennai/
    http://chennaitraining.in/uipath-training-in-chennai/
    http://chennaitraining.in/automation-anywhere-training-in-chennai/
    http://chennaitraining.in/microsoft-azure-training-in-chennai/
    http://chennaitraining.in/workday-training-in-chennai/
    http://chennaitraining.in/vmware-training-in-chennai/

    ReplyDelete
  101. You made some decent points there. I looked on the internet to learn more about the issue and found most individuals will go along with your reviews views on this website.

    ReplyDelete
  102. Excellent web site you have got here.. It’s hard to find excellent writing like yours nowadays.lap I truly appreciate people like you! Take care!!

    ReplyDelete
  103. Thank you so much for sharing this nice informations.
    android training institutes in coimbatore

    data science course in coimbatore

    data science training in coimbatore

    python course in coimbatore

    python training institute in coimbatore

    Software Testing Course in Coimbatore

    CCNA Course in Coimbatore

    ReplyDelete
  104. The thought on iOS app architecture and Test Driven Development has been a great concept to discuss. The ideas has been articulated with professional skills and efforts. I wish you further success in the next jot down.
    Web Designing Course Training in Chennai | Web Designing Course Training in annanagar | Web Designing Course Training in omr | Web Designing Course Training in porur | Web Designing Course Training in tambaram | Web Designing Course Training in velachery

    ReplyDelete
  105. I have express a few of the articles on your website now, and I really like your style of blogging. I added it to my favorite’s blog site list and will be checking back soon…keep it up
    Ai & Artificial Intelligence Course in Chennai
    PHP Training in Chennai
    Ethical Hacking Course in Chennai Blue Prism Training in Chennai
    UiPath Training in Chennai

    ReplyDelete
  106. After reading your article I was amazed. I know that you explain it very well. And I hope that other readers will also experience how I feel after reading your article.
    Data Analyst Course

    ReplyDelete
  107. Great Article… I love to read your articles because your writing style is too good, its is very very helpful for all of us and I never get bored while reading your article because, they are becomes a more and more interesting from the starting lines until the end.



    Robotic Process Automation (RPA) Training in Chennai | Robotic Process Automation (RPA) Training in anna nagar | Robotic Process Automation (RPA) Training in omr | Robotic Process Automation (RPA) Training in porur | Robotic Process Automation (RPA) Training in tambaram | Robotic Process Automation (RPA) Training in velachery



    ReplyDelete
  108. Neither a transistor nor an artificial neuron could manage itself; but an actual neuron can. artificial intelligence training in hyderabad

    ReplyDelete
  109. wonderful one. I would like to thank you for the efforts you had made for writing this awesome article. This article inspired me to read more. keep it up.
    Correlation vs Covariance
    Simple linear regression
    data science interview questions

    ReplyDelete
  110. Download and install panda helper on Android and iOS devices

    ReplyDelete
  111. Attend online training from one of the best training institute Data Science Course in Hyderabad

    ReplyDelete

  112. very nice blogs!!! i have to learning for lot of information for this sites...Sharing for wonderful information.Thanks for sharing this valuable information to our vision. You have posted a trust worthy blog keep sharing.
    Azure Training in Chennai

    Azure Training in Bangalore

    Azure Training in Hyderabad

    Azure Training in Pune

    Azure Training | microsoft azure certification | Azure Online Training Course

    Azure Online Training

    ReplyDelete
  113. Great site and a great topic as well I really get amazed to read this.This is incredible,I feel really happy to have seen your webpage.I gained many unknown information, the way you have clearly explained is really fantastic.keep posting like this information.
    Full Stack Training in Chennai

    Full Stack Course Chennai
    Full Stack Training in Bangalore

    Full Stack Course in Bangalore

    Full Stack Training in Hyderabad

    Full Stack Course in Hyderabad

    Full Stack Training

    Full Stack Course

    Full Stack Online Training

    Full Stack Online Course



    ReplyDelete
  114. I think this is an informative post and it is very useful and knowledgeable.
    Read more

    ReplyDelete
  115. Amazing Article ! I would like to thank you for the efforts you had made for writing this awesome article. This article inspired me to read more. keep it up. data science courses

    ReplyDelete
  116. When you think you found the course that fits for you, read some reviews to make sure you are doing the right choice, you can call their costumer service to let them explain how the course is structured. digital marketing course in hyderabad

    ReplyDelete
  117. Just saying thanks will not just be sufficient, for the fantasti c lucidity in your writing. I will instantly grab your rss feed to stay informed of any updates.
    data science training in Hyderabad

    ReplyDelete
  118. A pre-wedding shoot is a perfect way to capture your love for each other without any relatives or friends around. It makes for great pictures which you can later frame and keep by your bedside or on your work desk to remember the magical time leading up to your wedding. Best pre-wedding shoot in Delhi

    ReplyDelete
  119. This is an awesome motivating article. I am practically satisfied with your great work. You put truly extremely supportive data. Keep it up. Continue blogging. Hoping to be perusing your next post.
    Salesforce Training in Chennai

    Salesforce Online Training in Chennai

    Salesforce Training in Bangalore

    Salesforce Training in Hyderabad

    Salesforce training in ameerpet

    Salesforce Training in Pune

    Salesforce Online Training

    Salesforce Training

    ReplyDelete
  120. It is a good idea to read reviews on the internet before you make any purchase online. The reviews help you understand whether the item you are going to purchase will serve the purpose it intends to or not. Sometimes people end up with buying a wrong item and its a total waste of money and time. Top Review Advisor

    ReplyDelete
  121. The content is well acknowledged, so no one could allege that it is just one person's opinion yet it covers and justifies all the applicable points. I have read such a startling work after a long time!
    SAP training in Kolkata
    SAP course in Kolkata
    SAP training institute in Kolkata


    ReplyDelete
  122. This is a wonderful article, Given so much info in it, Thanks for sharing. CodeGnan offers courses in new technologies and makes sure students understand the flow of work from each and every perspective in a Real-Time environment react js Course in vijayawada. ,

    ReplyDelete
  123. software testing company in India
    software testing company in Hyderabad
    I would like to thanks for the efforts you made for writing this awesome article.
    Great information.
    keep sharing.

    ReplyDelete
  124. Hey there, I’m Judy Trimble. I’m a Blogger living in the USA. I am a fan of technology, web development, and programming.droidquack.com

    ReplyDelete
  125. Thank you because you have been willing to share information with us. we will always appreciate all you have done here because I know you are very concerned with our.
    Review Amazon Product

    ReplyDelete
  126. ExcelR provides data analytics course. It is a great platform for those who want to learn and become a data analytics Courses. Students are tutored by professionals who have a degree in a particular topic. It is a great opportunity to learn and grow.

    data analytics course
    data analytics courses

    ReplyDelete
  127. With special privileges and services, UEFA BET offers opportunities for small capitalists. Together ufa with the best websites that collect the most games With a minimum deposit starting from just 100 baht, you are ready to enjoy the fun with a complete range of betting that is available within the website

    ufabet , our one another option We are a direct website, not through an agent, where customers can have great confidence without deception The best of online betting sites is that our Ufa will give you the best price

    หาคุณกำลังหาเกมส์ออนไลน์ที่สามารถสร้างรายได้ให้กับคุณ เรามีเกมส์แนะนำ เกมยิงปลา รูปแบบใหม่เล่นง่ายบนมือถือ คาสิโนออนไลน์ บนคอม เล่นได้ทุกอุปกรณ์รองรับทุกเครื่องมือ มีให้เลือกเล่นหลายเกมส์ เล่นได้ทั่วโลกเพราะนี้คือเกมส์ออนไลน์แบบใหม่ เกมยิงปลา

    อีกทั้งเรายังให้บริการ เกมสล็อต ยิงปลา แทงบอลออนไลน์ รองรับทุกการใช้งานในอุปกรณ์ต่าง ๆ HTML5 คอมพิวเตอร์ แท็บเล็ต สมาทโฟน คาสิโนออนไลน์ และมือถือทุกรุ่น เล่นได้ตลอด 24ชม. ไม่ต้อง Downloads เกมส์ให้ยุ่งยาก ด้วยระบบที่เสถียรที่สุดในประเทศไทย

    ReplyDelete
  128. one fast brownIn case you are looking for a good site, UFA, UEFA Bet casino site. Which can be played as a thorough answer, in a position to answer Quality and Performance It's ideal to get a great deal of the issues. It can be something very punching and fascinating. Excellently, the items that UFABET football betting is absolutely nothing even with the practical experience of quality. Plus accessibility that are able to see final results It's a model that is very sharp and also different. Full of performance of creating wealth Attractiveness With the most beneficial opportunities it is quite nothing You will find opportunities and also probabilities for making profits. Quality and also somewhat diverse For people who have come to make use of the service excellently fox
    บาคาร่า
    สล็อต
    ufa
    แทงบอล

    ReplyDelete
  129. Thanks for sharing such a good and informative content to all of us.
    pega testing online course
    pega testing

    ReplyDelete
  130. These two ingredients are the key factors to make any Salesforce implementation successful and if they will not be present during your CRM implementation, then it can become a very disappointed IT transformation for the business. salesforce training in bangalore

    ReplyDelete
  131. Lembaga pelatihan Dirgantara Aviasi Nusantara adalah sebuah pelatihan yang didirikan di kota Purworejo yang mana lokasi relatif dekat dengan Bandara baru Yogyakarta International Airport (YIA) kulonprogo, didirikan oleh organisasi yang sudah berpengalaman di dunia aviasi untuk membantu para calon sumber daya manusia yang mencari keterampilan khususnya bidang aviasi atau penerbangan.Sekolah Penerbangan Terbaik

    ReplyDelete
  132. The software of running capital is important device used to decide the profitability of a agency.Alternative Funding Group

    ReplyDelete
  133. It's Very informative and helpful. If you are interested in learning JMeter please refer to the link below. Thank you

    JMeter Training in Chennai | JMeter Course in Chennai | JMeter Online Course

    ReplyDelete
  134. Took me time to read all the comments, but I really enjoyed the article. It proved to be Very helpful to me and I am sure to all the commenters here! It’s always nice when you can not only be informed, but also entertained
    vé máy bay từ seattle về việt nam

    vé máy bay từ đức về sài gòn

    lịch bay từ anh về việt nam hôm nay

    khi nào có chuyến bay từ úc về việt nam

    mua ve may bay gia re tu Dai Loan ve Viet Nam

    vé máy bay từ canada về việt nam

    ReplyDelete
  135. I wish more writers of this sort of substance would take the time you did to explore and compose so well. I am exceptionally awed with your vision and knowledge.
    data scientist training in hyderabad

    ReplyDelete
  136. Usually I never comment on blogs but your article is so convincing that I never stop myself to say something about it. Just have a look at this Air Hostess Course

    ReplyDelete
  137. Great Post! Thank you kindly for sharing this post, it was so great to peruse and valuable to work on my insight as refreshed one, continue to blog…

    AI Training in Hyderabad

    ReplyDelete
  138. Best Casinos For Real Money 2021 - DrMCD
    Real Money 경산 출장마사지 Casino Apps · 10 – Ignition Casino 전라남도 출장마사지 · 9 – BetMGM Casino · 목포 출장안마 8 – BetUS 광주광역 출장안마 Casino · 7 – 김제 출장안마 Caesars Casino · 6 – Caesars Casino.

    ReplyDelete
  139. I am stunned by the information that you have on this blog. It shows how well you fathom this subject. artificial intelligence course in pune

    ReplyDelete
  140. thanks For sharing this top notch article.i exploit this newsletter to function my venture in college.it's far beneficial For me amazing paintings. Sony Vegas Free Download With Crack

    ReplyDelete
  141. Malwarebytes Anti Malware Premium Crack that is my first duration i go to right here and i discovered consequently many enthralling stuff for your blog specifically it is aeration, thank you.

    ReplyDelete
  142. Happy brothers day! To have a brother like you is a blessing! that is truely intellectual content material and written thinking about ease for a regulate. it's best to peer that some human beings but put taking region as soon as a way to write a man or woman nation.! https://wishesquotz.com/brothers-day-quotes/

    ReplyDelete
  143. Thanks for writing such a splendid post. I keep visiting the webpage that helped me and many others. I don’t usually comment but this had me enough of that myself and this article has given me the to do. 
    SEO Company in London

    ReplyDelete
  144. I am highly overwhelmed to read this perfect piece of writing. It has really enthused me to read more on this topic.
    CCTV installation services in Hooghly
    CCTV camera installation in Kolkata

    ReplyDelete
  145. But now, many Korean on-line casinos are working on-line and getting positive evaluations, and gamers can really leap and have the joys they're looking for. To be trustworthy, heaps of|there are numerous} websites out there on the market that can provide 빅카지노 related providers. Most of them yow will discover by searching on google shall be worldwide or US/UK primarily based casino sites that simply collects casino data from different countries and flow into them. But communities like systemsacadem.io, being region-specific, provide solely the data you care about.

    ReplyDelete
  146. Minimum and recommended system requirements for a comfortable game with high FPS in Tekken 7 on PC. Check if the computer meets the system requirements and if an upgrade of the processor, video card or increase in RAM is required. Find out if Tekken 7 will run on a weak PC or laptop.

    ReplyDelete
  147. On the one hand, Logic Pro X has everything you need for world-class stars, including MainStage 3 - tools for conducting live performances. On the other hand, it has a moderately low entry threshold. In particular, it is not necessary to have a serious musical education and a perfect understanding of arpeggios and chords to work on tracks.

    ReplyDelete
  148. Procreate is something unique with regards to visual workmanship on compact gadgets. It is only Procreate is a finished visual expressions arrangement that permits you to make rich compositions, expressive representations, wonderful movements , and flawless outlines. Rather than getting together your whole workmanship studio, you can just introduce the Multiply application on your viable iPad gadget.

    ReplyDelete
  149. This blog article brings out a significant issue with the architecture of iOS apps and how view controllers may become cumbersome owing to shared responsibilities. It's interesting that the author wants to investigate a TDD-driven Clean Architecture approach. I'm looking forward to your future blogs for additional insights!

    Data Analytics Courses in India

    ReplyDelete
  150. This in-depth exploration of iOS app architecture and Test Driven Development (TDD) is refreshing and enlightening. The critique of the current approach to iOS development, especially the heavy reliance on view controllers, is thought-provoking. Looking forward to the Clean iOS Architecture journey!
    Data Analytics Courses in Nashik

    ReplyDelete
  151. good blog
    Data Analytics Courses In Vadodara

    ReplyDelete
  152. This blog offers a unique perspective on iOS development and urges developers to question conventional practices.
    Digital marketing courses in illinois

    ReplyDelete
  153. Thanks for sharing valuable and comprehensive guide on iOS Development.
    Digital Marketing Courses in Italy

    ReplyDelete
  154. This blog entry about finding joy is excellent and motivating. A good blog is one that always comes with new and exciting information.
    Data analytics framework

    ReplyDelete
  155. This blog profile brings out a significant issue with the architecture. https://www.blogger.com/profile/14833041112662158741

    ReplyDelete
  156. We are doing well or not? expert urdu

    ReplyDelete
  157. The Railway Recruitment Board (RRB) has published a press release with all details about the recruitment processRPF Recruitment 2024 notification 2024

    ReplyDelete
  158. iOS app architecture with Test-Driven Development (TDD) emphasizes writing tests before code, ensuring modular, maintainable components. Architectures like MVC, MVVM, or VIPER enhance testability and promote clean, scalable app development.
    Data science courses in Gurgaon

    ReplyDelete