简体   繁体   中英

Swift Extensions Best Practices

I've recently gotten addicted to Extensions . One thing I've found to be a gigantic PITA is figuring where I stashed a useful method for something like an Array , an NSManagedObject , etc.

What I've done is put my extensions in an NSObject class file called CustomExtensions and I list the Class Extensions there in alphabetical order.

It works, but I want to see if it aligns with best practices. If not, what is the best way to track Extensions ?

Here's what I've done:

class CustomExtensions: NSObject {
    /*
     Useful extensions, nothing else here
     */
}
// Example extension    
extension Int {
    func formatAsTimeString() -> String {
        let seconds = self % 60
        let minutes = (self / 60) % 60
        let hours = self / 3600
        let stringHours = hours > 9 ? String(hours) : "0" + String(hours)
        let stringMinutes = minutes > 9 ? String(minutes) : "0" + String(minutes)
        let stringSeconds = seconds > 9 ? String(seconds) : "0" + String(seconds)

        if hours > 0 {
            return "\(stringHours):\(stringMinutes):\(stringSeconds)"
        }
        else {
            return "\(stringMinutes):\(stringSeconds)"
        }
    }
}
// More extensions below

I don't think the "best practices" around this issue have been figured out yet. That said, here is MHO:

Ultimately, I treat these extensions the same way I do global functions in C. After all, that's basically what they are. Global functions with a special syntax...

If the extension is only used in one file, then I will put the extension in the same file. I end up with quite a few of these by the time I'm done. For example, your formatAsTimeString is a view model kind of thing. If it is used by a single view controller, I would leave it in the view controller's file.

If the extension is used by multiple classes, then I will break it out into a separate file. I will name the file after the extension. So for example if the formatAsTimeString is used in multiple files, then I will have a file named something like, "Int+formatAsTimeString.swift" where the extension can live.

If there are a number of related functions, then I will put them together in the same file and name the file based on the abstract idea of the functions. I decide that the functions are related by imagining whether they would all have to be moved together if I choose to use them in a different program... For example, maybe I have other time string related functions...

With Swift3, I generally tend to use 'fileprivate' if the extension is only used in one file.

fileprivate extension Date
{
    func toString( dateFormat format  : String ) -> String
    {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = format
        return dateFormatter.string(from: self)
    }
}

I'd love to have good "official" guidelines on this! But as mentioned people are figuring this out as we go along.

ViewController-Extensions

An interesting use I have found is the in-file extensions for a more complex class, like a ViewController.

Instead of going:

public class MainViewController: UIViewController, SpecialDelegate {
  // functions
}

You can spread each import into its own extension:

public class MainViewController: UIViewController {
  // functions
}

extension MainViewController: SpecialDelegate {
  // functions
}

These effectively serve as easier formatting, and can make it much easier to organize extended classes, especially with very large classes/projects. Note that these should stay in the same file, or at the least in a file right next to the main one.

Simple added functionality

For simple extensions like this:

extension Int {
  /**
   Returns true when the integer is even.
   */
  var isEven: Bool {
    return self % 2 == 0
  }
}

I usually have a folder labeled extensions where I keep all of the extension files. I have witnessed more complicated projects having the files more closely to where they are used, but I disagree with that usage.

After all, extensions are supposed to be used throughout the project, hiding them makes it more difficult to find if a certain extension already exists.

Highly specialized functionality

As mentioned in other replies here it makes sense to create a fileprivate extension for use-cases that will not be used elsewhere, and put in into the file where it is used

fileprivate extension String {
  // Highly specialized function that will NEVER be used anywhere else
}

Extension Naming

I have noticed various types of writing out extension-files. When I started out doing iOS-dev the prevailing guideline was to write String-Extension.swift . Nowadays String+Extension.swift seems to have won out.

In addition to that a lot of people write the name of the added functionality to the file, like Int+IsEven.swift , but that is only useful if there is a single function per file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM