Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[ios, macos] Add NSExpression convenience constructors and helper methods. #11278

Merged
merged 9 commits into from
Apr 17, 2018

Conversation

fabian-guerra
Copy link
Contributor

@fabian-guerra fabian-guerra commented Feb 21, 2018

Fixes #11016.

  • Add documentation.
  • Add convenience properties/initializers.
  • Modify expression tests.

This PR adds convenience initializers and properties that helps developers transition from the strongly typed MGLStyleValue class.

/cc @1ec5

@fabian-guerra fabian-guerra self-assigned this Feb 21, 2018
@kkaefer kkaefer added iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS labels Feb 23, 2018
@fabian-guerra fabian-guerra force-pushed the fabian-nsexpression-addons-11016 branch from c89a9bc to 11e50c7 Compare February 23, 2018 21:31
@fabian-guerra fabian-guerra added this to the ios-v4.0.0 milestone Feb 23, 2018
@1ec5
Copy link
Contributor

1ec5 commented Feb 27, 2018

Initially the approach was to extend the current NSExpression, but doing so restricted the chain invocation that can be useful to developers.

What kind of restrictions did you run into? As long as the category method returns instancetype, it should be possible to chain invocations as seen in #11016 (comment). For example:

- (instancetype)mgl_expressionByAppendingStringExpression:(NSExpression *)stringExpression {
    return [NSExpression expressionForFunction:self selectorName:@selector(stringByAppendingString:) arguments:@[stringExpression]];
}

The only restriction I’m aware of is that a category can’t declare or autosynthesize ivars, but there’s a common workaround using associated objects.

Using NSMutableString as example, I added MGLNSExpressionBuilder which behaves like a factory object whose sole purpose is encapsulate the details of initializing and mutating expression objects using chain invocation.

In general, I tend to be skeptical about vending a builder pattern as part of an Objective-C library (as opposed to doing so in a standalone application, where there’s more room for experimentation). Did you consider subclassing NSExpression as MGLMutableExpression?

Is it possible to "append" a mgl_stepWithMinimum:stops with mgl_interpolateWithCurveType:parameters:stops: function?

“Append” is probably not the best way to think about it, since usually only strings or arrays get appended to. Rather, a step can be nested inside an interpolation or vice versa. Under the hood, that ends up looking a lot like the composite function of yore.

@fabian-guerra fabian-guerra force-pushed the fabian-nsexpression-addons-11016 branch from 11e50c7 to 3cda0e3 Compare March 6, 2018 16:28
/**
Returns a constant expression containing an `NSString`.

This is equivalent to call `[NSExpression expressionForConstant:]`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may already be confusing enough that there are two ways to create a string-typed constant expression (+[NSExpression expressionWithFormat:] and +[NSExpression expressionForConstantValue:]). This third way provides a little bit of type safety, but I’m not sure that it’s worth the additional complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I thought it would be unnecessary to add this convenient method, but after making examples using each format it was more clear/natural use the constructor for each type. I'm not sure all people are familiar with which flavor of NSExpression they may need, and I don't want them to spend time trying to figure it out. I think we should keep it.

*/
+ (instancetype)mgl_expressionForValue:(nonnull NSValue *)value;

+ (instancetype)mgl_expressionForTernaryFunction:(nonnull NSString *)conditionString trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s turn this into a polyfill for the preexisting +expressionForConditional:trueExpression:falseExpression:. It’d be a lot friendlier and safer to require an NSPredicate than a freeform string. (What syntax would the string use, for instance?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the forms I tried was using an NSPredicate, it didn't work quite as I expected but I didn't use expressionForConditional:trueExpression:falseExpression in the underlaying implementation, let me give it a second try.


+ (instancetype)mgl_expressionForStepFunction:(MGLExpressionStyleFunction)function defaultExpression:(NSExpression *)expression stops:(nonnull NS_DICTIONARY_OF(NSNumber *, id) *)stops {
NSString *functionFormat = [NSString stringWithFormat:@"FUNCTION(%@, 'mgl_stepWithMinimum:stops:', %%@, %%@, %%@)", function];
return [NSExpression expressionWithFormat:functionFormat, expression, stops];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use +expressionForFunction:selectorName:arguments: for improved type safety.

}

+ (instancetype)mgl_expressionForStepFunction:(MGLExpressionStyleFunction)function defaultExpression:(NSExpression *)expression stops:(nonnull NS_DICTIONARY_OF(NSNumber *, id) *)stops {
NSString *functionFormat = [NSString stringWithFormat:@"FUNCTION(%@, 'mgl_stepWithMinimum:stops:', %%@, %%@, %%@)", function];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first argument to FUNCTION() is an expression representing the operand of the function. It may be a $zoomLevel variable expression or a key path expression or an entire interpolation expression. So the first parameter to this method needs to be an NSExpression.

@@ -511,8 +511,6 @@
DA8963381CC549A100684375 /* sprites in Resources */ = {isa = PBXBuildFile; fileRef = DA8963341CC549A100684375 /* sprites */; };
DA8963391CC549A100684375 /* styles in Resources */ = {isa = PBXBuildFile; fileRef = DA8963351CC549A100684375 /* styles */; };
DA89633A1CC549A100684375 /* tiles in Resources */ = {isa = PBXBuildFile; fileRef = DA8963361CC549A100684375 /* tiles */; };
DA9EA82B201C0C0C00F9874D /* NSExpression+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DA9EA82A201C0C0B00F9874D /* NSExpression+MGLAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header is intentionally public so that clients like the React Native SDK can use familiar JSON-like objects instead of composing NSExpression format strings.

/cc @nitaliano

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not intentional.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a test be added that ensures this will remain the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at the project and this header is marked as public. Not sure what happened.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1ec5 looks like this header was duplicated and somehow this commit removed the duplicated entries. Although it's still public and I didn't do anything to remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, as long as the header is still part of both the dynamic and static targets, we’re in good shape. Xcode sometimes grooms the project file to remove duplicate references that may sneak in due to bad merges.

@fabian-guerra fabian-guerra changed the title [ios, macos] Add NSExpression constant value concatenation. [ios, macos] Add NSExpression convenient constructors and helper methods. Mar 8, 2018
@param stops The stops dictionay must be numeric literals in strictly ascending order.
*/
+ (instancetype)mgl_expressionForStepFunction:(nonnull NSExpression*)operatorExpression defaultExpression:(nonnull NSExpression *)expression stops:(nonnull NSExpression*)stops;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a little confusing to pair [NSExpression expressionForConstantValue:] with a stops dictionary. I would not expect that to be paired with a stops dictionary, especially with exponential interpolation mode.

Also, capturing that in Swift, steps: NSExpression(format: "\(stops)") does not seem to work. steps: NSExpression(format: "%@", stops) does work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expected. Unlike with String(format:), the string representation of an object is not necessarily the same as the literal representation that NSExpression expects in a format string. NSExpression(format: "\(array)") would also likely fail, because the literal representation of an array would look like {1, 2, 3, 4}, not:

(
    1,
    2,
    3,
    4
)

In this case, there is no literal representation for a dictionary.


@param string The string constant.
*/
+ (instancetype)mgl_expressionForString:(nonnull NSString *)string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding #11278 (comment), I think we should remove these initializers.

The original goal in #8074 was to reuse standard NSExpression APIs as much as possible, so that developers would lean on non-Mapbox resources and experiences to craft their runtime styling code. Someone who already isn’t sure whether to use NSExpression(format:) or NSExpression(forConstantValue:) now has to contend with NSExpression.mgl_expression(for:) as well. If they’re used to NSExpression in other contexts, they may wonder what NSExpression.mgl_expression(for:) provides that the other methods do not.

#11016 is meant to ease the transition for developers accustomed to the more strongly-typed MGLStyleValue API, but not necessarily to make NSExpression more strongly typed. In fact, these initializers add a false sense of type safety. The MGLStyleLayer properties no longer make use of lightweight generic types, so the following code can still crash the application at runtime:

symbolStyleLayer.text = NSExpression.mgl_expression(for: number)

Finally, the approach here is inconsistent with MGLStyleValue. NSExpression’s built-in initializers distinguish between constant values and functions, just like MGLStyleValue’s initializers. By contrast, NSExpression.mgl_expression(for:) emphasizes the data type instead of the constant nature of the value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you. These are repetitive. Although these methods attempt to avoid the confusion of having to know the difference or when to use a specific method. For someone new to expressions will they choose expressionForConstantValue or expressionWithFormat for a string/color/value? Also does expressionForConstantValue means I won't be able to modify it later? These sort of questions are what these methods try to make easier to reason about.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or someone new to expressions will they choose expressionForConstantValue or expressionWithFormat for a string/color/value?

I think it would be the same as anyone newly discovering NSExpression or NSPredicate in general. There have always been multiple ways of creating one, but the format string is preferable unless you have an overriding need for type safety. Among Objective-C developers, that would be pretty rare. I originally brought up the idea of providing these initializers for Swift developers who are migrating from style functions, which are strongly typed to a fault.

Also does expressionForConstantValue means I won't be able to modify it later?

“Constant” here means the evaluated value doesn’t change based on the context; no expression is mutable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. This PR no longer has value based initializers and focuses on the generic types.

@param trueExpression The expression for conditions equal to true.
@param falseExpression The expression for conditions equal to false.
*/
+ (instancetype)mgl_expressionForConditional:(nonnull NSString *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression;
Copy link
Contributor

@1ec5 1ec5 Mar 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this method +expressionForConditional:trueExpression:falseExpression: and conditionalize it so that it’s only defined for iOS and macOS versions that lack this initializer?

/ref #11278 (comment)

Copy link
Contributor

@1ec5 1ec5 Mar 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that won’t work, because I don’t think the method is implemented at all on older versions of iOS and macOS – it isn’t just missing from the public headers.

Maybe we can combine this initializer with the one in #11450, since it’s just a special case of a case statement that has no else if clauses.

@param stops The stops dictionay must be numeric literals in strictly ascending order.
*/
+ (instancetype)mgl_expressionForStepFunction:(nonnull NSExpression*)operatorExpression defaultExpression:(nonnull NSExpression *)expression stops:(nonnull NSExpression*)stops;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expected. Unlike with String(format:), the string representation of an object is not necessarily the same as the literal representation that NSExpression expects in a format string. NSExpression(format: "\(array)") would also likely fail, because the literal representation of an array would look like {1, 2, 3, 4}, not:

(
    1,
    2,
    3,
    4
)

In this case, there is no literal representation for a dictionary.


@param expression The evaluated expression must return a string.
*/
- (instancetype)mgl_appendingExpression:(NSExpression *)expression;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per Objective-C naming conventions, this method should be named -mgl_expressionByAppendingExpression: (cf. -[NSString stringByAppendingString:]). Specify a Swift name of mgl_appending(_:).


+ (instancetype)mgl_expressionForConditional:(nonnull NSString *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression {
NSPredicate *conditional = [NSPredicate predicateWithFormat:conditionPredicate];
return [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work on iOS 8.x or macOS 10.10.x?

}

+ (instancetype)mgl_expressionForInterpolateFunction:(nonnull NSExpression*)expressionOperator curveType:(nonnull MGLExpressionInterpolationMode)curveType parameters:(nullable NSExpression *)parameters steps:(nonnull NSExpression*)steps {
NSExpression *sanitizeParams = parameters ? parameters : [NSExpression expressionWithFormat:@"%@", parameters];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If parameters is nil, then sanitizeParams becomes a constant value expression representing nil. Consider writing that out explicitly:

parameters = parameters ?: [NSExpression expressionForConstantValue:nil];

@param curveType The curve type could be `MGLExpressionInterpolationModeLinear`,
`MGLExpressionInterpolationModeExponential` and
`MGLExpressionInterpolationModeCubicBezier`.
@param steps The steps dictionay must be numeric literals in strictly ascending order.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: dictionary.

<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-zoom"><code>zoom</code></a>
expression reference in the Mapbox Style Specification.
*/
extern MGL_EXPORT const MGLExpressionStyleFunction MGLExpressionStyleFunctionZoomLevel;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren’t functions, they’re inputs to the expression, represented as variables. Rename MGLExpressionStyleFunction to MGLExpressionVariableName. Better yet, replace these constants with class properties on a category of NSExpression that return variable expressions. For example, the property NSExpression.mgl_zoomLevelVariableExpression would return an instance of NSExpression that was initialized using +[NSExpression expressionForVariable:] in a dispatch_once block.

@fabian-guerra fabian-guerra force-pushed the fabian-nsexpression-addons-11016 branch 2 times, most recently from 51e8894 to 0a72bff Compare March 20, 2018 15:56
@fabian-guerra fabian-guerra changed the title [ios, macos] Add NSExpression convenient constructors and helper methods. [ios, macos] Add NSExpression convenience constructors and helper methods. Apr 6, 2018
@fabian-guerra fabian-guerra force-pushed the fabian-nsexpression-addons-11016 branch 2 times, most recently from 4eb3476 to cdd2cc4 Compare April 12, 2018 17:45
Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the feedback below, the “Predicates and Expressions” guide needs to be updated to mention that some of the custom functions have corresponding category initializers on NSExpression.


@end

@interface NSExpression (MGLInitializerAdditions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this many separate categories? So far we’ve only split out separate categories due to differences in access control or because one category is automatically generated.

@param trueExpression The expression for conditions equal to true.
@param falseExpression The expression for conditions equal to false.
*/
+ (instancetype)mgl_expressionForConditional:(nonnull NSPredicate *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression NS_SWIFT_NAME(mgl_conditional(_:trueExpression:falseExpression:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with NSExpression(forConditional:true:false:), the Swift name should be init(forMGLConditional:true:false). Currently, a Swift developer is forced to write NSExpression.mgl_conditional(predicate, trueExpression: trueExpression, falseExpression: falseExpression), which violates Swift naming guidelines about redundancy.

@param from The expression which could be a constant or function expression.
@param stops The stops dictionay must be numeric literals in strictly ascending order.
*/
+ (instancetype)mgl_expressionForStepFunction:(nonnull NSExpression*)input from:(nonnull NSExpression *)from stops:(nonnull NSExpression*)stops NS_SWIFT_NAME(mgl_stepFunction(input:from:stops:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Function” here is ambiguous as to whether it refers to an expression function or a style function, which could be problematic for developers migrating from style functions. I wonder if we arrived at this name in order to align with +expressionForFunction:arguments:. The difference is that “function” in that method describes the parameter, which is the name of a function, whereas here it has nothing to do with the first parameter. If we wanted to align with that method, we’d have to call it +mgl_stepExpressionForInputExpression:minimumExpression:stops:.

The aftermarket expression function is called mgl_step:from:stops:. This initializer should be called either +mgl_expressionForSteppingExpression:minimum:stops: or +mgl_expressionForSteppingExpression:fromExpression:stops:. There may need to be a Swift name for this initializer as well, to avoid the problem in #11612.

@param parameters The parameters expression.
@param steps The steps expression.
*/
+ (instancetype)mgl_expressionForInterpolateFunction:(nonnull NSExpression*)input curveType:(nonnull MGLExpressionInterpolationMode)curveType parameters:(nullable NSExpression *)parameters steps:(nonnull NSExpression*)steps NS_SWIFT_NAME(mgl_interpolateFunction(input:curveType:parameters:steps:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stops, not steps:

+mgl_expressionForInterpolatingExpression:withCurveType:parameters:stops:

@param values The lookup values expression.
@param defaultValue The defaultValue expression to be used in case there is no match.
*/
+ (instancetype)mgl_expressionForMatchFunction:(nonnull NSExpression*)condition values:(nonnull NSExpression *)values defaultValue:(nonnull NSExpression *)defaultValue NS_SWIFT_NAME(mgl_matchFunction(condition:values:defaultValue:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s unclear to me how this method signature would produce a match expression. How would a developer specify multiple conditions? Where does the input expression go? What does the values parameter contain?

A more intuitive design would accept a dictionary mapping inputs to outputs:

+ (instancetype)mgl_expressionForMatchingKey:(NSExpression *)inputExpression inDictionary:(NSDictionary<NSExpression *, NSExpression *> *)matchedExpressions defaultExpression:(NSExpression *)defaultExpression;

+ (instancetype)mgl_expressionForMatchFunction:(nonnull NSExpression*)condition values:(nonnull NSExpression *)values defaultValue:(nonnull NSExpression *)defaultValue NS_SWIFT_NAME(mgl_matchFunction(condition:values:defaultValue:));

/**
Returns a constant expression appending the passed expression.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that both the receiver and the given expression must be strings; otherwise, an exception is raised.

<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-zoom"><code>zoom</code></a>
expression reference in the Mapbox Style Specification.
*/
+ (NSExpression *)mgl_zoomLevelVariableExpression;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be class properties, per #6778.

@fabian-guerra fabian-guerra force-pushed the fabian-nsexpression-addons-11016 branch 2 times, most recently from c29d11c to 889a413 Compare April 16, 2018 22:57
# Conflicts:
#	platform/darwin/src/NSExpression+MGLAdditions.h
#	platform/darwin/src/NSExpression+MGLAdditions.mm
#	platform/ios/app/MBXViewController.m
# Conflicts:
#	platform/darwin/src/NSExpression+MGLAdditions.h
#	platform/darwin/src/NSExpression+MGLAdditions.mm
#	platform/darwin/test/MGLExpressionTests.mm
# Conflicts:
#	platform/darwin/src/NSExpression+MGLAdditions.h
@fabian-guerra fabian-guerra force-pushed the fabian-nsexpression-addons-11016 branch from 889a413 to 417eee0 Compare April 16, 2018 23:37
@@ -9,6 +9,133 @@

NS_ASSUME_NONNULL_BEGIN

typedef NSString *MGLExpressionInterpolationMode NS_TYPED_EXTENSIBLE_ENUM;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this enumeration really should be extensible, since the interpolation modes are determined by the style specification rather than being a freeform value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this was a auto-completion problem.

@param trueExpression The expression for conditions equal to true.
@param falseExpression The expression for conditions equal to false.
*/
+ (instancetype)mgl_expressionForConditional:(nonnull NSPredicate *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression NS_SWIFT_NAME(init(forMGLConditional:trueExpression:falseExpression:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init(forMGLConditional:true:false:), not init(forMGLConditional:trueExpression:falseExpression:.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stand corrected: in Swift, the initializer is NSExpression(forConditional:trueExpression:falseExpression:), but the properties are just true and false. Go figure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per our chat this is not possible in Swift 4.1

@param steppingExpression The stepping expression.
@param fromExpression The expression which could be a constant or function expression.
@param stops The stops must be an `NSDictionary` constant `NSExpression`.
The wrapped dictionary must be numeric expression literals in strictly ascending order.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requirement exists when forming the stops in JSON because the JSON format uses an array instead of an object. But the keys of an NSDictionary are intrinsically unordered, so we should remove this line.

@param stops The stops must be an `NSDictionary` constant `NSExpression`.
The wrapped dictionary must be numeric expression literals in strictly ascending order.
*/
+ (instancetype)mgl_expressionForSteppingExpression:(nonnull NSExpression*)steppingExpression fromExpression:(nonnull NSExpression *)fromExpression stops:(nonnull NSExpression*)stops NS_SWIFT_NAME(init(forMGLStepping:from:stops:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fromExpression:(nonnull NSExpression *)minimumExpression

and stops.

@param steppingExpression The stepping expression.
@param fromExpression The expression which could be a constant or function expression.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the parameter should be minimumExpression, even though the selector piece is fromExpression:, because variable names should never begin with prepositions.

@param parameters The parameters expression.
@param stops The stops expression.
*/
+ (instancetype)mgl_expressionForInterpolatingExpression:(nonnull NSExpression*)interpolatingExpression withCurveType:(nonnull MGLExpressionInterpolationMode)curveType parameters:(nullable NSExpression *)parameters stops:(nonnull NSExpression*)stops NS_SWIFT_NAME(init(forMGLInterpolating:curveType:parameters:stops:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inputExpression, not interpolatingExpression. The idea is that this function produces an expression for interpolating an input expression named inputExpression.

@param matchedExpressions The matched values expression dictionary must be condition : value.
@param defaultExpression The defaultValue expression to be used in case there is no match.
*/
+ (instancetype)mgl_expressionForMatchingExpression:(nonnull NSExpression *)matchingExpression inDictionary:(nonnull NSDictionary<NSExpression *, NSExpression *> *)matchedExpressions defaultExpression:(nonnull NSExpression *)defaultExpression NS_SWIFT_NAME(init(forMGLMatchingKey:matched:default:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inputExpression, not matchingExpression.


@param expression The expression to append to the receiver.
system’s preferred language, if supported, specify `nil`. To use the local
language, specify a locale with the identifier `mul`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Languages have nothing to do with this method.

@param matchedExpressions The matched values expression dictionary must be condition : value.
@param defaultExpression The defaultValue expression to be used in case there is no match.
*/
+ (instancetype)mgl_expressionForMatchingExpression:(nonnull NSExpression *)matchingExpression inDictionary:(nonnull NSDictionary<NSExpression *, NSExpression *> *)matchedExpressions defaultExpression:(nonnull NSExpression *)defaultExpression NS_SWIFT_NAME(init(forMGLMatchingKey:matched:default:));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Swift name should be init(forMGLMatchingKey:in:default:), because “matched” doesn’t describe the relationship between the first and second arguments.

Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@fabian-guerra fabian-guerra merged commit cec4607 into release-boba Apr 17, 2018
@fabian-guerra fabian-guerra deleted the fabian-nsexpression-addons-11016 branch April 17, 2018 00:48
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants