Learn how to design a basic software system using classes and inheritance.
- [Instructor] Sift lets us design our software both in a protocol-oriented and an object-oriented way. It also supports generic and functional programming, and often blurs the lines between the various programming paradigms. Let's see some benefits of using POP over the classical object-oriented approach. Let's assume that we want to implement UI components which support specific animations and feature custom rounded corners. To demonstrate the problem, we'll start with the classical superclass-based approach.
Now I'm going to open up Xcode and create a playground project for this demo. Blank is good. Let's put it on the desktop. I'm going to call it SuperClassWay. Now let's remove the border plate code and also the comment. We only need the UIKit framework. All right. I'm going to create a UI view subclass. Let's call it CustomView.
It inherits from UIView. To keep it simple, we'll support only one kind of animation in this example. So let's implement the simple animation that fades out and then fades back in the view. I call it pulse. The function takes an argument called duration of type TimeInterval. It returns nothing, it just animates the view.
We're going to rely on the UIView's animate type method. First, we gradually decrease the view's alpha value until the view becomes totally transparent. So let's call the UIView's animate method. We need the one with the completion block. The duration for the first half will be only the half of the total duration. Here comes the animation. So we're going to gradually decrease the view's alpha until it becomes zero, which means it's totally transparent.
And after the first half of the animation finishes, we're going to gradually increase the view's alpha value back to one. This is going to make the view opaque again. For this, we'll call the UIView's animate method again, but first I'm going to get rid of this Bool in. We don't need it in this very example. And the code is, again, based on the animate method, but in this case we don't need the completion block, but duration should be set.
Again, it's only the half of the original value, and the animation should increase the alpha value gradually back to one. So this is our pulse function. Next, we add support for custom corners. I'm going to add a corner radius calculated property of type CGFloat. CornerRadius of type CGFloat. It has a getter which just returns the view layer's corner radius.
Return layer dot cornerRadius. And the setter, which lets corners set a new radius for the view's layer equals newValue. I set the layer's mask ToBounds property to true. Equals true. This is needed to apply the corner radius changes also to the contents of the view. The border of a view is invisible because its weight is zero by default.
So let's provide the calculated property that lets us manipulate the border weights. I'm going to call it borderWidth. Its type is CGFloat. The getter returns the layer's border widths. And the setter simply sets it equals newValue. All right, now we have our UIView subclass with support for pulse animation and custom borders.
But what if we need the same functionality for a UILabel object? We could create a custom UILabel subclass and squeeze in all the required functionality. Later, we may want to add these features to further UIView subclasses. We'd end up having the same code in as many classes. What if we need to change the implementation of the pulse method? We'd have to make those changes in all the custom classes. If we forget just one of them, that particular type will behave differently.
Duplicate code leads to maintenance issues and is always the sign of poor design and implementation. Avoid redundant code at all costs. Now, a better approach would be to create a UIView extension. Type extensions let us add new functionality to existing types. I'll go ahead and create the extension. It's a UIView extension, and I simply copy and paste the implementation from the custom UIView subclass, the pulse method and both calculated properties.
Now we can safely delete our custom class. If we instantiate any UIView subclass, it will automatically receive the methods and the properties defined in the UIView extension. Let's create a UI image view instance. I'm going to call it imageView and simply instantiate it like this. I'm not writing a frame for this example, but you get the idea.
We can involve the Pulse method and set its border widths or corner radius. imageView dot pulse, and we can provide the duration, say one second. We can also set its corner radius. Let's make it 10. Or... Its border widths. So we can use all these methods and properties because they are provided in the UIView extension.
The problem is that all UIView subclasses, like, for example, UILabel, UITestField, UIButton, and so on will automatically inherit the new features even if they don't need them. Besides, extensions don't provide a clear indication that the type has been enhanced. In other words, it is not obvious that we can use the pulse animation or the border-related properties on all of these types. Also, type extensions may become bloated.
As we keep squeezing in additional functionality, extensions keep growing, which eventually leads to the same issue that we had with superclasses. In the next example, we're going to redesign our system using a protocol-oriented approach.
- Comparing object-oriented programming with protocol-oriented programming
- Methods and class-bound protocols
- Adopting a protocol
- Declaring asynchronous behavior
- Preparing and implementing fallback logic
- Implementing an app using protocol-oriented programming