I am trying to pass information from controller A to controller B. The thing is, I want:
Concise: To minimize the autocomplete from XCode to some key information. I want to know in a easy way the exact parameters required before pushing the controller on the stack.
Avoid segues. From my understanding, segues create a lot of tight coupling in the storyboard. I don't want to be dependent of the storyboard to pass information. (Each time I want to switch controller A to another, I have to go to the storyboard to make some changes). I might split my app in several storyboards at some point, and segues can be quite annoying to deal with.
Beautiful: Maybe Swift can provide a Swifty solution I have not thought about.
What I have been trying to accomplish at first was to push controller as protocol. Even if it not possible, let me explain:
Pushing a controller as a protocol would allow me to have exact visibility on my attributes.
There is no tight coupling related to the storyboard
Many controllers (A, C, D) might want to push a B controller, I might give them each one of them a different protocol to push controller B. Maybe B could appear under different circumstances.
At first, my code was looking like that (the storyboard extension works):
if let myBCustomVC = storyboard.instanciateControllerWithIdentifier(MyBCustomVC.self) as? myVCustomFromAProtocol{
myBCustomVC.AToB = self.data.count
self.navigationController?.pushViewController(myBCustomVC, animated: true)
}
protocol myVCustomFromAProtocol {
var AToB: Int {get set}
}
The thing is, I cannot push a view controller downcast to a protocol. I had to come by an ugly UINavigationController extension. Here is the full result.
if let myBCustomVC = storyboard.instanciateControllerWithIdentifier(MyBCustomVC.self) as? myVCustomFromAProtocol{
myBCustomVC.AToB = self.data.count
self.navigationController?.pushViewController(vcToPush: myBCustomVC, animated: true)
}
extension UINavigationController{
func pushViewController(vcToPush: Any, animated: Bool){
if let vc = vcToPush as? UIViewController{
self.pushViewController(vc, animated: animated)
}
}
}
Let's face it, while achieving my first two goals, I have downcasted an Any to a UIViewController, woosh.
Is there any way to avoid tight coupling and push controllers on the stack in a beautiful way while keeping a limited visibility on the parameters to pass from the first view controller(A) to the second(B). What are your thoughts? Why wouldn't I want to do that?
I'd approach this from the other side (i.e. the side of the controller you are presenting).
You could do something like creating a Presenter protocol.
protocol Presenter {
func present(inContext context: UIViewController)
// possibly with some data in it too (if it's across all instances)...
var count: Int {get set}
}
And have some sort of factory struct on top of each...
struct BVCPresenter: Presenter {
var count: Int = 0
func present(inContext context: UIViewController) {
// create the BViewController here
let controller = ...
context.push(controller, animated: true)
}
}
Or something along those lines. It really depends on the use cases and the data being passed around etc...
So now A can do...
someGenericPresenter.present(inContext: navigationController)
The generic presenter can be passed in as a property to A or can be passed into a function as a parameter etc...
or something.
You could even create a "FlowController" that manages these transitions and data for you...
self.flowController.presentB()
The flowController then knows what B needs and how and where to present it and populate it with data. The individual view controllers don't actually need to know anything about each other.
I don't think there is a solution for this that would suit all cases. It requires a lot of thought and design. But there are many options. Think about what you do need between the view controllers and work from that.
Also, don't be too worried about creating something that "just works" even if it isn't textbook generic, "Swifty", elegant, beautiful code. Having code that works is much better than having a beautifully designed system that doesn't.
:)
With the help of Fogmeister I came up with this answer. It has several advantages and disadvantages over the use of a Presenter Object that inherits from a protocol.
Solution: Use a static factory method (or several depending of the use case) located in the controller to be instantiated.
Advantages:
Using a static factory method enforces to pass parameters that the actual controller know to be required for his own initialization.
No structure involved (less complexity?)
The constant use of a static factory method becomes natural over time. You know when looking at a controller you must implement the method to call it, and you know the parameters required. You do not have to worry which parameters must be implemented to push the controller, just implement the function. Using a presenter, you do not have this knowledge as the unwrapped parameters can be numerous.
The use of a Presenter is closer to MVP and gets away from the dependency injection model advocated by Apple (MVC)
Disadvantages:
The use of the Presenter pattern suggested by Fogmeister uses a protocol. The protocol gives limited visibility to controller A (and all controllers that want to push a controller B).
The use of the Presenter pattern gives more modularity and reduces tight coupling. Controller A has no need to know anything about controller B. The replacement of controller B to another controller C can be done without affecting all the controllers that were going to B (can be quite useful if you are restructuring your application or in some use cases).
Here is my current implementation in my class:
In controller B (static function):
static func prepareController(originController: UIViewController, AToB: Int) -> bVC? {
if let bVC = originController.storyboard?.instanciateControllerWithIdentifier(self) as? bVC{
bVC.AToB = AToB
return bVC
}
else{
return nil
}
}
In controller A (to push the controller on the stack):
if let bVC = bVC(originController: self, AToB: *someValue*){
self.navigationController?.pushViewController(bVC, animated: true)
}
Solution has to be taken with a grain of salt, let me know what you think #Fogmeister?
Both solutions can be combined. Using a protocoled structure that would use the static method provided by the controller. The thing is it can quickly become overly complex for people you introduce to the project. That's why I am sticking to my solution at the moment. I am looking at frameworks for dependency injection though.
Related
I am trying to understand how dependency injection works in Swift so I can inject into my ViewController. I have some experience with it in Kotlin but am unsure of how to approach it in Swift. I would prefer to have minimal reliance on external libraries but am not opposed to using one or two if they are reliable and well-maintained. Here is some code I found on a blog post about DI in Swift.
import UIKit
class ViewController: UIViewController {
lazy var requestManager: RequestManager? = RequestManager()
}
// Initialize View Controller
let viewController = ViewController()
// Configure View Controller
viewController.requestManager = RequestManager()
I understand that instead of instantiating RequestManager in the ViewController, I can create an instance of the ViewController itself and instantiate it using property access but I don't understand where I should declare the instance of the ViewController. I feel like I am missing some code or some context. Can someone help me to understand this?
To quickly answer the exact question asked, of how to pass in variables to VC's inside a storyboard. You can simply do something like this in the previous viewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let vc = segue.destination as? SomeViewController else {
return
}
vc.requestManger = SomeRequestManger()
}
To give a longer, more opinionated answer:
I personally only use DI in very small and very limited ways. There are a few exceptions where more advanced use cases crop up. But in general I feel like a lot of people way over think this and just do it because they "should" as oppose to actually needing too. A lot of cases I see online of creating mock instances of every service class, and passing them around where needed, is simply a bad idea as you wind up testing very little of your actual code, and create a tonne of extra effort for yourself for no reason.
For an app i'm working on, all I did was create a mock instance of URLProtocol and hold onto a reference of a separate URLSession instance in a class in my test bundle, something like this:
public let mockURLSession: URLSession
private init() {
let sessionConfig = URLSessionConfiguration.ephemeral // Uses no caching / storage
sessionConfig.protocolClasses = [MockURLProtocol.self]
mockURLSession = URLSession(configuration: sessionConfig)
...
}
and then I had a DependencyManger.swift that was a singleton. Any Service class that needed a URLSession, was created and held inside that class, and I had a function to pass the mock session or the real session into each of those classes, from the manager.
From then on I didn't have to worry about adding 20K lines of code of third party libraries, just so I could have fancy Init's on my VC's. Or worry about writing hundreds of lines of code passing references between one viewController to another. Inside each VC I would just do:
DependencyManger.shared.networkService.get(...) {
...
}
All I need to do then is flip the toggle while running my tests, and all my networking code will use my MockURLProtocol, where I can override the methods and have it return stubbed data per URL. In this situation I only needed to mock my networking data, if you need to also mock push notifications for example, you'd need to go one step further.
Incredibly simple, works with interface builder or UI written in code, extremely flexible, a tiny amount of code, no third party dependencies to maintain
How we can apply Dependency injection without using a Framework when we have two UIViewControllers that are very deep in the hierarchy and they both need the same dependency that holds state and those two UIViewControllers they don't have a common parent.
Example:
VC1 -> VC2 -> VC3 -> VC4
VC5 -> VC6 -> VC7 -> VC8
let's sat that VC4 and VC8 they both need UserService that holds the current user.
Note that we want to avoid Singleton.
Is there an elegant way to handle this kind of DI situations ?
After some research I found that some mention Abstract Factory, Context interfaces, Builder, strategy pattern
But I could not find an example on how to apply that on iOS
Okay, I'll give this a try.
You said "no singleton", so I exclude that in the following, but please also see the bottom of this answer.
Josh Homann's comment is a good pointer for one solution already, but personally I have my problems with the coordinator pattern.
As Josh correctly said view controllers shouldn't know (much) about each other [1], but then how is e.g. a coordinator or any dependency passed around/accessed? There's several patterns out there that suggest how, but most have an issue that's basically going against your requirement: They more or less make the coordinator a singleton (either itself or as a property of another singleton like the AppDelegate). A coordinator is often times de factor a singleton, too (but not always, and it doesn't have to be).
What I tend to do is relying on simple initialized properties or (most often) lazy properties and protocol oriented programming. Let's construct an example: UserService shall be the protocol defining all the functionality your service needs, MyUserService its implementing struct. Let's assume UserService is a design construct that basically functions as a getter/setter system for some user related data: Access tokens (e.g. saved in the keychain), some preferences (an avatar image's URL) and the like. On initialization MyUserService also prepares the data (loads from the remote, for example). This is to be used in several independent screens/view controllers and is not a singleton.
Now each view controller that is interested in accessing this data has a simple property for it:
lazy var userService: UserService = MyUserService()
I keep it public because that allows me to easily mock/stub it in unit tests (if I need to do that, I can create a dummy TestUserService that mocks/stubs the behavior). The instantiation could also be a closure that I can easily switch out during a test if the init needs parameters. Obviously the properties don't even necessarily need to be lazy depending on what the objects actually do. If instantiating the object ahead of time does no harm (keep unit tests in mind, also outgoing connections), just skip the lazy.
The trick is obviously to design UserService and/or MyUserService in a way that doesn't lead to problems when creating multiple instances of it. However, I found that this is not really a problem 90% of the time as long as the actual data the instance is supposed to rely on is saved somewhere else, in a single point of truth, like the keychain, a core data stack, user defaults, or a remote backend.
I am aware this is kind of a cop-out answer, as in a way I am just saying describing an approach that's (at least part of) many generic patterns out there. However I found this to be the most generic and simple form to approach dependency injection in Swift. The coordinator pattern can be used orthogonal to it, but I found it to be less "Apple-like" in day to day use. It does solve a problem, but mostly the one you get it you don't properly use storyboards as they are intended (especially: just using them as "VC repos", instantiating them from there and transitioning yourself in code).
[1] Except some basic and/or minor things you can pass in a completion handler or prepareForSegue. That's debatable and depends on how strict you follow the coordinator or another pattern. Personally I sometimes take a shortcut here as long as it doesn't bloat up things and becomes messy. Some pop-up designs are simpler done that way.
As a closing remark, the phrase "Note that we want to avoid Singleton" as well as your comment regarding that under the question give me the impression you just follow that advice without properly having thought about the rationale. I know that "Singleton" is often times considered an anti-pattern, but just as often that judgement is mis-informed. A singleton can be a valid architectural concept (which you can see by the fact that it's used extensively in frameworks and libraries). The bad thing about it is just that it too often tempts developers to take shortcuts in design and abuse it as kind of an "object repository" so that they don't need to think about when and where to instantiate objects. This leads to messy-ness and the bad reputation of the pattern.
A UserService, depending on what that actually does in your app might be a good candidate for a singleton. My personal rule of thumb is: "If it manages state of something that's singular and unique, like a specific user that can only ever be in one state at a given time", I might go for a singleton.
Especially if you cannot design it in the way I outlined above, i.e. if you need to have in-memory, singular state data, a singleton is basically an easy and proper way to implement this. (Even then using (lazy) properties is beneficial, your view controllers then don't even need to know whether it's a singleton or not and you can still stub/mock it individually (i.e. not just the global instance).)
These are your requirements as I understand them:
VC4 and VC8 must be able to share state via a UserService class.
UserService must not be a singleton.
UserService must be supplied to VC4 and VC8 using dependency injection.
A dependency injection framework must not be used.
Within these constraints, I would suggest the following approach.
Define a UserServiceProtocol that has methods and/or properties for accessing and updating the state. For example:
protocol UserServiceProtocol {
func login(user: String, password: String) -> Bool
func logout()
var loggedInUser: User? //where User is some model you define
}
Define a UserService class that implements the protocol and stores its state somewhere.
If the state only needs to last as long as the app is running, you could store the state in a particular instance, but this instance would have to be shared between VC4 and VC8.
In this case, I would recommend creating and holding the instance in AppDelegate and passing it through the chain of VCs.
If the state needs to persist between launches of the app, or if you don't want to pass an instance through the chain of VCs, you could store the state in user defaults, Core Data, Realm, or any number of places external to the class itself.
In that case, you could create the UserService in VC3 and VC7 and pass it in to VC4 and VC8. VC4 and VC8 would have var userService: UserServiceProtocol?. The UserService would need to restore its state from the external source. This way even though VC4 and VC8 have different instances of the object, the state would be the same.
First of all, I believe there's a wrong assumption in your question.
You define your VC'c hierarchy as such:
Example:
VC1 -> VC2 -> VC3 -> VC4
VC5 -> VC6 -> VC7 -> VC8
However, on iOS (unless you're using some very strange hacks) there's always going to be a common parent at some point, like a navigation controller, tab bar controller, master-detail controller or page view controller.
So I assume that a correct scheme could look, for example, like this:
Tab Bar Controller 1 -> Navigation Controller 1 -> VC1 -> VC2 -> VC3 -> VC4
Tab Bar Controller 1 -> Navigation Controller 2 -> VC5 -> VC6 -> VC7 -> VC8
I believe looking at it like this makes it easy to answer your question.
Now if you're asking for an opinion what's the best way to handle DI on iOS, I'd say there's no such thing as the best way. However I personally like to stick to the rule that objects should not be responsible for their own creation/initialization. So things like
private lazy var service: SomeService = SomeService()
are out of question. I'd prefer an init that requires SomeService instance or at least (easy for ViewControllers):
var service: SomeService!
That way you pass the responsibility of fetching the right models/services etc up to the creator of the instance, meanwhile you can implement your logic with a simple but important assumption that you have everything you need to have (or you make your class fail early (for example by using force unwrapping), which is actually good during development).
Now, how you fetch these models - is it by initialising them, passing them around, having a singleton, using providers, containers, coordinators etc - it's entirely up to you and also should depend on factors like the complexity of the project, client demands, whatever tools you're using - so generally, whatever works is fine, as long as you stick to good OOP practices.
Here is an approach I've used on a few projects that may help you.
Create all your view controllers via factory methods in a ViewControllerFactory.
The ViewControllerFactory has its own UserService object.
Pass the ViewControllerFactory's UserService object to those view controllers that need it.
A modest example here:
struct ViewControllerFactory {
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol) {
self.userService = userService
}
// This VC needs the user service
func makeVC4() -> VC4 {
let vc4 = VC4(userService: userService)
return vc4
}
// This VC does not
func makeVC5() -> VC5 {
let vc5 = VC5()
}
// This VC also needs the user service
func makeVC8() -> VC8 {
let vc8 = VC8(userService: userService)
return vc8
}
}
The ViewControllerFactory object can be instantiated and stored in AppDelegate.
That's the basics. In addition, I'd also look at the following (see also the other answers that have made some good suggestions here):
Create a UserServiceProtocol that UserService conforms to. This makes it easy to create mock objects for testing.
Look into the Coordinator pattern to handle navigation logic.
I find that the coordinator/router design pattern is most suited for injecting dependences and handling app navigation. Have a look at this post, it helped me a lot https://medium.com/#dkw5877/flow-coordinators-333ed64f3dd
I've tried to solve this and uploaded an example architecture here: https://github.com/ivanovi/DI-demo
To make it more clear, I simplified the implementation using three VCs, but the solution will work with any kind of depth. The view controllers chain is as follows:
Master -> Detail -> MoreDetail(where the dependency is injected)
The proposed architecture has four building blocks:
Coordinator Repository: Contains all coordinators and the shared states. Injects the required dependencies.
ViewController Coordinator: Executes the navigation to the next ViewController. The coordinator holds a factory that produces the needed next instance of a VC.
ViewController factory: Responsible for initialising and configuring a specific ViewController. It is normally owned by a coordinator and injected by the CoordinatorRepository into the Coordinator.
The ViewController: The ViewController to be presented on the screen.
N.b.: In the example I return the newly created VC instance just to produce the example - i.e. in real life implementation returning the VC is not needed.
Hope it helps.
let viewController = CustomViewController()
viewController.data = NSObject() //some data object
navigationController.show(viewController, sender: self)
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var appCoordinator:AppCoordinator?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = UINavigationController()
appCoordinator = AppCoordinator(with: window?.rootViewController as! UINavigationController)
appCoordinator?.start()
window?.makeKeyAndVisible()
return true
}
}
I'm trying to use the MVC in an app loading a url into a webView. I'm struggling with how to/whether to define the back, forward, reload, etc... functions in the model or the viewController. I'm pretty sure they belong in the model, but then how do I call them in the IBAction for the corresponding button?
In trying to call the class function in the IBAction, first I have to create an instance of WebViewLoadRequest in each IBAction which seems extraneous. I can't (and probably shouldn't) create a global instance of WebViewLoadRequest because self isn't available in the property initializer to reference the UIWebView Outlet
class WebViewLoadRequest {
var outlet: UIWebView
var url : String
private var address: URL
init(outlet: UIWebView, url: String) {
self.outlet = outlet
self.url = url
self.address = URL(string: url)!
}
func load() {
self.outlet.loadRequest(URLRequest(url:address))
}
func back() {
if outlet.canGoBack {
outlet.goBack()
}
}
func forward() {
if outlet.canGoForward {
outlet.goForward()
}
}
func refresh() {
outlet.reload()
}
}
To simplify:
How can I write the following function in the model layer and implement it in the ViewController?
//outlet refers to a UIWebView
func back() {
if outlet.canGoBack {
outlet.goBack()
}
}
Or should this strictly be a function of the view controller? I realize essentially just the view is being changed, but there's a lot going on behind the scenes in the goBack operation that I'm not sure belongs in the ViewController
You are confusing two things here IMO, there is object oriented programming which you described in your question. There is also another concept which is commonly used and also very common in Swift, this concept is MVC. Model, View, Controller. This is basically a seperation of powers. In practice this comes down to:
Model - custom class with all the logic, in your case the WebViewLoadRequest
View - defined in a 'storyboard'
Controller - Will be a subclass of UIViewController, this subclass also has a reference to your model, all the IBActions and IBOutlets to connect to and update your UI.
You can learn more about MVC here:
https://www.raywenderlich.com/132662/mvc-in-ios-a-modern-approach
Also I would strongly recommend watching the C193p course from Standford by Paul Hegarty, this teaches you all there is to know. For example the 4th week is about MVC
https://itunes.apple.com/us/course/developing-ios-10-apps-with-swift/id1198467120
EDIT:
Basically your construction is a construction of delegation. The normal way to solve this issue is by creating a delegate property on your model-class, the delegate implements a certain protocol with for example a 'canIGoBack'-function. The model can request extra data from the delegate by calling that delegate function. Apple has something about delegation in their swift manual (check the 'Delegation' part:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
I believe Paul Hegarty also discussed it.
However, in this case it is also fine to do the canGoBack in the viewcontroller and conditionally call your model. MVC is only a guideline, there are many reasons to sometimes do logic in a viewcontroller, the biggest reason is probably if it makes shit easier. Implementing delegation only for a canGoBack is most of the times just silly.
It may look a dumb question (sorry if it is), but usually, in my apps, I have this rootVC to inherit with others VCs. So came to me that I have multiples instances of this root. For example, in AppDelegate I call my first view as:
let window = UIWindow(frame: UIScreen.main.bounds)
let root = SplashViewController(nibName: "SplashViewController", bundle: nil)
window.rootViewController = UINavigationController(rootViewController: root)
window.makeKeyAndVisible()
self.window = window
Then SplashViewController inherits from my RootViewController where I can make some view configurations. But, when I call another VC (like InitialViewController) which also inherit from my root, I'm creating new instance from my root or using the same?
And do you think that it is a good practice?
I was reading and searching but I couldn't find or understand clearly in the api reference: https://developer.apple.com/reference/uikit/uiviewcontroller
Any Suggestion? Thanks in advance!
Having a common subclass for all your view controllers could be useful, but try not to burden it too much. Also consider using composition instead of inheritance where possible and reasonable (Prefer composition over inheritance?).
Then SplashViewController inherits from my RootViewController where I can make some view configurations. But, when I call another VC (like InitialViewController) which also inherit from my root, I'm creating new instance from my root or using the same?
Don't worry. If you don't use static variables or variables the in global scope, then each instance of your view controllers will be independent.
This is an example falling into the anti patterns of OOPS. In Swift you can address this with the use of protocols.
Say, you have a common class to inherit , what if some set of class needs a special functionality which others don't?. If you choose to add this in the common superclass you tend to bloat all other subclasses that inherit the super class. If you don't, you may need to duplicate the code.
You can approach the problem elegantly with the use of protocols, I think this how you would do,
protocol CommonTraits { }
extension CommonTraits { // provide default implementations of common methods }
protocol SpecialSetOfTraits { }
extension SpecialSetOfTraits { // provide default implementations of common methods }
Class A : CommonTraits {} // less bloating
Class B : CommonTraits, SpecialSetOfTraits { } // Additional functionality.
This is why i love Swift :)
Why do we need to downcast using the keyword as after instantiating a view controller with UIStoryboard's instantiateViewController(withIdentifier:) method in order for the view controller's properties to be accessible? The UIStoryboard method instantiateViewController(withIdentifier:) already returns a UIViewController and knows which class based on the Storyboard ID or this is what I assume happens but not totally true.
The following code works and compiles but I want to understand why. If I were building this based on the documentation, I wouldn't have assumed downcasting would be necessary so I'm trying to figure out what part I haven't learned or understood in regards to types and/or objects being returned from functions.
func test_TableViewIsNotNilOnViewDidLoad() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(
withIdentifier: "ItemListViewController")
let sut = viewController as! ItemListViewController
_ = sut.view
XCTAssertNotNil(sut.tableView)
}
Because storyboard.instantiateViewController... always returns a UIViewController (the base class for your specific subclass) and thus cannot know implementation details specific to your subclass.
The method mentioned above doesn't infer your sub-class type based on the storyboard id, this is something you do in your code when downcasting (see here).
func instantiateViewController(withIdentifier identifier: String) -> UIViewController
So it works because you get an UIViewController from the method above and then you force downcast it to your ItemListViewController (it always works because you defined ItemListViewController as an UIViewController subclass).
PS. I'm not sure I've understood your question though, this seems pretty straightforward.
knows which class based on the Storyboard ID
This is completely incorrect. The Storyboard ID is a string. In your case it happens to be a static string, but this method doesn't require that. It could be computed at run time (and I've personally written code that does that). Your string happens to match the classname, but there's no requirement that this be true. The identifier can be any string at all. And the storyboard isn't part of the compilation process. The storyboard could easily be changed between the time the code is compiled and the time it's run such that the object in question has a different type.
Since there isn't enough information to compute the class at compile time, the compiler requires that you explicitly promise that it's going to work out and to decide what to do if it fails. Since you use as!, you're saying "please crash if this turns out to be wrong."
This is partially due to polymorphism. Take for example the following code:
class Person {
let name: String
}
class Student: Person {
let schoolName: String
}
func createRandomPerson(tag: Int) -> Person {
if tag == 1 { return Person() }
else { return Student() }
}
let p = createRandomPerson(2)
Depending on the value of the tag parameter you'll get either a Person or a Student.
Now if you pass 2 to the function, then you can be sure that createRandomPerson will return a Student instance, but you will still need to downcast as there are scenarios where createRandomPerson will return an instance of the base class.
Similar with the storyboard, you know that if you pass the correct identifier you'll get the view controller you expect, however since the storyboard can create virtually any instance of UIViewController subclasses (or even UIViewController) the function in discussion has the return type set to UIViewController.
And similarly, if you do your math wrong - i.e. you pass 1 to createRandomPerson(), or wrong identifier to the storyboard, then you won't receive what you expect to.
You maybe need to do some background reading on object oriented programming in general to help you understand. Key concepts are class, instance/object, inheritance, and polymorphism.
storyboard.instantiateViewController() will create an instance of ItemListViewController but it is being returned as UIViewController. If this part is difficult to understand then this is where you need objected oriented knowledge background.
In OO languges, like C++ for example, an instance of a class (also known as an object) can be referenced by a pointer to its parent (or grandparent or great grand parent etc.) class. In the majority of literature and tutorials on object orientation the concept on inheritance and casting and polymorphism is always illustrated using pointers to base classes and derived objects and so on.
However with Swift, pointers aren't exposed in the way there are in many other OO languages.
In your code, and explaining this in a simplified way and as if it was C++ or a similar OO language as that is how most tutorials on OO explain things:
let viewController = storyboard.instantiateViewController(
withIdentifier: "ItemListViewController")
viewController is a "pointer" of class type UIViewController which is pointing to an object of type ItemListViewController.
The compiler sees the pointer viewController as being of type UIViewController and therefore it does not know about any of the specific methods or properties of ItemListViewController unless you explicitly do the cast so that it knows about them.