Jonathan.
Jonathan.

Reputation: 55544

Unwrapping multiple optionals in swift

I want to load a PDF that is in my application bundle into a CGPDFDocument.

Is there some way of calling a function that if any of the parameters that don't accept options have values that are nil, the function isn't called and nil is returned.

eg:

let pdfPath : String? = NSBundle.mainBundle().pathForResouce("nac_06", ofType:"pdf")
//I want to do this
let data : NSData? = NSData(contentsOfFile:pdfPath)
//I have to do this
let data : NSData? = pdfPath != nil ? NSData(contentsOfFile:pdfPath) : nil
let doc : CGPDFDocumentRef? = CGPDFDocumentCreateWithProvider(CGDataProviderCreateWithCFData(data));
//pageView.pdf is optional, nicely this function accepts the document as an optional
pageView.pdfPage = CGPDFDocumentGetPage(doc, 1);

Because NSData.init?(contentsOfFile path:String), doesn't define path as optional, even though it is has an optional return value, I have to check before and if the parameter is nil, return nil. Is there some syntactic sugar for the data assignment (instead of the ?: operator)?

Upvotes: 0

Views: 686

Answers (4)

vadian
vadian

Reputation: 285069

Either use multiple optional bindings separated by commas

func loadPDF() -> CGPDFDocumentRef? 
{
  if let pdfPath = NSBundle.mainBundle().pathForResouce("nac_06", ofType:"pdf"),
       data = NSData(contentsOfFile:pdfPath), 
       doc = GPDFDocumentCreateWithProvider(CGDataProviderCreateWithCFData(data)) {
    return doc
  } else {
    return nil
  }
}

or use the guard statement

func loadPDF() -> CGPDFDocumentRef? 
{
  guard let pdfPath = NSBundle.mainBundle().pathForResouce("nac_06", ofType:"pdf") else { return nil }
  guard let data = NSData(contentsOfFile:pdfPath) else { return nil }
  return GPDFDocumentCreateWithProvider(CGDataProviderCreateWithCFData(data))
}

All explicit type annotations are syntactic sugar and not needed.

Edit:

In your particular case you need only to check if the file exists and even this – the file is missing – is very unlikely in iOS. Another benefit is to be able to return a non-optional PDFDocument.

func loadPDF() -> CGPDFDocumentRef
{
  guard let pdfPath = NSBundle.mainBundle().pathForResource("nac_06", ofType:"pdf") else {
    fatalError("file nac_06.pdf does not exist")
  }
  let data = NSData(contentsOfFile:pdfPath)
  return CGPDFDocumentCreateWithProvider(CGDataProviderCreateWithCFData(data!))!
}

Upvotes: 3

Cristik
Cristik

Reputation: 32789

You could do something fancy by defining a custom operator to deal with this situation. For example:

infix operator ^> {associativity left precedence 150}

func ^><T, U>(arg: T?, f: T->U?) -> U?{
    if let arg = arg {
        return f(arg)
    } else {
        return nil
    }
}

The operator takes an optional left-side argument and a function that takes a non-optional and returns another optional as a right-side argument.

You could then write your code like this:

let pdfPath = NSBundle.mainBundle().pathForResource("nac_06", ofType:"pdf")
//the line below needs a NSData extension
let data = pdfPath ^> NSData.fileContents
let doc = data ^> CGDataProviderCreateWithCFData ^> CGPDFDocumentCreateWithProvider
//pageView.pdf is optional, nicely this function accepts the document as an optional
pageView.pdfPage = CGPDFDocumentGetPage(doc, 1)

Note that for this to work you need to add an extension to NSData, as you cannot map the init(contentsOfFile:) initializer to a generic function that can be passed to ^>.

extension NSData {
    class func fileContents(path: String) -> NSData? {
        return NSData(contentsOfFile: path)
    }
}

The usage of the ^> operator reverts however the order you write the function names, if you prefer having the function names in the same order as the original code, you can add a reversed operator that does the same thing:

infix operator ^< {associativity right precedence 150}

func ^< <T, U>(f: T->U?, arg: T?) -> U?{
    if let arg = arg {
        return f(arg)
    } else {
        return nil
    }
}

let pdfPath = NSBundle.mainBundle().pathForResource("nac_06", ofType:"pdf")
let data = NSData.fileContents ^< pdfPath
let doc = CGPDFDocumentCreateWithProvider ^< CGDataProviderCreateWithCFData ^< data
//pageView.pdf is optional, nicely this function accepts the document as an optional
pageView.pdfPage = CGPDFDocumentGetPage(doc, 1)

Upvotes: 0

Midhun MP
Midhun MP

Reputation: 107121

There is two ways to achieve this.

  1. Extend NSData class and create your own convenience init? method
  2. Use the guard statement

I prefer the second method:

func getPDF(path : String?) -> CGPDFDocumentRef?
{
    guard let filePath = path,
              data     = NSData(contentsOfFile: filePath),
              pdf      = GPDFDocumentCreateWithProvider(CGDataProviderCreateWithCFData(data)) else
    {
        return nil
    }
    return pdf
}

Call the method like:

let doc = getPDF(path : NSBundle.mainBundle().pathForResouce("nac_06", ofType:"pdf"))

Upvotes: 0

Cristik
Cristik

Reputation: 32789

I assume that you also don't want to continue with the execution of the function if pdfPath or data is nil. In this case, guard would be the best choice:

guard let pdfPath = NSBundle.mainBundle().pathForResouce("nac_06", ofType:"pdf") else {
    // eventually also report some error
    return
}

guard let data = NSData(contentsOfFile:pdfPath) else {
    // eventually also report some error
    return
}

// at this point you have a valid data object

You could also combine this into a single guard statement, to reduce the code duplication, you'll loose however in this case the possibility to know which of the two failed.

guard let pdfPath = NSBundle.mainBundle().pathForResource("nac_06", ofType:"pdf"),
    data = NSData(contentsOfFile:pdfPath) else {
        // eventually also report some error
        return
}

Upvotes: 0

Related Questions