236

I've got the following function which compiled cleanly previously but generates a warning with Xcode 8.

func exitViewController()
{
    navigationController?.popViewController(animated: true)
}

"Expression of type "UIViewController?" is unused".

Why is it saying this and is there a way to remove it?

The code executes as expected.

Gruntcakes
  • 37,738
  • 44
  • 184
  • 378

7 Answers7

505

TL;DR

popViewController(animated:) returns UIViewController?, and the compiler is giving that warning since you aren't capturing the value. The solution is to assign it to an underscore:

_ = navigationController?.popViewController(animated: true)

Swift 3 Change

Before Swift 3, all methods had a "discardable result" by default. No warning would occur when you did not capture what the method returned.

In order to tell the compiler that the result should be captured, you had to add @warn_unused_result before the method declaration. It would be used for methods that have a mutable form (ex. sort and sortInPlace). You would add @warn_unused_result(mutable_variant="mutableMethodHere") to tell the compiler of it.

However, with Swift 3, the behavior is flipped. All methods now warn that the return value is not captured. If you want to tell the compiler that the warning isn't necessary, you add @discardableResult before the method declaration.

If you don't want to use the return value, you have to explicitly tell the compiler by assigning it to an underscore:

_ = someMethodThatReturnsSomething()

Motivation for adding this to Swift 3:

  • Prevention of possible bugs (ex. using sort thinking it modifies the collection)
  • Explicit intent of not capturing or needing to capture the result for other collaborators

The UIKit API appears to be behind on this, not adding @discardableResult for the perfectly normal (if not more common) use of popViewController(animated:) without capturing the return value.

Read More

tktsubota
  • 9,371
  • 3
  • 32
  • 40
  • 15
    This is (in my opinion) definitely a step back from Swift 2, especially when there are methods like this that, even though they _do_ return a value, there are perfectly valid use cases where you just don't use it. – Nicolas Miari Jun 16 '16 at 03:26
  • 16
    1. You don't need the `let`: you can just assign to _ without preceding it with `let` or `var`. – rickster Jun 16 '16 at 04:34
  • 1
    @rickster Did not know that will add to answer. – tktsubota Jun 16 '16 at 04:36
  • 5
    2. @NicolasMiari [File a bug](http://bugreport.apple.com). There's an annotation (`@discardableResult`) for functions that do return a value but where it's expected that one might ignore the return value. UIKit just hasn't applied that annotation to their API. – rickster Jun 16 '16 at 04:36
  • 37
    This is horrible syntax. Why would they do this? Yuck. – David S. Jul 12 '16 at 21:18
  • @DavidShaw Yeah, the syntax is pretty bad. Though I am guessing they do this for making sure your code is as bug-free as it can get. The latest Xcode automatically found a bug for me when I was assigning a variable using `==`. However, Apple's code should definitely be updated for this using `@discardableResult`. – tktsubota Jul 12 '16 at 21:49
  • Well I found the "reason" document. If you are interested, it's described in great detail here: https://github.com/apple/swift-evolution/blob/master/proposals/0047-nonvoid-warn.md -- I still disagree with it. – David S. Jul 12 '16 at 21:59
  • I think this is one of the advantages of Swift 3. Then we can give affects to previous view controller, couldn't we? – Capella Oct 02 '16 at 18:58
  • @SoftDev If you mean the view controller being popped, then yes, but I don't see the reason to. However, this warning has helped me multiple times during the time I've been using Xcode 8 and Swift 3. – tktsubota Oct 02 '16 at 21:47
  • I agree with what @DavidShaw says about it just being ugly. Also it may be best to just leave the warning - perhaps on the next update and subsequent migration the code will be converted *correctly*. – xivusr Oct 31 '16 at 05:54
  • So why does the warning disappear if you unwrap the navigation controller? `if let nc = self?.navigationController { nc.popViewController(animated: true) }` – Darko Dec 19 '16 at 15:51
  • The real question is why I want that UIViewController. I could get it through viewControllers.last if needed, I can't imagine any pattern where I would want it like that. – Lucas van Dongen Dec 23 '16 at 15:54
  • @Darko It probably has to do with how it handles optionals. Thanks for bringing it up, I'll look into it some more. – tktsubota Dec 24 '16 at 20:59
  • yet another reason why Swift is NOT ready for prime time. just say'n – eric Mar 28 '17 at 22:00
38

When life gives you lemons, make an extension:

import UIKit

extension UINavigationController {
    func pop(animated: Bool) {
        _ = self.popViewController(animated: animated)
    }

    func popToRoot(animated: Bool) {
        _ = self.popToRootViewController(animated: animated)
    }
}

Note that adding something like @discardableResult func pop(animated: Bool) -> UIViewController? will result in the same warning you are trying to avoid.

With the extension you can now write:

func exitViewController()
{
    navigationController?.pop(animated: true)
}

func popToTheRootOfNav() {
    navigationController?.popToRoot(animated: true)
}

Edit: Added popToRoot too.

StuartM
  • 6,743
  • 18
  • 84
  • 160
CodeReaper
  • 5,988
  • 3
  • 35
  • 56
  • This should be the accepted solution since it's the cleanest fix to what is sure to be fixed in an Xcode update. – xivusr Oct 31 '16 at 06:00
24

In Swift 3, ignoring the return value of a function that has a declared return value results in a warning.

One way to opt out of this is to mark the function with the @discardableResult attribute. Since you don't have control over this function, that won't work.

The other method to get rid of the warning is to assign the value to _. This tells the compiler you know the method returns a value but you don't want to retain it in memory.

let _ = navigationController?.popViewController(animated: true)
Matthew Seaman
  • 7,952
  • 2
  • 37
  • 47
  • 2
    I guess we'll have to stick with the ugly `_` until Apple updates UIKit with this new attribute. – Nicolas Miari Jun 16 '16 at 03:27
  • 2
    Unfortunately `@discardableResult` does not work (at least it still croaks with 8b4). Friedrich Schiller loved rotten apples. Probably a matter of taste :-( – qwerty_so Aug 03 '16 at 11:17
5

Screenshot 1

Although it work correctly if kept as it is but the number of warning increases.

The solution is to simply replace it with underscore ( _ ) though it seems to be ugly.

Eg.  _ = navigationController?.popViewController(animated: true)

Screenshot 2

Jayprakash Dubey
  • 35,723
  • 18
  • 170
  • 177
3

Use discardableResult in this condition.

According to < Swift Programming Language > , chapter Language Reference - Attributes.

discardableResult

Apply this attribute to a function or method declaration to suppress the compiler warning when the function or method that returns a value is called without using its result.

There is also a demo in < Swift Programming Language >, chapter Language Guide - Methods.

@discardableResult
mutating func advance(to level: Int) -> Bool {
    ...
    return true
}

Because it’s not necessarily a mistake for code that calls the advance(to:) method to ignore the return value, this function is marked with the @discardableResult attribute. For more information about this attribute, see Attributes.

b m gevariya
  • 300
  • 1
  • 4
  • 12
black_pearl
  • 2,549
  • 1
  • 23
  • 36
0

If you want to go the road of extensions like CodeReaper's answer you should use @descardableResult. This keeps all the possibilities, but silences the warning.

import UIKit

extension UINavigationController {
    @discardableResult func pop(animated: Bool) -> UIViewController? {
        return self.popViewController(animated: animated)
    }

    @discardableResult func popToRoot(animated: Bool) -> [UIViewController]? {
        return self.popToRootViewController(animated: animated)
    }
}
Casper Zandbergen
  • 3,419
  • 2
  • 25
  • 49
-1

Another way is you can unwrap the self.navigationController? value and call the popViewController function.

if let navigationController = navigationController {
    navigationController.popViewController(animated: true)
}
b m gevariya
  • 300
  • 1
  • 4
  • 12
muazhud
  • 914
  • 7
  • 18