Understanding optional global variables in swift
The reason we've set it with an exclamation mark or question mark is
because there isn't an initializer within this class
Because when every instance of a class is initialized, it has to allocate memory for its properties/instance var. So with var sut: ItemManager!
, the value of sut
is nil
when init
. Without !
, ?
the compiler can't allocate, init, so you have to init manually in an initializer. That is what compiler told you.
For using !
or ?
.
!
is used when the property/var always has value after the first assigning. And after the first time it's assigned, it can't be nil later?
is used when the pro/var can have value or not
Global variable and optional binding in Swift
I'll start by repeating the my comment from above.
Possibly you've misunderstanding the concept of global variables in Swift.
If you have a global variable, you won't have to "pass" it between any views/methods/classes etc, because the variable is defined at global scope (accessible everywhere).
Generally global variables is not a good idea, and something that you want to avoid.
Regarding the matter of global variables and swift, you really should include singletons into the discussion. See e.g. the following existing SO thread(s):
- Any reason not use use a singleton "variable" in Swift?
(How to create a global variable?)
(Declaring Global Variables in Swift)
Communication between TableViewController and ViewController by means of segues (prepare for & unwind segues)
(This answer ended up being very and probably a bit too thorough, as I didn't know in detail what your current tableview/viewcontroller program state looks like. Sorry for the lengthy answer and any inconvenience it might bring to readers of it).
Now, lets leave global variables and discuss one (among other) viable options for the communication between the two controllers in your example. From your question, I'll summarize your example as follows
- VC1: storyboard entry point, a
UITableViewController
consisting ofUITableViewCell
s, where, in these cells, you display some text, say, via instances ofUILabel
. - VC2: a
UIViewController
, accessible from the cells of VC1, containing anUITextField
instance. When user enters text into this text field, your want the text to be displayed in the associated cell in VC2 (associated in the sense that it was the cell in VC1 that was used to access VC2).
We'll associate VC1 and VC2 with (cocoa touch) classes TableViewController
(TableViewController.swift) and ViewController
(ViewController.swift), respectively. The cells in the table view controller will be associated with (cocoa touch) class TableViewCell
(TableViewCell.swift). Details for these classes follow below.
For this simple example, note that we will not embed VC1 into a navigation controller (which is otherwise appropriate for table view -> view navigation).
We'll start in the storyboard, adding objects (drag-and-drop from object library) for a Table View Controller
and a View Controller
. The table view container will also, automatically, contain, in its Table View
, a TableViewCell
. Continuing in the storyboard:
- Add a
UILabel
object to theTableViewCell
container in theTable View Controller
(align it as you wish) - In the
View Controller
, add aText Field
object and aButton
object (align them as you wish). - Set the entry point to the
Table View Controller
. - Thereafter Ctrl-drag a 'Show' segue from the
TableViewCell
to theView Controller
. - Select the
Show
segue and, from the Attributes inspector, enter an identifier for it, say, ShowDetail. - Finally, with the
TableViewCell
selected, (as above; from the attribute inspector), enter an identifier for the cell. Here, we'll use simply use identifier TableViewCell.
We now leave the storyboard for now and implement three classes, associated with the Table View Controller
, the View Controller
and the formers' TableViewCell
.
We start with the Table View Controller
, and implement our UITableViewController
sub-class. Note that here, instead of using an NSMutableArray
to hold the texts of the UITextLabel
in each cell, we'll simply use a String
array.
// TableViewController.swift
Import UIKit
class TableViewController: UITableViewController {
// Properties
var userTextLabels = [String]()
var numberOfCells: Int?
override func viewDidLoad() {
super.viewDidLoad()
numberOfCells = loadSampleTextLabels() // Load sample labels.
}
func loadSampleTextLabels() -> Int {
userTextLabels += ["Label #1", "Label #2", "Label #3"]
return userTextLabels.count
}
// func numberOfSectionsInTableView(tableView: UITableView) ...
// func tableView(tableView: UITableView, numberOfRowsInSection section: Int) ...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = ("TableViewCell")
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableViewCell
// Text for current cell
let cellText = userTextLabels[indexPath.row]
cell.userSuppliedTextLabel.text = cellText
return cell
}
// ... communication?
}
Where the two commented out methods are standard methods used in any UITableViewController
, for number of sections (e.g. return 1
) and cells (e.g. return (numberOfCells ?? 0)
) in the table, respectively. I'll leave fixing these to you.
Now, we associate the TableViewCell
object(s) in the table view with instances of a subclass to UITableViewCell
. Here, we'll use a very simple class for our cells; each cell just containing a single UILabel
instance (created via storyboard Ctrl-drag as an @IBOutlet
from the UILabel
in the table view cells).
// TableViewCell.swift
import UIKit
class TableViewCell: UITableViewCell {
// Properties
@IBOutlet weak var userSuppliedTextLabel: UILabel!
// Ctrl-drag from UILabel (in TableViewCell) in storyboard
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Finally, for the view controller that is accessed from the table view cells: use a single @IBOutlet
to the UITextField
used for user text input, and handle events in this text field using the pre-existing UITextFieldDelegate
. E.g.:
// ViewController.swift
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
// Properties
@IBOutlet weak var userSuppliedText: UITextField!
// Ctrl-drag from storyboard...
var cellText: String?
override func viewDidLoad() {
super.viewDidLoad()
userSuppliedText.text = cellText ?? "..."
// Handle the user input in the text field through delegate callbacks
userSuppliedText.delegate = self
}
// UITextFieldDelegate
func textFieldShouldReturn(textField: UITextField) -> Bool {
// User finished typing (hit return): hide the keyboard.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
cellText = textField.text
}
}
We've also declared a string property (cellText
) here, that will as act as container for communication between VC1 and VC2.
We return to the storyboard and---from the Identity inspector---associate the three storyboard objects (Table View Controller
, View Controller
, TableViewCell
) with their associated classes that we've just written above.
We're now almost at our goal; it only remains to specify how to communicate between the two controllers.
We'll begin with communication from VC1 to VC2. In your comment above, you were on the right track (for this specific solution, anyway) by looking at the prepareForSegue(...)
method. In the class for the Table View Controller
, we add the following method:
// ... add to TableViewController.swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "ShowDetail" {
let viewController = segue.destinationViewController as! ViewController
if let selectedCell = sender as? TableViewCell {
let indexPath = tableView.indexPathForCell(selectedCell)!
let currentTextInCell = userTextLabels[indexPath.row]
viewController.cellText = currentTextInCell // <-- note this
}
}
}
Hence, for VC1->VC2 communication, we can (in this example) bring whatever existing text that is currently occupying the UILabel
in the sender cell (as is specified by the String array userTextLabels
). Look at the viewDidLoad(...)
method in the ViewController.swift to see how this value is passed from VC1 and set as default text in the UITextField
in VC2.
Now, for communication VC2->VC1, which was the specific communication direction you were asking about, add another method (programmatically), again to TableViewController.swift:
// ... add to TableViewController.swift
@IBAction func unwindToTableView(sender: UIStoryboardSegue) {
if let sourceViewController = sender.sourceViewController as? ViewController,
text = sourceViewController.cellText {
// ^ note 2nd clause of if let statement above
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update cell text
userTextLabels[selectedIndexPath.row] = text
tableView.reloadRowsAtIndexPaths([selectedIndexPath], withRowAnimation: .None)
}
}
}
Here, we define an unwind action that, when triggered, retrieves the cellText
property of the view controller that was the source of the segue, i.e., in our case, the instance of ViewController
. But how do we trigger this action?
Return to the storyboard and the View Controller
. Note the three little icons in the top of the View Controller
object, more specifically, the right-most of these, named Exit
. Ctrl-drag an action from your Button
to the Exit
icon, and select the unwindToTableView
Action Segue. When you click your button the view controller, the view unwind (exit) and land at the unwindToTableView
method in the TableViewController
.
The resulting app should look something like this:
This was way longer than I had expected, but once you get started writing... Anyway, the method above uses, naturally, no global variables, but make use of references to future (prepareForSegue
) or historic (unwindToTableView
) views to get (generally from current or historic view) or set (generally in current of future view) values by using these references (to future/historic view).
Apple has their own very thorough tutorial on an example app in the tableviewcontroller/viewcontroller context that I would recommend going over. I found it very valuable myself when I started coding Swift.
Start Developing iOS Apps (Swift)
Variables need global scope, but avoid Implicitly Unwrapped Optionals
You should prefer using optional before implicitly unwrapped optional.
Therefore use following:
var cameraSession : AVCaptureSession?
Then whenever you need to use it, you can use one of the following:
If there is only one occurrence, you can use optional chaining:
cameraSession?.sessionPreset = AVCaptureSession.Preset.photo
If there are multiple occurrences, and/or the
cameraSession
is used on the right hand side (so a value is needed, not just optional):guard let cameraSession = cameraSession else { return }
// from now till the end of scope (in this case the method) you
// can use cameraSession as a non optional type
cameraSession.sessionPreset = AVCaptureSession.Preset.photo
...
Then in the
cameraInit()
, I cannot use theguard let newVarName
orif let newVarName
as thisnewVarName
object will be limited in scope and is also using a different name.
So what? The scope is limited for the given variable, not for the instance it points to. In that scope you can access it as if it was not optional. Yes, you will need to use if let cameraSession = cameraSession
or guard let cameraSession = cameraSession
everywhere where you need to use cameraSession
, but that's the price you have to pay for the type safety (and it is a very small price considering what you get for it).
UPDATE
cameraRelease
is a bit tricky.
If let solution:
func cameraRelease() {
if let cameraSession = cameraSession {
if cameraSession.isRunning {
cameraSession.stopRunning()
cameraSession.removeInput(cameraInput)
cameraSession.removeOutput(cameraOutput)
cameraOutput = nil
cameraLayer.removeFromSuperlayer()
}
}
// this you have to put outside, because here you are trying to assign
// something to the cameraSession instance property, not to the local
// variable created by if let
cameraSession = nil
}
Guard let solution:
func cameraRelease() {
guard let cameraSession = cameraSession else { return }
if cameraSession.isRunning {
cameraSession.stopRunning()
cameraSession.removeInput(cameraInput)
cameraSession.removeOutput(cameraOutput)
cameraOutput = nil
cameraLayer.removeFromSuperlayer()
}
// use self.cameraSession instead of cameraSession to refer to
// instance property and not to the local variable created by guard let
self.cameraSession = nil
}
P.S.: You should change to optionals all the variables that you have created (you can leave @IBOutlet
s as implicitly unwrapped).
How is a global variable set to private understood in swift?
A private global variable in Swift is a global that is only accessible from the file in which it is declared.
The book you are using isn't following current best-practice as far as creating singletons in Swift (perhaps it is a little out-dated?).
There is no need for the private global variable. You can just say:
class CDHelper: NSObject {
// MARK: - SHARED INSTANCE
static let shared = CDHelper()
}
Swift Global Variables
You shouldn't use global variables, I don't think that's recommended in any language.
Now here you have what looks like a Singleton class (BroadService), that's good because it's a nice solution for what you're looking for.
Next all you need to do is add a property to that class. Let's say videoLink
is a string, you can add a string property to BroadService
, for example storedVideoLink
as an optional String, and the next time you need to obtain that value after you have already fetched it, you can access it like so: BroadService.sharedInstance.storedVideoLink
.
One more thing, to have BroadService
work properly as a singleton, you should make its init
private.
To sum up, here's what I'm suggesting:
class BroadService {
static let sharedInstance = BroadService()
var storedVideoLink: String?
private init() {} // to ensure only this class can init itself
func fetchBroadcasts(completion: @escaping ([Games]?) -> ()) {
// your code here
}
}
// somewhere else in your code:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
let game = games[indexPath]
let videoLink = game.videoLink
BroadService.sharedInstance.storedVideoLink = videoLink
}
}
// now you can access it from anywhere as
// BroadService.sharedInstance.storedVideoLink
This way it all stays cohesive in the same class. You can even add a getter method for storedVideoLink
so you don't have to access it directly, and in this method you could state that if the string is nil then you fetch the data, store the link to the string, and then return the string.
When to use global variables in Swift
Every time you find yourself using a global, you need to take a step back and think hard about what the data is and how it relates to the rest of your app. It is easy to say you need to avoid globals, the hard part is knowing the best alternative for the scenario, something even veteran Cocoa developers will disagree on.
In the singleton pattern, you create a class and stash your global inside it. This is often offered as a solution because it's the easiest to prescribe and follow, but many times I wonder if it is a solution at all. Wrapping a class around a global doesn't give you any additional protections. After all, the class itself is now a global entity. I like to think of the Singleton pattern as a way of organizing, categorizing and containing globals as opposed to avoiding globals.
Singletons should be reserved for the tentpoles of your application like database or remote backend connection handlers. Every Cocoa/CocoaTouch App comes with a built in Singleton, the AppDelegate, and in many cases, assorted things can go there.
In many cases, the "correct" solution is to pass the data along, such as passing data between view controllers in the prepareForSegue:
class. This is well described in Andy Matuschak's brilliant WWDC 2014 session, Advanced iOS Application Architecture and Patterns. I agree with you though, that this doesn't apply in your example. In your example, you're not handing relevant data between two views, you're trying to share a common facility to conserver resources.
For your specific example, I would use a Singleton or similar pattern. One way that makes sense to me is to stash them inside their corresponding classes using extensions. For example:
extension NSDateFormatter {
static let newDateFormatter = NSDateFormatter()
}
// use it in your app like this:
NSDateFormatter.newDateFormatter
Like commenters said, this is a matter of opinion. Also keep in mind that Swift is still young and while it borrows heavily from Cocoa out of necessity, idioms are still evolving.
How to create a global variable?
From the official Swift programming guide:
Global variables are variables that are defined outside of any
function, method, closure, or type context. Global constants and
variables are always computed lazily.
You can define it in any file and can access it in current module
anywhere.
So you can define it somewhere in the file outside of any scope. There is no need for static
and all global variables are computed lazily.
var yourVariable = "someString"
You can access this from anywhere in the current module.
However you should avoid this as Global variables are not good for application state and mainly reason of bugs.
As shown in this answer, in Swift you can encapsulate them in struct
and can access anywhere.
You can define static variables or constant in Swift also. Encapsulate in struct
struct MyVariables {
static var yourVariable = "someString"
}
You can use this variable in any class or anywhere
let string = MyVariables.yourVariable
println("Global variable:\(string)")
//Changing value of it
MyVariables.yourVariable = "anotherString"
Declaring Global Variables in Swift
From the Swift Programming Language -
Global variables are variables that are defined outside of any
function, method, closure, or type context
So you can simply declare your variable at the top of any file straight after the import
statements.
However, I would suggest you seriously reconsider. Generally globals aren't a good idea. You are better off with properties on a singleton or using dependency injection.
Your second question "where would I instantiate the array?" is part of the reason why globals are bad - their lifecycle isn't well defined in terms of your other objects. A singleton that is initialised on first use eliminates this issue.
What is best practice for initialization of global variables in Swift?
You can declare it as optional:
var button: UIButton?
and initialize it later:
button = UIButton(frame: CGRectMake(0,0,64,49))
About Global variable for iOS Project
There are several ways to create "global" variables in Swift, and I will describe some of them.
1. Defining variables in AppDelegate
AppDelegate
seems like a logic place for some global variables. As you said you can create instance of logger or create instance of something else in AppDelegate
.
To create instance which will be used as a global variable go to the AppDelegate.swift
and create the variable like this:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
let myAppInstance = SomeClass()
...
}
Then, if you want to access myAppInstance
in any other part of the application you would write:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.myAppInstance.doSomething()
2. Creating singletons
Singletons are probably one of the most used design patters used through any Apple platform. They are present in almost any iOS API you are using when creating application, and they are commonly used to create global variables.
Essentially, singletons are lazy-loaded instances which are only created once. Let's look at the code example:
class MyDataStructure {
static var sharedInstance = MyDataStructure() // This is singleton
private init() {
}
}
We have created the class MyDataStructure
and the singleton instance named sharedInstance
. This is the most common name for the singletons since that singletons are shared instances through the app.
Note the use of the static
keyword when defining singleton. static
keyword tells the compiler to create sharedInstance
only the first time it is being accessed. Any other access to the sharedInstance
will just reuse instance that is created first time.
To use it you would just write:
MyDataStructure.sharedInstance
Related Topics
Cannot Convert Value of Type 'Nsmutablearray' to Expected Argument Type '[Skaction]'
Swift Unsafemutablepointer & Unsafemutablepointer<Unsafepointer<Sometype>>
Automatically Reload Tableviewcontroller on Rewind
Trouble Calling a Method in an Init
Core Data with Swiftui Mvvm Feedback
Swift: Get The Compile Time Name of Variable (Referencing to a Class)
Swift Preserve UIswitch State on UIlongpress
Prevent Retain Cycle in Swift Function Pointers
How to Make a Function Operate on a Sequence of Optional Values
Passing Values Between Viewcontrollers Based on List Selection in Swift
Continuous Rotation of Nsimageview (So It Appears to Be Animated)
How to Add UIpickerview in UIalertcontroller
How to Navigate from Initial UIviewcontroller to UIsplitviewcontroller in Swift