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

Commit 53255ab

Browse files
Robert Mooreianegordon
Robert Moore
authored and
ianegordon
committed
Return nil CAAction when swapping implementation (#109)
When swapping the implementation of `actionForKey:`, returning `NSNull` would result in a crash because it could not respond to other messages being passed (like `runAction:forKey:`). Instead, returning `nil` will safely receive additional messages. This was easily reproducible by running the unit test suite on iOS 8.1 or 8.3 simulators. Returning `nil` matches the documented behavior of `actionForKey:` in the CALayer headers: > Returns the action object associated with the event named by the > string 'event'. The default implementation searches for an action > object in the following places: > > 1. if defined, call the delegate method -actionForLayer:forKey: > 2. look in the layer's `actions' dictionary > 3. look in any `actions' dictionaries in the `style' hierarchy > 4. call +defaultActionForKey: on the layer's class > > If any of these steps results in a non-nil action object, the > following steps are ignored. If the final result is an instance of > NSNull, it is converted to `nil'. > > - (nullable id<CAAction>)actionForKey:(NSString *)event; Also fixed a crash with CASpringAnimation on iOS 8.x because the desired selectors aren't available.
1 parent fd9710d commit 53255ab

File tree

3 files changed

+45
-30
lines changed

3 files changed

+45
-30
lines changed

src/private/CABasicAnimation+MotionAnimator.m

+2-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ void MDMConfigureAnimation(CABasicAnimation *animation, MDMAnimationTraits * tra
138138
// linking against the public API on iOS 9+.
139139
#pragma clang diagnostic ignored "-Wpartial-availability"
140140
BOOL isSpringAnimation = ([animation isKindOfClass:[CASpringAnimation class]]
141-
&& [traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]);
141+
&& [traits.timingCurve isKindOfClass:[MDMSpringTimingCurve class]]
142+
&& [animation respondsToSelector:@selector(setInitialVelocity:)]);
142143
MDMSpringTimingCurve *springTimingCurve = (MDMSpringTimingCurve *)traits.timingCurve;
143144
CASpringAnimation *springAnimation = (CASpringAnimation *)animation;
144145
#pragma clang diagnostic pop

src/private/MDMBlockAnimations.m

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ @interface MDMLayerDelegate: NSObject <CALayerDelegate>
124124
// animations, we queue up the modified actions and then add them all at the end of our
125125
// MDMAnimateImplicitly invocation.
126126
[context addActionForLayer:layer keyPath:event];
127-
return [NSNull null];
127+
return nil;
128128
}
129129

130130
NSArray<MDMImplicitAction *> *MDMAnimateImplicitly(void (^work)(void)) {

tests/unit/InitialVelocityTests.swift

+42-28
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@ class InitialVelocityTests: XCTestCase {
4949

5050
XCTAssertEqual(addedAnimations.count, 3)
5151
addedAnimations.flatMap { $0 as? CASpringAnimation }.forEach { animation in
52-
XCTAssertEqual(animation.initialVelocity, 0.5,
53-
"from: \(animation.fromValue!), "
54-
+ "to: \(animation.toValue!), "
55-
+ "withVelocity: \(velocity)")
52+
if (animation.responds(to: #selector(getter: CASpringAnimation.initialVelocity))) {
53+
XCTAssertEqual(animation.initialVelocity, 0.5,
54+
"from: \(animation.fromValue!), "
55+
+ "to: \(animation.toValue!), "
56+
+ "withVelocity: \(velocity)")
57+
}
5658
}
5759
}
5860

@@ -62,10 +64,12 @@ class InitialVelocityTests: XCTestCase {
6264

6365
XCTAssertEqual(addedAnimations.count, 3)
6466
addedAnimations.flatMap { $0 as? CASpringAnimation }.forEach { animation in
65-
XCTAssertEqual(animation.initialVelocity, 0.5,
66-
"from: \(animation.fromValue!), "
67-
+ "to: \(animation.toValue!), "
68-
+ "withVelocity: \(velocity)")
67+
if (animation.responds(to: #selector(getter: CASpringAnimation.initialVelocity))) {
68+
XCTAssertEqual(animation.initialVelocity, 0.5,
69+
"from: \(animation.fromValue!), "
70+
+ "to: \(animation.toValue!), "
71+
+ "withVelocity: \(velocity)")
72+
}
6973
}
7074
}
7175

@@ -75,10 +79,12 @@ class InitialVelocityTests: XCTestCase {
7579

7680
XCTAssertEqual(addedAnimations.count, 3)
7781
addedAnimations.flatMap { $0 as? CASpringAnimation }.forEach { animation in
78-
XCTAssertGreaterThan(animation.initialVelocity, 0,
79-
"from: \(animation.fromValue!), "
80-
+ "to: \(animation.toValue!), "
81-
+ "withVelocity: \(velocity)")
82+
if (animation.responds(to: #selector(getter: CASpringAnimation.initialVelocity))) {
83+
XCTAssertGreaterThan(animation.initialVelocity, 0,
84+
"from: \(animation.fromValue!), "
85+
+ "to: \(animation.toValue!), "
86+
+ "withVelocity: \(velocity)")
87+
}
8288
}
8389
}
8490

@@ -88,10 +94,12 @@ class InitialVelocityTests: XCTestCase {
8894

8995
XCTAssertEqual(addedAnimations.count, 3)
9096
addedAnimations.flatMap { $0 as? CASpringAnimation }.forEach { animation in
91-
XCTAssertLessThan(animation.initialVelocity, 0,
92-
"from: \(animation.fromValue!), "
93-
+ "to: \(animation.toValue!), "
94-
+ "withVelocity: \(velocity)")
97+
if (animation.responds(to: #selector(getter: CASpringAnimation.initialVelocity))) {
98+
XCTAssertLessThan(animation.initialVelocity, 0,
99+
"from: \(animation.fromValue!), "
100+
+ "to: \(animation.toValue!), "
101+
+ "withVelocity: \(velocity)")
102+
}
95103
}
96104
}
97105

@@ -101,10 +109,12 @@ class InitialVelocityTests: XCTestCase {
101109

102110
XCTAssertEqual(addedAnimations.count, 3)
103111
addedAnimations.flatMap { $0 as? CASpringAnimation }.forEach { animation in
104-
XCTAssertGreaterThan(animation.initialVelocity, 0,
105-
"from: \(animation.fromValue!), "
106-
+ "to: \(animation.toValue!), "
107-
+ "withVelocity: \(velocity)")
112+
if (animation.responds(to: #selector(getter: CASpringAnimation.initialVelocity))) {
113+
XCTAssertGreaterThan(animation.initialVelocity, 0,
114+
"from: \(animation.fromValue!), "
115+
+ "to: \(animation.toValue!), "
116+
+ "withVelocity: \(velocity)")
117+
}
108118
}
109119
}
110120

@@ -114,10 +124,12 @@ class InitialVelocityTests: XCTestCase {
114124

115125
XCTAssertEqual(addedAnimations.count, 3)
116126
addedAnimations.flatMap { $0 as? CASpringAnimation }.forEach { animation in
117-
XCTAssertLessThan(animation.initialVelocity, 0,
118-
"from: \(animation.fromValue!), "
119-
+ "to: \(animation.toValue!), "
120-
+ "withVelocity: \(velocity)")
127+
if (animation.responds(to: #selector(getter: CASpringAnimation.settlingDuration))) {
128+
XCTAssertLessThan(animation.initialVelocity, 0,
129+
"from: \(animation.fromValue!), "
130+
+ "to: \(animation.toValue!), "
131+
+ "withVelocity: \(velocity)")
132+
}
121133
}
122134
}
123135

@@ -127,10 +139,12 @@ class InitialVelocityTests: XCTestCase {
127139

128140
XCTAssertEqual(addedAnimations.count, 3)
129141
addedAnimations.flatMap { $0 as? CASpringAnimation }.forEach { animation in
130-
XCTAssertEqual(animation.duration, animation.settlingDuration,
131-
"from: \(animation.fromValue!), "
132-
+ "to: \(animation.toValue!), "
133-
+ "withVelocity: \(velocity)")
142+
if (animation.responds(to: #selector(getter: CASpringAnimation.settlingDuration))) {
143+
XCTAssertEqual(animation.duration, animation.settlingDuration,
144+
"from: \(animation.fromValue!), "
145+
+ "to: \(animation.toValue!), "
146+
+ "withVelocity: \(velocity)")
147+
}
134148
}
135149
}
136150

0 commit comments

Comments
 (0)