神刀安全网

Introduction to Protocol-Oriented MVVM

About the Speaker: Natasha Murashev

Natasha is secretly a robot that loves to learn about Swift and iOS development. She previously worked as a senior iOS Engineer at Capital One in San Francisco, but now she travels around, writing about her experiences with new technologies. In her free time, she works on personal projects, speaks at meetups and conferences, contributes to open source, and likes to cross things off her bucket list.

@natashatherobot Website

Why Simplify Code in Swift?

Hi, I’m Natasha. I’m @NatashaTheRobot on Twitter. A little bit about me: I have a weekly Swift newsletter , I blog a lot about Swift, and I’ve been working with Swift pretty much since it came out from day one.

As an Objective-C developer, I basically started writing Objective-C code in Swift when it came out – I used reference types. I would start with making everything a class, since I was used to object-oriented programming. That’s what Objective-C is about.

I was feeling still good about myself because I sometimes used enums. They were more complex than a normal enum, which made me feel good. However, I started going to events and listening to talks, and my mind was blown by Andy Matuschak’s talk on controlling complexity in Swift. He talked about using value types. Until that point, I knew there were structs in Swift, but as an Objective-C developer going to Swift, it was natural for me to start with classes.

After seeing this talk, I was impressed and felt the need to use value types for everything. In fact, he showed that almost all the Swift standard libraries use value types, and that the language creators themselves are using them. I went to my work and created a new file that I would start as a value type.

Initially, I had the great feeling of “I’m a Swift developer! I’m using a struct!”, but then I had to subclass and it felt horrible, but I didn’t really know how to go beyond that until…

Protocol-Oriented Programming

At this year’s WWDC, there was this incredible talk on protocol-oriented programming in Swift where we met Crusty. There, they explained how to use protocols instead of subclassing. If you haven’t seen this talk and you’re switching to Swift, I think this is the most important talk to watch from WWDC last year.

In this talk, Dave Abrahams , Apple’s tech lead for the Swift standard library says:

“Swift is a protocol-oriented programming language.”

In the video, his title was “Professor of Blowing Your Mind,” and he definitely blew my mind, as well as the minds of everyone in the audience.

It’s not a completely new concept to us: we’ve seen a lot of protocols that Apple uses, such as a table view, and we can all agree that that’s a cool design pattern, because none of us want to be subclassing from a UITableViewController all the time. Instead, you can use protocols to tell Apple how many table view cells you need. We already know the power of this design pattern, but now we have to take it to the next level ourselves.

For me, I was very excited. I wanted to go back and go deeper and be that protocol-oriented programmer. I went back to work with all that excitement. In my work, I have the codebase that I’m already using, with certain patterns that are already established. It’s hard to simply do something new or to understand how you should use it. I want to use it, but I feel confined to my existing project and I’m not sure how to move forward beyond what I already know and do.

I still kept protocols in the back of my mind, thinking “how do I incorporate those into my code?” It’s something that always stuck in my mind, but I didn’t quite know how to do it. Then, I went to sleep one night. I highly recommend it, despite the programmer reputation for being extreme and never sleeping. For me, sleeping is the key to solve many of my problems, most likely because of how our brain processes information.

One night, I woke up and everything connected. I thought of at least one use case where I can apply it in my code at work that I am excited about. That use case was using MVVM.

For those of you who are not as familiar, definitely go read the blog posts by Ash Furrow, “MVVM in Swift.” There’s also one on objc.io called “Intro to MVVM.” I’ll go through a quick example so hopefully you can see how it works.

I used to work in a bank. Let’s say you have your model, which would have some raw data about your account balances. On the model layer, you want to keep that value as a raw NSDecimalNumber .

let amount = 6729383.99

When you display that same number to the user, you want it to be something pretty, like “Hey, your balance is this,” and you want to have dollar signs and formatting in your view:

Your balance is $6,729,383.99

Many people put that code in the view controller. That’s what creates massive View Controllers that are hard to test. Alternatively, you can put it in a model and have a dirty model, where you have all this formatting code in it.

Instead, you can keep you model very clear and mirror your raw data. This is where your view model comes in:

struct AccountViewModel {     let displayBalance: String       init(mode: BankAccount) {         let formattedBalance = model.balance.currencyValue         displayBalance = "Your balance is /(formattedBalance)"     } }

Your view model will actually take in your model, and it can format the information that’s going to be displayed on your view. That’s the magic of view models. This is very testable. You can put in the model with the account information and you’re testing display, whereas before, if you try to test your view controllers or your views, it’s harder because the output is not as clean.

Notice that my view model is a value type. How does this work in Swift?

The key is that your view controller should keep the latest version of the view model. A value type is a data type. It shouldn’t really react, it’s just a copy of data, at that point of time. Your view controller will be the one that keeps track of which copy of data should be displayed to the user (the most current copy).

The way to think about it, as in Andy Matuschak’s talk, is the example of the zoetrope . (There’s a really magical one in the Ghibli Museum in Japan.

The key here is that each frame of the zoetrope is a static value. You can encode a character by changing how far his hand is raised or how back is the head tilted. Each version is very static, but when you put them all together and look at one center, there’s a new data value all the time, so you have a beautiful, alive image.

You could approach value types the same way. Your view controller is what’s keeping track of what is the latest frame of the zoetrope – the latest piece of data that’s active, that’s being shown to the user. As soon as your model is updated, there’s new data, so you would calculate a new view model. Now, your view would be updated with the newest information.

var viewModel = ViewModel(model: Account)

The Ugliness Before Protocols

Now we get to the exciting part. I’ll walk through a very simple example. In a table view, like a settings screen that most apps have, imagine I have only one setting: a slider to turn the whole app yellow.

This is something that should be very simple, but it can get complicated. Here’s the problem: in our table view cell, every single component in it needs to be formatted in some way. If you have a label, you have to declare the font, the font color, the font size, etc. If you have a switch, what happens when the switch turns on? Should the initial state be on or off? For a very simple table view cell with two elements, I already have six different ways to configure it:

class SwitchWithTextTableViewCell: UITableViewCell {     func configure(         title: String,         titleFont: UIFont,         titleColor: UIColor,         switchOn: Bool,         switchColor: UIColor = .purpleColor(),         onSwitchToggleHandler: onSwitchTogglerHandlerType? = nil)     {         // Configure views here     } }

You can imagine that most of us work with more complex table view cells. In my code, this configure method would be really massive. Adding a subtitle will add three new properties to set. It does help in Swift that you can use default values, but it’s still not clean with a massive configure method.

In the view controller where you’re actually calling this method, it has stacks of all the information that should be there. It doesn’t look very pretty; it doesn’t feel good, but it was kind of the best I could come up with until protocols.

View Models & Protocols

Instead of having that huge configure method, you can take out every part of it and put it into a SwitchWithTextCellProtocol for that cell. That got me really excited. This way, I can have my view model conform to this protocol, and set all these properties right there. Now, I don’t have to have this huge configure method, but I still have a way to ensure that every single property is actually set.

protocol SwitchWithTextCellProtocol {     var title: String { get }     var titleFont: UIFont { get }     var titleColor: UIColor { get }          var switchOn: Bool { get }     var switchColor: UIColor { get }      func onSwitchToggleOn(on: Bool) }

With protocol extensions in Swift 2.0, you can do the same thing with default values. If there is a certain color that’s relevant for most cells, you can just extend it and set that color. Any view model that implements this doesn’t have to set the color anymore. That’s really nice:

extension SwitchWithTextCellProtocol {     var switchColor: UIColor {         return .purpleColor()     } }

Now, my configure method just takes in something that conforms to this protocol, and that’s it:

class SwitchWithTextTableViewCell: UITableViewCell {     func configure(withDelegate delegate: SwitchWithTextCellProtocol)     {         // Configure views here     } }

It only has one argument, which is great improvement from having six or more arguments. This is an example of my view model now:

struct MinionModeViewModel: SwitchWithTextCellProtocol {     var title = "Minion Mode!!!"     var switchOn = true          var switchColor: UIColor {         return .yellowColor()     }          func onSwitchToggleOn(on: Bool) {         if on {             print("The Minions are here to stay!")         } else {             print("The Minions went out to play!")         }     } }

It conforms to this protocol and sets everything up. As you’ve seen in an earlier example, you can instantiate your view model with your model object. Now, if you need that information, like currency balance, you can actually use that information throughout your view model for figuring out how to configure it to display to the view.

So, this is pretty simple. Now, my cellForRowAtIndexPath() , is really simple as well:

// YourViewController.swift let cell = tableView.dequeueReusableCellWithIdentifier("SwitchWithTextTableViewCell", forIndexPath: indexPath) as! SwitchWithTextTableViewCell  // This is where the magic happens! cell.configure(withDelegate: MinionModeViewModel())  return cell

I dequeue my cell, and I call my configure method with my view model. In this case, it doesn’t have that framing; it doesn’t have that model layer, but you can also put the model to keep track of it at the view controller level. You’re passing in that view model, and your cell is generated. After our refactor, it’s all only three lines of code.

At this point, I’m pretty happy with myself. I took this big configure method with six arguments, and instead put it up as a protocol. I found a use case for protocols that makes my code nicer, cleaner, with extracted logic.

What I typically do next is blog about it. I like blogging just for learning, so whenever I learn something new or discover something, I blog about it. I blogged about this, and someone posted a comment saying, “what about creating two protocols? One for the data source of the actual encoded information, like what the title is, of the cell – the actual data.” Versus things like colors and fonts, that information should be separate, because it’s more about formatting versus actual information, like critical data information, and this is the pattern we already see Apple using in UITableViewCells or collection views.

I thought this was an amazing idea. I separated my logic, and then created my cell data storage and a cell delegate:

protocol SwitchWithTextCellDataSource {     var title: String { get }     var switchOn: Bool { get } }  protocol SwitchWithTextCellDelegate {     func onSwitchToggleOn(on: Bool)          var switchColor: UIColor { get }     var textColor: UIColor { get }     var font: UIFont { get } }

Then, I put my configure method with the data storage, so I take a delegate. Since the delegate could be all configured in the protocol extension with defaults, like the fonts and colors, I could theoretically not even like pass something in; I can just create one right there, if it’s there:

// SwitchWithTextTableViewCell func configure(withDataSource dataSource: SwitchWithTextCellDataSource, delegate: SwitchWithTextCellDelegate?) {     // Configure views here }

I can now improve my view model with extensions. I’ll have one cell block that conforms to the data source and gives that raw information translated into the view:

struct MinionModeViewModel: SwitchWithTextCellDataSource {     var title = "Minion Mode!!!"     var switchOn = true }

Then, I’ll have the delegate which will take stuff like fonts and colors and deal with it there, in a separate part of the view model:

extension MinionModeViewModel: SwitchWithTextCellDelegate {     var switchColor: UIColor {         return .yellowColor()     }          func onSwitchToggleOn(on: Bool) {         if on {             print("The Minions are here to stay!")         } else {             print("The Minions went out to play!")         }     } }

Finally, my table view cell is pretty simple:

// SettingsViewController  let viewModel = MinionModeViewModel() cell.configure(withDataSource: viewModel, delegate: viewModel) return cell

I just create the view model, pass it into my configure method and return the cell.

Mixins and Traits in Swift 2.0

I’m quite satisfied at this point. I created protocols, simplified my code, blogged about it, and learned. Then, I read a really amazing article that everyone should read: Mixins and Traits in Swift 2.0 by @mhollemans . Matthijs goes through game development, and even though I’m not experienced with that, we can all relate to the underlying concepts he goes over.

In game development, there is usually a strong hierarchy and a lot of subclassing. Something like a monster will have many different types of monsters. Subclassing in this view makes a lot of sense. But, then, things can start getting messy.

Introduction to Protocol-Oriented MVVM

With this type of hierarchy, things start out okay. Eventually, though, you get into situations where you have a monster that can shoot, but then a castle can also shoot because there can be cannons on top of the castle, so now you have to extract this “shooting helper.” Things like that are the default behavior that you think is singular when you’re creating all that subclassing, but it starts becoming messy and you end up with spaghetti code.

Matthijs refactors this code so that instead of having the logic of what can shoot or what can heal on the subclassed object, he extracts it into protocols through protocol extensions.

This makes the code look much cleaner and easier to comprehend. For example:

class ZapMonster: GameObject, GunTrait, RenderTrait, HealthTrait, MovementTrait {     ... }

Just by looking at the object’s type, I can immediately understand a lot of what’s going on with the object without ever seeing the implementation. I personally just fell in love with this pattern.

Applying Mixins to Our App

Even though this is game development, I can actually apply this to my own code with table view cells. Instead of tying my protocols to the actual cell, I can tie it to a more generic TextPresentable . This way, any view that has a label, not just cells, can conform to the text presentable protocol. This is where I can say that this text should have, this is the text, that’s the color, that’s the font:

protocol TextPresentable {     var text: String { get }     var textColor: UIColor { get }     var font: UIFont { get } }  protocol SwitchPresentable {     var switchOn: Bool { get }     var switchColor: UIColor { get }          func onSwitchToggleOn(on: Bool) }

The switch will have its own protocol as to how it should be configured fully. You can immediately imagine this expanding to the game development example: now you need an image, now you have an ImagePresentable protocol, now you have a TextFieldPresentable protocol where you do all the configuring with that:

protocol ImagePresentable {     var imageName: String { get } }  protocol TextFieldPresentable {     var placeholder: String { get }     var text: String { get }          func onTextFieldDidEndEditing(textField: UITextField) }

With protocol extensions, you can configure all the fonts and colors, and any single view that conforms to this TextPresentable protocol will have a default configuration for the label, and, most likely, your labels across the app are very similar:

extension TextPresentable {          var textColor: UIColor {         return .blackColor()     }          var font: UIFont {         return .systemFontOfSize(17)     }      }

You can even go further and create different types of labels, like a header label. Maybe it has a certain fonts and colors, and that means you can now reuse this over and over again throughout your app. It’s really easy to change as soon as your designer comes and asks that all the headers be blue today. You go to this protocol extension, change it to blue, and now, every single view that has this HeaderTextPresentable will immediately change with just one line of code.

I fell in love with this pattern. This is my cell now:

class SwitchWithTextTableViewCell<T where T: TextPresentable, T: SwitchPresentable>: UITableViewCell {     private var delegate: T?          func configure(withDelegate delegate: T) {         // Configure views here     } }

In this case, it’s not conforming to these protocols, but it’s expecting something that conforms to these protocols, so it’s using some generics. The cell is expecting a delegate that conforms to the TextPresentableProtocol and the SwitchPresentableProtocol . The configure method thus doesn’t care what that object is. In our case, it’s going to be a view model, but, it just wants something passed in that conforms to those protocols and now, you can configure everything in the cell based on that information.

extension MinionModeViewModel: TextPresentable {     var text: String { return "Minion Mode" }     var textColor: UIColor { return .blackColor() }     var font: UIFont { return .systemFontOfSize(17.0) } }

Our view model is going to have that TextPresentable block of code where you configure the text, color, fonts, and anything that’s a default in the protocol extensions, you don’t have to even implement on the view models.

The same goes for SwitchPresentable . Should the switch be on or off? What should happen when the switch is on? Here, you’re talking about small parts of the view:

extension MinionModeViewModel: Switch Presentable {     var switchOn: Bool { return false }      var switchColor: UIColor { return .yellowColor() }         func onSwitchToggleOn(on: Bool) {         if on {             print("The Minions are here to stay!")         } else {             print("The Minions went out to play!")         }     } }

Lastly, the code in the view controller is very simple: you’re just dequeuing the cell. You configure it with the view model, and you return the cell. The one key thing is because we are using generics, it does have to specify what that T is, and in our case the T is the view model.

Swift: An Evolving Language

At this point, I’m ecstatic. I went through three different iterations of all of this. Nevertheless, Swift is a new language, and it’s only been out for less than two years. It’s changing a lot, and we as a community have to decide what are the best practices.

One thing I always think about when I discover or come up with new patterns in my code is how easy is it to make changes. That’s something I really believe in:

Change is the only constant.

This particularly applies to coding. I usually end up rewriting each app over the course of a year, because you end up needing to change many things; you add tests, or you redesign for iOS 7. Sometimes Apple comes out with new stuff, or you need to delete or add features; everything changes constantly.

As such, I always work under the assumption that my code will be changed. For long term product development, I have to be thinking about which patterns I’m using that would easily allow for that change to happen, versus having a single god class that falls apart with tiny changes. To me, this pattern is amazing, because it does allow for quick change.

Suppose my product manager comes and says, “hey, for this cell, I want that to have an image.” Initially, we just had the label and the switch, but now there’s an image as well. So, I say that my cell has to expect something that also conforms the the ImagePresentableProtocol and, that’s all I do in the cell layer.

I also have to update my configure method to actually use the image, but that takes like two lines of code. Lastly, I just extend my view model.

extension MinionModeViewModel: ImagePresentable {     var imageName: String { return "minionParty.png" } }

Natasha the Happy Robot: Summary

The robot in me is very happy at this point. These are the things I’ve discussed here, using protocols for the MVVM pattern.

  • Use protocols to configure your views.
  • Use protocol extensions for defaults – that’s where you set all the fonts and colors and configuration that’s used across your app. This was likely the biggest hurdle to subclassing; that’s why people usually subclass – to have commonalities across multiple classes.
  • Use view models to provide the data for the protocols. The reason you do view models is because they’re easily tested, and not changing.Your view controller can decide which view model version it’s using as the latest for the code.

The main takeaway is that next year at WWDC, they can come up with a brand new cool pattern in Swift, or someone can give me a cool new idea in a tweet right after this talk. When working with Swift, I’d recommend that you all keep an open mind. Think about how you can make your code better.

Listen to your co-workers. Listen to your community. There are always ways you can improve, and I’m sure I can improve with this. At this point, I’m happy, but I could read something next week that changes my mind or teaches me something new.

The cool thing about Swift is that we all come from different experiences. Your co-workers may be coming from a functional programming language, or Ruby, or .NET – they’re going to have different ideas than you. Since Swift is an evolving language, that’s where you get to be open minded and learn from others. This will improve your patterns and help you figure out what is the best pattern. I think you can always improve and get better, share your findings, and repeat the same cycle.

See the discussion on Hacker News .

Get new videos & tutorials — we won’t email you for any other reason, ever.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Introduction to Protocol-Oriented MVVM

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮