tl;dr: They probably look different because they do different things and result in different animations being added to the two views. Unfortunately I don't really know how to fix it.
What's happening behind the first version
As you have already said the first version doesn't use the API correctly. It doesn't add keyframes using addKeyframeWithRelativeStartTime:relativeDuration:animations:
but instead changes the properties directly inside the animation block. Then it creates a new "key frame animation" (you will soon see why I used scare quotes there) in the completion block.
Behind the scenes this doesn't actually result in CAKeyframeAnimations
(though I don't think you should rely on that fact). This is some logging that I did of the four animation objects that are added to the layer behind view1
in your project. (I cheated a bit and only logged the .m41
part of the transform (which corresponds to translation along x)).
BASIC
keyPath: transform
duration: 0.125
from: 300.0
model value: -10.0
timing: easeInEaseOut
BASIC
keyPath: transform
duration: 0.125
from: -10.0
model value: 5.0
timing: easeInEaseOut
BASIC
keyPath: transform
duration: 0.125
from: 5.0
model value: -2.0
timing: easeInEaseOut
BASIC
keyPath: transform
duration: 0.125
from: -2.0
model value: 0.0
timing: easeInEaseOut
As you can see each animation has a fromValue
but neither toValue
or byValue
. As you can read in the documentation, this means:
fromValue
is non-nil
. Interpolates between fromValue
and the current presentation value of the property.
What's happening behind the second version
On the other hand, the second version that correctly adds keyframes to the animation results in this single CAKeyframeAnimation to be added to the layer behind view2
. (I'm still only logging the .m41
part of the transform)
KEYFRAME
keyPath: transform
duration: 0.500
values: (
300,
-10,
5,
-2,
0
)
keyTimes: (
0.0,
0.25,
0.5,
0.75,
1.0
)
timing: easeInEaseOut
timings: (nil)
calculationMode: linear
As you can see it animates between the same values with the same times but in a single key frame animation. It also has the same total duration and uses the same timing function.
There is one thing that I'm not understanding. If I modify the real key frame animation (inside of my overridden addAnimation:forKey:
before calling super) to explicitly set the the timing function in the array of timing functions, then they do look exactly the same. That is, I do this to the keyframe animation before it is added to the layer.
CAMediaTimingFunction *function = keyFrame.timingFunction;
keyFrame.timingFunction = nil;
keyFrame.timingFunctions = @[function, function, function, function];
This trick is super ugly and you should never use it for things other than pure debugging!
So why do they look different?
Well, I guess the answer has to do with the timing functions array of the keyframe animation that UIKit created in the second case. That's pretty much the only conclusion I got.
That said, I don't know if this is a bug or how to fix it. I just know what that code in your project actually does on the Core Animation level.
No, there are definitely times where you would not want to use [unowned self]
. Sometimes you want the closure to capture self in order to make sure that it is still around by the time the closure is called.
Example: Making an asynchronous network request
If you are making an asynchronous network request you do want the closure to retain self
for when the request finishes. That object may have otherwise been deallocated but you still want to be able to handle the request finishing.
When to use unowned self
or weak self
The only time where you really want to use [unowned self]
or [weak self]
is when you would create a strong reference cycle. A strong reference cycle is when there is a loop of ownership where objects end up owning each other (maybe through a third party) and therefore they will never be deallocated because they are both ensuring that each other stick around.
In the specific case of a closure, you just need to realize that any variable that is referenced inside of it, gets "owned" by the closure. As long as the closure is around, those objects are guaranteed to be around. The only way to stop that ownership, is to do the [unowned self]
or [weak self]
. So if a class owns a closure, and that closure captures a strong reference to that class, then you have a strong reference cycle between the closure and the class. This also includes if the class owns something that owns the closure.
Specifically in the example from the video
In the example on the slide, TempNotifier
owns the closure through the onChange
member variable. If they did not declare self
as unowned
, the closure would also own self
creating a strong reference cycle.
Difference between unowned
and weak
The difference between unowned
and weak
is that weak
is declared as an Optional while unowned
is not. By declaring it weak
you get to handle the case that it might be nil inside the closure at some point. If you try to access an unowned
variable that happens to be nil, it will crash the whole program. So only use unowned
when you are positive that variable will always be around while the closure is around
Best Solution
For anyone else that is having a problem with this, if anything is interrupting the animation, the completion closure is immediately called. In my case, this was due to a slight overlap with a modal transition of the view controller that the custom segue was unwinding from. Using the
delay
portion ofUIView.animateWithDuration(0.3, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations:{}
had no effect for me. I ended up using GCD to delay animation a fraction of a second.