translator:Poxue
Required before submitting review
As part of the following three documents, their contents are related to each other:
The contents of these three documents are interrelated (for example, the adjustment of an API in the standard library corresponds to an API guideline, or the clang importer rule formulated according to a design guide, etc.). Because of these overlapping contents, in order to ensure that the discussion is maintainable, we hope you:
-
Before submitting the review, have a basic understanding of all the contents of the above three documents;
-
When submitting the review of the above three documents, please refer to the review statement of each document。 When you submit a review, if cross references between documents help you explain your ideas, you should include them (which is also advocated).
brief introduction
This proposal describes how we can improve Swift’sClang Importer
, it has two functions: first, map the APIs of C and Objective-C to swift version; Secondly, translate the names of functions, types, methods and attributes in Objective-C to meet their requirementsAPI design guideThese requirements are the principles we established when designing swift 3.
Our approach focuses on the Objective-C versionCocoa coding guideAnd swift versionAPI design guideDifferences between. Some simple linguistic analysis methods are used to help us automatically convert the names in Objective-C into more “original” names.
The conversion results can be displayed inSwift 3 API Guidelines ReviewView in this repository. This repository containsSwift 2andSwift 3Written Objective-C APIs project, and some sample code that has been migrated to swift 3 version. You can also passCompare the two branchesTo see all the changes.
motivation
Objective-C versionCocoa coding guideIt provides a complete framework for creating simple and consistent APIs using Objective-C. But swift is a different programming language. In particular, it is a strongly typed language that supports language features such as type derivation, generic programming and overloading. Therefore, Apis written based on Objective-C are a little acclimatized to swift, and these APIs are very wordy in swift. For example:
let content =
listItemView.text.stringByTrimmingCharactersInSet(
NSCharacterSet.whitespaceAndNewlineCharacterSet())
This is obviously an Objective-C style function call. If we write it in swift, the result should look like this:
let content =
listItemView.text.trimming(.whitespaceAndNewlines)
This is obviously more followedSwift API design guideIn particular, we ignore the type names that the compiler can force us to use (for example, view, string, character set, etc.). The purpose of this proposal is to make the API introduced from Objective-C more “original swift”, so that swift developers can have a more consistent development experience when using objective API and using swift “native code”.
The solution in this proposal is the same for the Objective-C framework (e.g. cocoa and cocoa touch) and any Objective-C API that can be used in the swift hybrid project. To illustrate,Swift core libraryThe API in Objective-C framework is re implemented, so the changes to these APIs (names) will be reflected in the implementation of swift 3 core library.
Proposed solution
The proposed solution introduces a definition of Objective-CCocoa coding guideandSwift API design guideThis method can help us change the former into the latter by setting a series of rules and referring to the cocoa coding guide and the established customs in Objective-C. This is a heuristic extension for name translation with clang importer. For example, put the global in Objective-Cenum
The constants are changed into cases in swift (this needs to remove the prefix set by Objective-C for the global enum constant name) and the factory methods in Objective-C (for example:+[NSNumber numberWithBool:]
)Mapping to initialization methods in swift(NSNumber(bool: true)
)。
The heuristic described in this proposal needs to be iterated, tuned, and tested by covering a large number of Objective-C APIs to ensure that it eventually works. However, it is still impossible to work perfectly. There must be some APIs. After “translation”, they will not be as clear as the original meaning. Therefore, our goal is to ensure that most objective C APIs can be more original in swift after translation. It also allows the author of the Objective-C API to explain the problem through API comments in the Objective-C header file for those unsatisfied translations.
The proposed solution introduces the following changes to the clang importer:
-
generalization
swift_name
Application scope of property: clang’sswift_name
Now it can only be used for renamingenum
Cases and factory methods. When it is introduced into swift, it should be generalized to allow renaming of arbitrary C or Objective-C language elements, so as to facilitate the author of C or Objective-C API to better adjust the renaming process. -
Remove redundant type names: Objective-C cocoa coding guidelines require method declarations with descriptions of each parameter. When this description restates the type of parameter, the name of the method violates the design requirement of “ignoring unnecessary characters” in swift coding guidelines. Therefore, when performing translation, we should remove those parts that describe the type.
-
Add default parameters: if the declaration of the Objective-C API strongly implies that parameters need parameter default values, you should add parameter default values for such APIs when introducing swift. For example, a parameter representing a set of options can be set to [].
-
Add label for the first parameter: if the first parameter of the method has a default value,You should set a parameter label for this parameter 。
-
Prefix Boolean semantic attributes with “is”:Bool attribute should express the semantics of assertion when readingHowever, in the Objective-C cocoa coding guide,The word “is” is prohibited in attribute names。 Therefore, when such attributes are introduced, they are prefixed with “is”.
-
A name that expresses the semantics of a value, starting with a lowercase letter: in the swift API design guide, lowercase letters are required for “non type declarations”. include,
enum
Mediumcase
And the declaration of a property or function. Therefore, when introducing values without prefixes in Objective-C, make the initials of these names lowercase (for example, a name calledURLHandler
The property of should becomeurlHandler
)。 -
Let realize
compare(_:) -> NSComparisonResult
The class complies with the comparable protocol: in Objective-C, the comparison results of class objects are judged by “sorting” (Note: for example:NSOrderedDescending
andNSOrderedAscending
)。 During the import process, by making these classes comply withComparable
Protocol, which can make the implementation of comparison operation more formal (Note: throughComparable
Provided operator (method).
In order to feel the actual effect of these conversion rules, take a lookUIBezierPath
From swift 2:
class UIBezierPath : NSObject, NSCopying, NSCoding {
convenience init(ovalInRect: CGRect)
func moveToPoint(_: CGPoint)
func addLineToPoint(_: CGPoint)
func addCurveToPoint(_: CGPoint,
controlPoint1: CGPoint, controlPoint2: CGPoint)
func addQuadCurveToPoint(_: CGPoint,
controlPoint: CGPoint)
func appendPath(_: UIBezierPath)
func bezierPathByReversingPath() -> UIBezierPath
func applyTransform(_: CGAffineTransform)
var empty: Bool { get }
func containsPoint(_: CGPoint) -> Bool
func fillWithBlendMode(_: CGBlendMode, alpha: CGFloat)
func strokeWithBlendMode(_: CGBlendMode, alpha: CGFloat)
func copyWithZone(_: NSZone) -> AnyObject
func encodeWithCoder(_: NSCoder)
}
Changes after migration to swift 3:
class UIBezierPath : NSObject, NSCopying, NSCoding {
convenience init(ovalIn rect: CGRect)
func move(to point: CGPoint)
func addLine(to point: CGPoint)
func addCurve(to endPoint: CGPoint,
controlPoint1 controlPoint1: CGPoint,
controlPoint2 controlPoint2: CGPoint)
func addQuadCurve(to endPoint: CGPoint,
controlPoint controlPoint: CGPoint)
func append(_ bezierPath: UIBezierPath)
func reversing() -> UIBezierPath
func apply(_ transform: CGAffineTransform)
var isEmpty: Bool { get }
func contains(_ point: CGPoint) -> Bool
func fill(_ blendMode: CGBlendMode, alpha alpha: CGFloat)
func stroke(_ blendMode: CGBlendMode, alpha alpha: CGFloat)
func copy(with zone: NSZone = nil) -> AnyObject
func encode(with aCoder: NSCoder)
}
It can be seen that in the swift 3 version, many parts describing type information in the original API have been removed. The converted result is closer to the requirements in swift API design guide. Now, swift developers canfoo.copy()
In this way, copy any complianceNSCopying
Instead of looking like the originalfoo.copyWithZone(nil)
In this way.
Implementation process
A pilot implementation of this proposal is in swift main repository. The swift compiler provides switches to help us see the Objective-C API introduced as described in this proposal and the conversion results of the swift code itself (for example, through the utils / omit-needless-words.py script). These switches are:
-
--enable-omit-needless-words
: this switch enables most changes to the clang importer (1, 2, 4, 5 mentioned in the previous section). It is mainly suitable for printing on master andSwift 2.2 branchOn, swift provides an interface to the Objective-C module. staySwift 3 API guidelines branchOn, it is on by default; -
--enable-infer-default-arguments
: this switch enables interference with the default value of the parameter in the clang importer (3 in the previous section); -
--swift-migration
: only inSwift 2.2 branchThis option performs the basic transformation of migrating names from swift 2 to swift 3 by adding “fix its”. By using it with other compiler switches (e.g. – fixit code, – fixit all) and a script for collecting and applying “fix its” (utils / apply fixit edits. Py), the basic migration work provided by this switch can help us understand the appearance of swift code after conversion according to this proposal in various declaration and call scenarios;
In order to use the converted name and really compile swift 3 code, you can useSwift 3 API GuidelinesBranch. The compiler enables the above functions by default, with a standard library changed with them.
Design details
This section describes the experimental implementation details of articles 2-5 of the above change rules. The real implementation is in the swift code tree, mainlylib/Basic/StringExtras.cppMediumomitNeedlessWords
Function.
The following description is closely related to the Objective-C API involved in name translation. For example:startWithQueue:compeltionHandler:
It’s a method with two selector fragments,startWithQueue
andcompletionHandler
。 The translation of this selector into swift should bestartWithQueue(_:completionHandler:)
。
Remove redundant names
Objective-C APIs often contain type names of parameters or return values, which should be removed from swift APIs. The following rules are used to identify and remove these words representing type names. [[see API design principles ignore unnecessary words]].
Define type name
The matching process is to search for specific information in the selector fragment of the old swift APIType nameSuffix, these type names are defined as follows:
-
For most Objective-C types, type names are ignored
nullable
The name introduced by swift, for example:Objective-C type Type name float Float nullable NSString String UIDocument UIDocument nullable UIDocument UIDocument NSInteger NSInteger UIUInteger NSUInteger CGFloat CGFloat -
When the Objective-C type is a block, the type name in swift is
Block
; -
When the type of Objective-C is a function pointer or reference, the type name in swift is
Function
; -
When the type of Objective-C is a division
NSInteger
、NSUInteger
orCGFloat
For typedefs other than, the type name in swift is the name of the type actually represented by these typedefs. For example, the type in Objective-C isUILayoutPriority
Actually, it’s afloat
For typedef of type, we will try to match the string (in the old version of swift API)Float
。 [see API design principles for information compensation for weak types].
Match type name
In order to delete redundant type information in the selector fragment, we need to match the string containing the above type information in the selector.
All matches are managed by the following two basic rules:
-
Match at the beginning and end of the word: whether inside the selector fragment or the type name, the word boundary position is the beginning or end of a string and before each uppercase letter. Taking each capital letter as a word boundary allows us to match “capital acronyms” without maintaining a special list of acronyms or prefixes;
For example, the followingURL
Is a match:
func documentForURL(_: NSURL) -> NSDocument?
However, the following view cannot be matched because it is in the middle of the word.
var thumbnailPreview : UIView // not matched
-
The matching character extends to the end of the type name: because we support the suffix matching a type name, so:
func constraintEqualToAnchor(anchor: NSLayoutAnchor) -> NSLayoutConstraint?
Can be simplified to:
func constraintEqualTo(anchor: NSLayoutAnchor) -> NSLayoutConstraint?
Based on the above two principles, we can perform the following matching processes:
-
Basic matching:
-
The string in the selector fragment matches the string of the type name, for example:
appendString
MediumString
matchingNSString
MediumString
:func appendString(_: NSString)
; -
In the selector fragment
Index
Match in type nameInt
For example:func characterAtIndex(_: Int) -> unichar
;
-
-
Collection matches:
-
In the selector fragment
Indexes
orIndices
Match in type nameIndexSet
For example:func removeObjectsAtIndexes(_: NSIndexSet)
; -
If the singular form of the plural noun in the selector fragment matches the type of the element in the set, the plural noun in the selector matches the set type name, for example:
func arrangeObjects(_: [AnyObject]) -> [AnyObject]
-
Special suffix matching:
-
In the selector fragment, match the in the type name with an empty string suffix
Type
or_t
For example://Note: type is matched with an empty string, so saveoperation plus //The empty string matches the saveoperationtype, //Then the part of saveoperation in the selector can be deleted. func writableTypesForSaveOperation( _: NSSaveOperationType) -> [String] //Note: type is matched with an empty string. func objectForKey(_: KeyType) -> AnyObject //Note: match with an empty string_ t。 func startWithQueue(_: dispatch_queue_t, completionHandler: MKMapSnapshotCompletionhandler)
-
The empty string in the selector fragment can match the suffix in the form of “number + D” in the type name, for example:
//Coordinate + empty string matches to 2D func pointForCoordinate(_: CLLocationCoordinate2D) -> NSPoint
Name constraints
(Note: restrictions on deleting type names)
If any of the following restrictions are violated when deleting the selector name, the deletion will not occur:
-
Don’t delete all selectors;
-
Do not convert the first segment of the selector to the swift keyword:
In swift, the first selector fragment in the Objective-C method becomesThe basis for building a method nameperhapsThe name of an attribute。
None of them can be swift keywords, otherwise, users need to use backquotes to use them.
For example, the following usage is reasonable:
extension NSParagraphStyle {
class func defaultParagraphStyle() -> NSParagraphStyle
}
let defaultStyle =
NSParagraphStyle.defaultParagraphStyle() // OK
If we deleteParagraphStyle
, it will be bad to use:
extension NSParagraphStyle {
class func `default`() -> NSParagraphStyle
}
let defaultStyle =
NSParagraphStyle.`default`() // Awkward
Other selector fragments in the Objective-C method name will become the parameter label of the swift method. This label is allowed to use the swift keyword, for example:
receiver.handle(someMessage, for: somebody) // OK
-
Do not convert the method name to “get”, “set”, “with”, “for” or “using”, the meaning of the name formed by these words is very empty;
-
Do not delete suffixes in method names that describe parameters unless the suffix is directly preceded by a preposition, verb, or gerund:
This heuristic transformation helps us avoid breaking noun phrases that introduce parameters. Deleting noun phrase suffixes directly usually does not bring the expected ideographic results. For example:
func setTextColor(_: UIColor)
...
button.setTextColor(.red()) // clear
If we delete theColor
, onlyText
, the semantics expressed when calling a method can be confusing:
func setText(_: UIColor)
...
button.setText(.red()) // appears to be setting the text!
-
If the name of a method matches an attribute in its type, do not convert the method name:
This heuristic transformation allows us to avoid converting too general names to methods that modify class properties, such as:
var gestureRecognizers: [UIGestureRecognizer]
func addGestureRecognizer(_: UIGestureRecognizer)
If we delete the in the methodGestureRecognizer
, just leaveadd
, for one is actually modifyinggesturerecognizers
The name is obviously too generic for the method of property.
var gestureRecognizers: [UIGestureRecognizer]
func add(_: UIGestureRecognizer) // should indicate that we're adding to the property
To delete a type name
We delete redundant names as follows:
1. Delete the result type information of the header。 In particular, when:
-
When a method returns a type of its own;
-
And the name of this type matches the first selector fragment in the method;
-
The matched noun is followed by a preposition;
You can delete the match.
Generally, the properties and methods that match these conditions are used to change their own type into some other equivalent form of value.
For example:
extension NSColor {
func colorWithAlphaComponent(_: CGFloat) -> NSColor
}
let translucentForeground =
foregroundColor.colorWithAlphaComponent(0.5)
Can be simplified to:
extension NSColor {
func withAlphaComponent(_: CGFloat) -> NSColor
}
let translucentForeground =
foregroundColor.withAlphaComponent(0.5)
2. Delete the redundant preposition by。 In particular, when:
-
In the first step, delete the beginning noun;
-
In the method name, the rest is
By
+The form of gerund;You can delete the superfluous
By
Yes.This heuristic allows us to use similar methods
a = b.frobnicating(c)
Method call form. For example:
extension NSString {
func stringByApplyingTransform(_: NSString,
reverse: Bool) -> NSString?
}
let sanitizedInput =
rawInput.stringByApplyingTransform(
NSStringTransformToXMLHex, reverse: false)
Through the first and second steps, it can be simplified into:
extension NSString {
func applyingTransform(
_: NSString, reverse: Bool
) -> NString?
}
let sanitizedInput =
rawInput.applyingTransform(NSStringTransformToXMLHex,
reverse: false)
3. In the last selector fragment of the method signature, delete any matching type name。 In particular, the following types:
The tail of the method name is | Delete matched |
---|---|
Selector fragment for introducing parameters | Parameter type name |
An attribute name | The type name of the property |
A method without parameters | The type name of the return value |
For example, the following situations:
extension NSDocumentController {
func documentForURL(
_ url: NSURL) -> NSDocument? // parameter introducer
}
extension NSManagedObjectContext {
var parentContext: NSManagedObjectContext? // property
}
extension UIColor {
class func darkGrayColor() -> UIColor // zero-argument method
}
...
myDocument = self.documentForURL(locationOfFile)
if self.managedObjectContext.parentContext != changedContext {
return
}
foregroundColor = .darkGrayColor()
Can be simplified to:
extension NSDocumentController {
func documentFor(_ url: NSURL) -> NSDocument?
}
extension NSManagedObjectContext {
var parent : NSManagedObjectContext?
}
extension UIColor {
class func darkGray() -> UIColor
}
...
myDocument = self.documentFor(locationOfFile)
if self.managedObjectContext.parent != changedContext {
return
}
foregroundColor = .darkGray()
4. As long as the verb is directly connected in front of the matched type name, even if the type name is in the middle of the method name, it can be deletedFor example:
extension UIViewController {
func dismissViewControllerAnimated(
flag: Bool,
completion: (() -> Void)? = nil)
}
Can be simplified to:
extension UIViewController {
func dismissAnimated(
flag: Bool,
completion: (() -> Void)? = nil)
}
Why must deletion be performed in order?
Some of the following simplifications match in the first selector fragment of the method name, and some match at the end of the method name. WhenName constraintsWhen preventing us from deleting from the beginning and end, deleting names from the head can maintain the consistency of the method family, for example, forNSFontDescriptor
For example:
func fontDescriptorWithSymbolicTraits(
_: NSFontSymbolicTraits) -> NSFontDescriptor
func fontDescriptorWithSize(
_: CGFloat) -> UIFontDescriptor
func fontDescriptorWithMatrix(
_: CGAffineTransform) -> UIFontDescriptor
...
According to the header matching rules, they can become as follows:
func withSymbolicTraits(
_: UIFontDescriptorSymbolicTraits) -> UIFontDescriptor
func withSize(
_: CGFloat) -> UIFontDescriptor
func withMatrix(
_: CGAffineTransform) -> UIFontDescriptor
...
If we insist on deleting from the tail, delete the in the first methodSymbolicTraits
After that, we can no longer delete the headerfontDescriptor
Because it goes against ourName constraintsThere is only one principle defined inwith
Meaning is not enough.
func fontDescriptorWith(
_: NSFontSymbolicTraits) -> NSFontDescriptor // inconsistent
func withSize(_: CGFloat) -> UIFontDescriptor
func withMatrix(_: CGAffineTransform) -> UIFontDescriptor
...
Note: in this way, we destroy the name consistency of the original family method.
Add parameter defaults
Except for setter methods with only one parameter, default values should be added to the parameters in the following cases:
-
Give WayThe trailing closure parameter can be nullValue is
nil
; -
Give WayCan be empty
NSZone
parameterDefault tonil
。 Zones are rarely used in swift, they should always benil
of -
Give WayParameters with “options” in the parameter nameThe default value is
[]
, representing an empty set of options; -
Nsdictionary parameter related to options, properties, or information. The default value is
[:]
;
By collecting these rules, this heuristic method can put the following code:
rootViewController.presentViewController(
alert, animated: true, completion: nil)
UIView.animateWithDuration(0.2, delay: 0.0,
options: [],
animations: { self.logo.alpha = 0.0 }) {
_ in self.logo.hidden = true
}
Become like this:
rootViewController.present(alert, animated: true)
UIView.animateWithDuration(0.2, delay: 0.0,
animations: { self.logo.alpha = 0.0 }) {
_ in self.logo.hidden = true
}
Add label for the first parameter
If the first selector fragment in the method name contains a preposition,Just separate the selector fragment from the last preposition, turn the part after the preposition in the selector into the label of the first parameter.
In addition to adding labels for the first parameter of a large number of APIs, when the first parameter has a default value, this heuristic method can also eliminate the fuzzy semantics expressed by the method name when the method is called. For example:
extension UIBezierPath {
func enumerateObjectsWith(
_: NSEnumerationOptions = [],
using: (AnyObject, UnsafeMutablePointer) -> Void)
}
array.enumerateObjectsWith(.Reverse) { // OK
// ..
}
array.enumerateObjectsWith() { // ?? With what?
// ..
}
become:
extension NSArray {
func enumerateObjects(
options _: NSEnumerationOptions = [],
using: (AnyObject, UnsafeMutablePointer) -> Void)
}
array.enumerateObjects(options: .Reverse) { // OK
// ..
}
array.enumerateObjects() { // OK
// ..
}
Prefix the attribute of bool semantics with “is”
In Objective-C, the attribute expressing bool semantics uses the corresponding getter method as the name of the attribute in swift. For example:
objective-c
@interface NSBezierPath : NSObject
@property (readonly,getter=isEmpty) BOOL empty;
Will become:
extension NSBezierPath {
var isEmpty: Bool
}
if path.isEmpty { ... }
Guidelines for implementing comparison methods
Nowadays, in order to realize object comparison, for exampleNSDate
In general, developers often extendNSDate
Let it complyComparable
Or useNSDate
ofcompare(_:) -> NSComparisonResult
method. In these scenes, rightNSDate
Using table operators can effectively improve code readability, such assomeDate < today
ThansomeDate.comare(today) == .OrderedAscending
Much clearer. Since the conversion process can determine whether a class implements the comparison method in Objective-C, all classes that implement this method can comply with itComparable
Protocol.
not onlyNSDate
, some other classes in foundation will also be affected by this change, for example:
func compare(other: NSDate) -> NSComparisonResult
func compare(decimalNumber: NSNumber) -> NSComparisonResult
func compare(otherObject: NSIndexPath) -> NSComparisonResult
func compare(string: String) -> NSComparisonResult
func compare(otherNumber: NSNumber) -> NSComparisonResult
Impact on existing code
The proposed changes introduce a number of breaking changes to existing swift code using the Objective-C framework. So, we need a migration tool to migrate swift 2 code to swift 3. stayImplementation processDescribed in-swift3-migration
Switches provide basic information for such conversion tools. In addition, the compiler needs to provide good error information (with modification suggestions) for swift code that references the old version Objective-C name. In addition, it should also provide an auxiliary query mechanism through the old version name.
statement
In order to finally formSwift API design guide, this automatic name conversion proposal was developed by Dmitri hrybenko, Ted kremenek, Chris LATTNER, Alex migicovsky, Max moiseev, Ali ozer and Tony Parker.
Supplement the comparable part added byChris AmanseSubmitted tocore-librariesIn the mailing list. Philippe hausler added it to this proposal after reviewing it.