1067 lines
80 KiB
XML
1067 lines
80 KiB
XML
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
|
||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
|
||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
|
||
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
|
||
>
|
||
|
||
<channel>
|
||
<title>Natasha The Robot</title>
|
||
<atom:link href="https://www.natashatherobot.com/feed/" rel="self" type="application/rss+xml" />
|
||
<link>https://www.natashatherobot.com</link>
|
||
<description></description>
|
||
<lastBuildDate>Fri, 07 Jul 2017 11:06:10 +0000</lastBuildDate>
|
||
<language>en-US</language>
|
||
<sy:updatePeriod>hourly</sy:updatePeriod>
|
||
<sy:updateFrequency>1</sy:updateFrequency>
|
||
<generator>https://wordpress.org/?v=4.7.2</generator>
|
||
<site xmlns="com-wordpress:feed-additions:1">42122868</site> <item>
|
||
<title>The Easiest Way to Get a URL for your Apple Wallet Passkit Pass</title>
|
||
<link>https://www.natashatherobot.com/url-apple-wallet-passkit-pass/</link>
|
||
<comments>https://www.natashatherobot.com/url-apple-wallet-passkit-pass/#comments</comments>
|
||
<pubDate>Fri, 07 Jul 2017 11:06:10 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[iPhone]]></category>
|
||
<category><![CDATA[event]]></category>
|
||
<category><![CDATA[ios]]></category>
|
||
<category><![CDATA[passkit]]></category>
|
||
<category><![CDATA[passkit pass]]></category>
|
||
<category><![CDATA[wallet]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6602</guid>
|
||
<description><![CDATA[For try! Swift NYC, we are playing with the idea of adding QR codes to conference badges that will add attendee’s contact information business card to your Wallet app...]]></description>
|
||
<content:encoded><![CDATA[<p>For <a href="https://www.tryswift.co/events/2017/nyc/">try! Swift NYC</a>, we are playing with the idea of adding QR codes to conference badges that will add attendee’s contact information business card to your Wallet app. It is easy to generate a QR code from a URL. And it is relatively easy to <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/PassKit_PG/YourFirst.html#//apple_ref/doc/uid/TP40012195-CH2-SW1">create a PassKit Pass</a> – it’s basically just a bunch of files in a folder. But I was super confused on that step in-between – how do I create a link from the final Passkit <strong>.pkpass</strong> file…</p>
|
||
<p>The <a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/PassKit_PG/DistributingPasses.html#//apple_ref/doc/uid/TP40012195-CH11-SW1">Distributing Passes instructions</a> include the following information about this:</p>
|
||
<p align="center"><img class="alignnone wp-image-6604" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.27.33-PM.png" alt="" width="503" height="88" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.27.33-PM.png 1312w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.27.33-PM-300x53.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.27.33-PM-768x135.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.27.33-PM-1024x180.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.27.33-PM-140x25.png 140w" sizes="(max-width: 503px) 100vw, 503px" /></p>
|
||
<p>So what the hell is a MIME type?!!! I have no idea. But googling around for it, I learned that I couldn’t set it for my jekyll website. I needed another solution!</p>
|
||
<p>So after the initial panic and confusion, I took a breath and stepped back. I realized that <strong>.pkpass</strong> is simply a file. All that was needed was a link to this file (and that MIME type thing, but I chose to ignore it at that moment). Immediately, my thoughts went to Amazon Services, with <a href="https://aws.amazon.com/s3/">S3</a> as the perfect file storage solution.</p>
|
||
<p>So I signed-in and created the <em>tryswiftpasskit</em> bucket. I set the location to US East (N. Virginia), since the conference would take place in New York, so that’s the closest.</p>
|
||
<p align="center"><img class="alignnone wp-image-6605" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.35.13-PM.png" alt="" width="495" height="426" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.35.13-PM.png 1384w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.35.13-PM-300x258.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.35.13-PM-768x660.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.35.13-PM-1024x880.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.35.13-PM-140x120.png 140w" sizes="(max-width: 495px) 100vw, 495px" /></p>
|
||
<p>I went through the rest of the bucket creation process and finished. I then went inside my bucket and clicked the <strong>Upload</strong> button. I downloaded the <a href="https://developer.apple.com/services-account/download?path=/iOS/Wallet_Support_Materials/WalletCompanionFiles.zip" target="_blank">Sample Passes</a> listed in their tutorial, so I had one ready to test!</p>
|
||
<p align="center"><img src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-07-07-at-12.42.26-PM.png" alt="" width="495" height="419" /></p>
|
||
<p>I set the permissions to public, since the link has to be public in order to get the .pkpass file to download into the Wallet app:</p>
|
||
<p align="center"><img src="https://www.natashatherobot.com/wp-content/uploads/S3_Management_Console.png" alt="" width="495" height="424" /></p>
|
||
<p>And now the magical part… remember that whole <strong>MIME type</strong> thing?!! From googling around, I figured out enough to set is as the Content-Type property on the file:</p>
|
||
<p align="center"><img src="https://www.natashatherobot.com/wp-content/uploads/S3_Management_Console-1.png" alt="" width="495" height="430" /></p>
|
||
<p>It should be set to <strong>application/vnd.apple.pkpass</strong>. Save and finish and you’re ready to go!</p>
|
||
<p>The final step is to get that link. Click into the object in your bucket, and you’ll see it right there for you:</p>
|
||
<p align="center"><img src="https://www.natashatherobot.com/wp-content/uploads/S3_Management_Console-2.png" alt="" width="495" height="568" /></p>
|
||
<p>That’s it! Open that link in Safari or via the Mail app (or <a href="http://www.qr-code-generator.com/" target="_blank">generate a QR Code</a>) and you’ll see the pass <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f483.png" alt="💃" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f3fb.png" alt="🏻" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
|
||
<p align="center"><img src="https://www.natashatherobot.com/wp-content/uploads/IMG_6627.png" alt="" width="495" height="879" /></p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/url-apple-wallet-passkit-pass/feed/</wfw:commentRss>
|
||
<slash:comments>6</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6602</post-id> </item>
|
||
<item>
|
||
<title>Swift: Alternative to Default Implementations in Protocols</title>
|
||
<link>https://www.natashatherobot.com/swift-alternative-to-default-implementations-in-protocols/</link>
|
||
<comments>https://www.natashatherobot.com/swift-alternative-to-default-implementations-in-protocols/#comments</comments>
|
||
<pubDate>Sun, 26 Mar 2017 23:51:49 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[Swift]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6592</guid>
|
||
<description><![CDATA[I recently wrote about why I prefer not to use default implementations in Swift protocols. Here is how you can still optimize using a default implementation without the unintended side-effects...]]></description>
|
||
<content:encoded><![CDATA[<p>I recently wrote about <a href="https://www.natashatherobot.com/swift-use-default-implementations-protocols/" target="_blank">why I prefer not to use default implementations in Swift protocols</a>.</p>
|
||
<p><strong>TL;DR</strong></p>
|
||
<ul>
|
||
<li>I want a lot of conscious thought put in into each method of the protocol – adding a default implementation will make it easy to forget and not think about much. Oh, and the compiler won’t complain!</li>
|
||
<li>I want it to be easy to make changes in the implementation. If a method is not included in the file because the implementation is in the protocol, it’s more work to make the decision to add that into the file. The assumption is that the default should win. If it’s already there, it’s easier to just go in and make a small change.</li>
|
||
<li>Similar to the above point, I want it to be super readable where each method is coming from. If a bunch of methods are in the default implementation of the protocol, it’s not as clear just by looking at the file.</li>
|
||
</ul>
|
||
<p>But there might still be repeating or “default” implementations that are shared across protocol implementations. In my case, I approached this issue by creating an object with some variables that could be shared across protocols:</p><pre class="crayon-plain-tag">struct SessionDataDefaults {
|
||
|
||
let title: String
|
||
let subtitle = "try! Conference"
|
||
let logoImageURL = Bundle.trySwiftAssetURL(for: "Logo.png")!
|
||
let imageURL: URL?
|
||
let location: String
|
||
let summary = Conference.current.localizedDescription
|
||
let twitter = Conference.current.twitter!
|
||
|
||
init(session: Session) {
|
||
title = session.localizedString(for: session.title ?? "TBD", japaneseString: session.titleJP)
|
||
|
||
if let url = session.imageWebURL {
|
||
imageURL = URL(string: url)
|
||
} else if let assetName = session.imageAssetName {
|
||
imageURL = Bundle.trySwiftAssetURL(for: assetName)
|
||
} else {
|
||
imageURL = nil
|
||
}
|
||
|
||
if let location = session.location {
|
||
self.location = location.localizedName
|
||
} else {
|
||
self.location = Venue.localizedName(for: .conference)
|
||
}
|
||
}
|
||
}</pre><p>However, when I wrote the article about it, <a href="https://twitter.com/jckarter" target="_blank">@jckarter</a> tweeted the following:</p>
|
||
<blockquote class="twitter-tweet" data-width="500">
|
||
<p lang="en" dir="ltr"><a href="https://twitter.com/NatashaTheRobot">@NatashaTheRobot</a> Another approach: put the defaults on a separate protocol you conform to when you intentionally want them on your type</p>
|
||
<p>— Joe Groff (@jckarter) <a href="https://twitter.com/jckarter/status/845642396230606848">March 25, 2017</a></p></blockquote>
|
||
<p><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p>
|
||
<p>In my case, it didn’t make sense to create a separate protocol with default implementation because the use of the default values is pretty random – there isn’t any type of session that will use all defaults at the same time (this is one reason I didn’t want to include a default implementation in the first place).</p>
|
||
<p>But this did spark another idea! I could make my data defaults object conform to the same protocol as the objects where the defaults are being used – in my case, <strong>SessionDisplayable</strong>:</p><pre class="crayon-plain-tag">struct SessionDataDefaults: SessionDisplayable {
|
||
|
||
fileprivate let session: Session
|
||
|
||
init(session: Session) {
|
||
self.session = session
|
||
}
|
||
|
||
var title: String {
|
||
return session.localizedString(for: session.title ?? "TBD", japaneseString: session.titleJP)
|
||
}
|
||
|
||
var subtitle: String {
|
||
return Conference.current.name ?? "try! Conference"
|
||
}
|
||
|
||
|
||
var logoURL: URL {
|
||
return Conference.current.logoURL
|
||
}
|
||
|
||
|
||
var location: String {
|
||
if let location = session.location {
|
||
return location.localizedName
|
||
} else {
|
||
return Venue.localizedName(for: .conference)
|
||
}
|
||
}
|
||
|
||
var sessionDescription: String {
|
||
return "❤️".localized()
|
||
}
|
||
|
||
var presentationSummary: String {
|
||
return Conference.current.localizedDescription
|
||
}
|
||
|
||
var twitter: String {
|
||
return Conference.current.twitter!
|
||
}
|
||
|
||
var selectable: Bool {
|
||
return false
|
||
}
|
||
}</pre><p></p>
|
||
<p>The best part about this is that when a method needs to use the default implementation, it can use the defaults method that has the same exact name as the method being implemented!</p>
|
||
<p>Here is an example of another <strong>SessionDisplayable</strong> using the new defaults: </p>
|
||
<p></p><pre class="crayon-plain-tag">struct CoffeeBreakSessionViewModel: SessionDisplayable {
|
||
|
||
private let session: Session
|
||
private let dataDefaults: SessionDataDefaults
|
||
|
||
init?(_ session: Session) {
|
||
if session.type == .coffeeBreak {
|
||
self.session = session
|
||
self.dataDefaults = SessionDataDefaults(session: session)
|
||
} else {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
var title: String {
|
||
if let sponsor = session.sponsor {
|
||
return String(format: "Coffee Break, by %@".localized(), sponsor.name)
|
||
}
|
||
return "Coffee Break".localized()
|
||
}
|
||
|
||
var subtitle: String {
|
||
if let sponsor = session.sponsor {
|
||
return sponsor.localizedName
|
||
}
|
||
// Matching variable name in the defaults
|
||
// makes it super easy to use!!
|
||
return dataDefaults.subtitle
|
||
}
|
||
|
||
var logoURL: URL {
|
||
if let imageURL = dataDefaults.imageURL {
|
||
return imageURL
|
||
}
|
||
|
||
if let sponsor = session.sponsor {
|
||
return sponsor.logoURL
|
||
}
|
||
|
||
// Matching variable name in the defaults
|
||
return dataDefaults.logoURL
|
||
}
|
||
|
||
var location: String {
|
||
// Matching variable name in the defaults
|
||
return dataDefaults.location
|
||
}
|
||
|
||
var sessionDescription: String {
|
||
return "❤️".localized()
|
||
}
|
||
|
||
var presentationSummary: String {
|
||
// Matching variable name in the defaults
|
||
return dataDefaults.presentationSummary
|
||
}
|
||
|
||
var selectable: Bool {
|
||
return session.sponsor != nil
|
||
}
|
||
|
||
var twitter: String {
|
||
let twitter = session.sponsor?.twitter ?? dataDefaults.twitter
|
||
return "@\(twitter)"
|
||
}
|
||
}</pre><p></p>
|
||
<p>I’m very happy with this solution. It’s super easy to use the defaults, but still allows for the compiler to complain when a protocol method is not implemented. </p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/swift-alternative-to-default-implementations-in-protocols/feed/</wfw:commentRss>
|
||
<slash:comments>3</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6592</post-id> </item>
|
||
<item>
|
||
<title>Swift: Why You Shouldn’t Use Default Implementations in Protocols</title>
|
||
<link>https://www.natashatherobot.com/swift-use-default-implementations-protocols/</link>
|
||
<comments>https://www.natashatherobot.com/swift-use-default-implementations-protocols/#comments</comments>
|
||
<pubDate>Fri, 24 Mar 2017 13:19:34 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[Swift]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6578</guid>
|
||
<description><![CDATA[Why I decided against a nice refactor... ]]></description>
|
||
<content:encoded><![CDATA[<p>I’m currently doing a very <a href="https://github.com/tryswift/trySwiftData/commit/03273badffb83c39a5d21dce4bd31b9094c8f81e" target="_blank">big refactor</a> of try! Swift Data now that the Tokyo conference is over and I have time to repay some technical debt. As part of the refactor, I’m removing a bunch of large switch statements for view-level display data and putting them into individual view models that conform to a strict protocol.</p>
|
||
<h2>The Setup</h2>
|
||
<p>The <a href="https://github.com/tryswift/trySwiftAppFinal" target="_blank">conference app</a> has different sessions – talks, breakfast, lunch, announcements, etc. They are all displayed in a Table View with a title, subtitle, location, etc. Before, the data layer was messy like this:</p><pre class="crayon-plain-tag">// Session.swift
|
||
|
||
import RealmSwift
|
||
import Foundation
|
||
|
||
@objc public enum SessionType: Int {
|
||
case workshop
|
||
case meetup
|
||
case breakfast
|
||
case announcement
|
||
case talk
|
||
case lightningTalk
|
||
case sponsoredDemo
|
||
case coffeeBreak
|
||
case lunch
|
||
case officeHours
|
||
case party
|
||
}
|
||
|
||
public class Session: Object {
|
||
/** The type of content in this particular session */
|
||
open dynamic var type: SessionType = .talk
|
||
|
||
// many other class properties here
|
||
|
||
|
||
/***************************************************/
|
||
|
||
// VIEW DISPLAY LOGIC BELOW
|
||
|
||
/** The main name of this session */
|
||
public var formattedTitle: String? {
|
||
switch self.type {
|
||
// VERY LONG SWITCH STATEMENT
|
||
// LOTS OF DISPLAY LOGIC
|
||
}
|
||
}
|
||
|
||
/** A follow-up tagline for the session */
|
||
public var formattedSubtitle: String? {
|
||
switch self.type {
|
||
// VERY LONG SWITCH STATEMENT
|
||
// LOTS OF DISPLAY LOGIC
|
||
}
|
||
}
|
||
|
||
/** What image, if any is available for this session */
|
||
public var logoURL: URL {
|
||
switch self.type {
|
||
// VERY LONG SWITCH STATEMENT
|
||
// LOTS OF DISPLAY LOGIC
|
||
}
|
||
}
|
||
|
||
/** The location for where this session will occur */
|
||
public var formattedLocation: String {
|
||
switch self.type {
|
||
// VERY LONG SWITCH STATEMENT
|
||
// LOTS OF DISPLAY LOGIC
|
||
}
|
||
}
|
||
|
||
/** A long-form description of the session */
|
||
public var sessionDescription: String {
|
||
switch self.type {
|
||
// VERY LONG SWITCH STATEMENT
|
||
// LOTS OF DISPLAY LOGIC
|
||
}
|
||
}
|
||
|
||
/** Presentation Summary */
|
||
public var presentationSummary: String {
|
||
switch self.type {
|
||
// VERY LONG SWITCH STATEMENT
|
||
// LOTS OF DISPLAY LOGIC
|
||
}
|
||
}
|
||
|
||
// YOU GET THE POINT
|
||
// MORE METHODS HERE WITH A LOT OF SWITCH STATEMENTS
|
||
}</pre><p>So I extracted the data display methods into a protocol:</p><pre class="crayon-plain-tag">protocol SessionDisplayable {
|
||
|
||
/** The main name of this session */
|
||
var title: String { get }
|
||
|
||
/** A follow-up tagline for the session */
|
||
var subtitle: String { get }
|
||
|
||
/** What image, if any is available for this session */
|
||
var logoURL: URL { get }
|
||
|
||
/** The location for where this session will occur */
|
||
var location: String { get }
|
||
|
||
/** A long-form description of the session */
|
||
var sessionDescription: String { get }
|
||
|
||
/** Presentation Summary */
|
||
var presentationSummary: String { get }
|
||
|
||
/** What Twitter handle, if any represents this session */
|
||
var twitter: String { get }
|
||
|
||
/** Whether this type of session requires a new view controller to display more information */
|
||
var selectable: Bool { get }
|
||
}</pre><p>And created individual view models for each session type. For example, here is the <strong>BreakfastSessionViewModel</strong>:</p><pre class="crayon-plain-tag">// BreakfastSessionViewModel.swift
|
||
|
||
struct BreakfastSessionViewModel: SessionDisplayable {
|
||
|
||
private let session: Session
|
||
private let dataDefaults: SessionDataDefaults
|
||
|
||
init?(session: Session) {
|
||
if session.type == .breakfast {
|
||
self.session = session
|
||
self.dataDefaults = SessionDataDefaults(session: session)
|
||
} else {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
var title: String {
|
||
return dataDefaults.title
|
||
}
|
||
|
||
var subtitle: String {
|
||
return dataDefaults.subtitle
|
||
}
|
||
|
||
var logoURL: URL {
|
||
return dataDefaults.imageURL ?? dataDefaults.logoImageURL
|
||
}
|
||
|
||
var location: String {
|
||
return dataDefaults.location
|
||
}
|
||
|
||
var sessionDescription: String {
|
||
return "❤️".localized()
|
||
}
|
||
|
||
var presentationSummary: String {
|
||
return dataDefaults.summary
|
||
}
|
||
|
||
var selectable: Bool {
|
||
return false
|
||
}
|
||
|
||
var twitter: String {
|
||
return "@\(dataDefaults.twitter)"
|
||
}
|
||
}</pre><p>This allowed me to have only one switch statement:</p><pre class="crayon-plain-tag">public struct SessionViewModel: SessionDisplayable {
|
||
|
||
private let displayble: SessionDisplayable
|
||
|
||
public init(session: Session) {
|
||
switch session.type {
|
||
case .workshop:
|
||
displayble = WorkshopSessionViewModel(session)!
|
||
case .meetup:
|
||
displayble = MeetupSessionViewModel(session)!
|
||
case .breakfast:
|
||
displayble = BreakfastSessionViewModel(session)!
|
||
case .announcement:
|
||
displayble = AnnouncementSessionViewModel(session)!
|
||
case .talk:
|
||
displayble = TalkSessionViewModel(session)!
|
||
case .lightningTalk:
|
||
displayble = LightningTalkSessionViewModel(session)!
|
||
case .sponsoredDemo:
|
||
displayble = SponsoredDemoSessionViewModel(session)!
|
||
case .coffeeBreak:
|
||
displayble = CoffeeBreakSessionViewModel(session)!
|
||
case .lunch:
|
||
displayble = LunchSessionViewModel(session)!
|
||
case .officeHours:
|
||
displayble = OfficeHoursSessionViewModel(session)!
|
||
case .party:
|
||
displayble = PartySessionViewModel(session)!
|
||
}
|
||
}
|
||
|
||
public var title: String { return displayble.title }
|
||
|
||
public var subtitle: String { return displayble.subtitle }
|
||
|
||
public var logoURL: URL { return displayble.logoURL }
|
||
|
||
public var location: String { return displayble.location }
|
||
|
||
public var sessionDescription: String { return displayble.sessionDescription }
|
||
|
||
public var presentationSummary: String { return displayble.presentationSummary }
|
||
|
||
public var twitter: String { return displayble.twitter }
|
||
|
||
public var selectable: Bool { return displayble.selectable }
|
||
}</pre><p></p>
|
||
<h2>The Problem</h2>
|
||
<p>The big thing here is that multiple session view models have the same default data implementation. That is why I created a <strong>SessionDataDefaults</strong> object to access the default data easily (see the use-case in the <strong>BreakfastSessionViewModel</strong> implementation).</p><pre class="crayon-plain-tag">// SessionDefaults.swift
|
||
|
||
struct SessionDataDefaults {
|
||
|
||
let title: String
|
||
let subtitle = "try! Conference"
|
||
let logoImageURL = Bundle.trySwiftAssetURL(for: "Logo.png")!
|
||
let imageURL: URL?
|
||
let location: String
|
||
let summary = Conference.current.localizedDescription
|
||
let twitter = Conference.current.twitter!
|
||
|
||
init(session: Session) {
|
||
// properties set here
|
||
}
|
||
}</pre><p>So as you can imagine, some of the session view model implementations (check the <strong>BreakfastSessionViewModel</strong> for reference) use the default data values.</p>
|
||
<p>When I shared this refactor with a friend, he immediately saw a new refactoring opportunity – create default implementations of the relevant methods in the protocol!</p>
|
||
<h2>Default Implementation?</h2>
|
||
<p>At first, that sounded great, but after thinking about it I decided against the default implementation refactor. Here’s why:</p>
|
||
<ul>
|
||
<li>Each variable of a session needs a lot of thought put into it. Even if the implementation details end up being the same as the default, I want it to be a strong conscious choice. If I make it a default implementation, it would be too easy to forget and not think about much. Oh, and the compiler won’t complain!</li>
|
||
<li>I want it to be easy to change the variable for each session. If a variable is not included in the file because the implementation is in the protocol, it’s more work to make the decision to add that into the file. The assumption is that the default should win. If it’s already there, it’s easier to just go in and make a small change.</li>
|
||
<li>Similar to the above point, I want it to be super readable where each variable is coming from. If a bunch of variables are in the default implementation of the protocol, it’s not as clear just by looking at the file.</li>
|
||
</ul>
|
||
<p>I think adding default implementations to protocols should be considered very very carefully. The default needs to be something that is consistent and is the default for most cases, so if someone forgets to implement it (because the compiler won’t complain), it’s most likely not a big deal.</p>
|
||
<p>The one case where I love default implementations in protocols is when the function is not included in the the protocol definition – it’s just a common method with no interface exposed. Otherwise, it’s way too easy to forget about and introduce confusing bugs later! </p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/swift-use-default-implementations-protocols/feed/</wfw:commentRss>
|
||
<slash:comments>4</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6578</post-id> </item>
|
||
<item>
|
||
<title>Swift: When to use guard vs if</title>
|
||
<link>https://www.natashatherobot.com/swift-when-to-use-guard-vs-if/</link>
|
||
<comments>https://www.natashatherobot.com/swift-when-to-use-guard-vs-if/#comments</comments>
|
||
<pubDate>Thu, 23 Mar 2017 04:11:21 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[Swift]]></category>
|
||
<category><![CDATA[Guard]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6568</guid>
|
||
<description><![CDATA[One thing I’ve noticed recently in my code-base is that I tend to default to guard vs if. In fact, whenever I write an if statement, I facepalm myself and change it to a guard without thinking much. But that’s become a problem. There is in fact a difference between guard and if and thought does need to be put into which one to use.]]></description>
|
||
<content:encoded><![CDATA[<p>One thing I’ve noticed recently in my code-base is that I tend to default to <strong>guard</strong> vs <strong>if</strong>. In fact, whenever I write an <strong>if</strong> statement, I facepalm myself and change it to a <strong>guard</strong> without thinking much.</p>
|
||
<p>But that’s become a problem. There is in fact a difference between <strong>guard</strong> and <strong>if</strong> and thought does need to be put into which one to use.</p>
|
||
<p>The difference is a bit subtle, but it is there. <strong>guard</strong> should be used when certain values are expected to be present for the function to execute as intended.</p>
|
||
<p>For example, in the <a href="https://github.com/tryswift/trySwiftAppFinal" target="_blank">try! Swift app</a>, when it displays a presentation session type, the presentation title is the session title.</p>
|
||
<p align="center"><img class="alignnone wp-image-6571" src="https://www.natashatherobot.com/wp-content/uploads/Simulator_Screen_Shot_Mar_23__2017__12_44_22_PM.png" alt="" width="250" height="445" srcset="https://www.natashatherobot.com/wp-content/uploads/Simulator_Screen_Shot_Mar_23__2017__12_44_22_PM.png 750w, https://www.natashatherobot.com/wp-content/uploads/Simulator_Screen_Shot_Mar_23__2017__12_44_22_PM-169x300.png 169w, https://www.natashatherobot.com/wp-content/uploads/Simulator_Screen_Shot_Mar_23__2017__12_44_22_PM-576x1024.png 576w, https://www.natashatherobot.com/wp-content/uploads/Simulator_Screen_Shot_Mar_23__2017__12_44_22_PM-79x140.png 79w" sizes="(max-width: 250px) 100vw, 250px" /></p>
|
||
<p>However, not every session has a presentation, so the presentation is optional. However, for this specific session type, it is expected that a presentation is in fact present and that it has a title. This is a perfect use-case for <strong>guard</strong>!</p><pre class="crayon-plain-tag">@objc public enum SessionType: Int {
|
||
case workshop
|
||
case meetup
|
||
case breakfast
|
||
case announcement
|
||
case talk
|
||
case lightningTalk
|
||
case sponsoredDemo
|
||
case coffeeBreak
|
||
case lunch
|
||
case officeHours
|
||
case party
|
||
}
|
||
|
||
public class Session: Object {
|
||
// this is optional because not all sessions have presentations
|
||
// e.g. no presentation during breakfast
|
||
open dynamic var presentation: Presentation?
|
||
// other properties here
|
||
|
||
/** The main name of this session */
|
||
public var formattedTitle: String {
|
||
|
||
switch self.type {
|
||
case .talk, .lightningTalk:
|
||
// for the talk / lighting talk session type
|
||
// we expect the presentation to be there
|
||
// if it's not there, it's a fail, so `guard` is used
|
||
guard let presentation = presentation else { return defaultTitle }
|
||
return presentation.localizedTitle
|
||
// other cases continued...
|
||
}
|
||
}</pre><p>The talk title should always be present for a presentation session type. If it’s not there, it’s a fail. This is why we use <strong>guard</strong> in this case. </p>
|
||
<p>However, consider another case. A Coffee Break session could be sponsored. In that case, the title of the coffee break should include the sponsor name. Both are correct – if there is a sponsor, we include the name of the sponsor, but if there isn’t one, we don’t include it. This is the type of case where <strong>if</strong> should be used: </p>
|
||
<p></p><pre class="crayon-plain-tag">public class Session: Object {
|
||
|
||
/** A sponsor, if any, responsible for this session. */
|
||
open dynamic var sponsor: Sponsor?
|
||
|
||
/** The main name of this session */
|
||
public var formattedTitle: String {
|
||
|
||
switch self.type {
|
||
case .coffeeBreak:
|
||
// some sessions are sponsored, some aren't
|
||
// it's not a fail if there is no sponsor
|
||
// so `if` is used
|
||
if let sponsor = sponsor {
|
||
return "Coffee Break, by \(sponsor.name)".localized()
|
||
}
|
||
return "Coffee Break".localized()
|
||
// other cases continued...
|
||
}
|
||
}</pre><p></p>
|
||
<p>So as <a href="https://ericcerney.com/swift-guard-statement/" target="_blank">@ecerney puts it so well</a>, think of <strong>guard</strong> as a lightweight <strong>Assert</strong>: </p>
|
||
<blockquote><p>Like an <strong>if</strong> statement, <strong>guard</strong> executes statements based on a Boolean value of an expression. Unlike an <strong>if</strong> statement, <strong>guard</strong> statements only run if the conditions are <em>not</em> met. You can think of <strong>guard</strong> more like an <strong>Assert</strong>, but rather than crashing, you can gracefully exit.</p></blockquote>
|
||
<p>So think <strong>if</strong> before you <strong>guard</strong>!</p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/swift-when-to-use-guard-vs-if/feed/</wfw:commentRss>
|
||
<slash:comments>13</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6568</post-id> </item>
|
||
<item>
|
||
<title>Architecting for Features</title>
|
||
<link>https://www.natashatherobot.com/architecting-for-features/</link>
|
||
<comments>https://www.natashatherobot.com/architecting-for-features/#respond</comments>
|
||
<pubDate>Sun, 12 Mar 2017 08:54:27 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[iOS10]]></category>
|
||
<category><![CDATA[ios]]></category>
|
||
<category><![CDATA[iOS 10]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6553</guid>
|
||
<description><![CDATA[A few months ago, I gave a talk titled Build Features, Not Apps at iOS Conf SG – you can view the full talk here. It was clearer than ever to me after WWDC 2016 that the future of apps is a web of distributed features instead of one concentrated app. Think of Apple Watch, Today’s Widget, Rich Notifications, App Search, iMessage Stickers, and the list goes on…]]></description>
|
||
<content:encoded><![CDATA[<p>A few months ago, I gave a talk titled <strong>Build Features, Not Apps</strong> at <a href="http://iosconf.sg/" target="_blank">iOS Conf SG</a> – you can <a href="https://www.youtube.com/watch?v=lJlyR8chDwo" target="_blank">view the full talk here</a>. It was clearer than ever to me after WWDC 2016 that the future of apps is a web of distributed features instead of one concentrated app. Think of Apple Watch, Today’s Widget, Interactive Notifications, App Search, iMessage Stickers, Apple Maps Integration and the list goes on…</p>
|
||
<p align="center"><img class="alignnone wp-image-6554" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-03-12-at-5.15.38-PM.png" alt="" width="500" height="352" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-03-12-at-5.15.38-PM.png 1446w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-03-12-at-5.15.38-PM-300x211.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-03-12-at-5.15.38-PM-768x541.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-03-12-at-5.15.38-PM-1024x721.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2017-03-12-at-5.15.38-PM-140x99.png 140w" sizes="(max-width: 500px) 100vw, 500px" /></p>
|
||
<p>If done right, each of these extensions will be just that – extensions (or features) of your app. For example, you shouldn’t have your whole app functionality in a Today’s Widget – just the part that is useful to the user on a quick glance (such as the weather right now).</p>
|
||
<p>But increasingly, these types of extensions allow the user to break free from your app. They can just glance at the relevant information – such as the weather right now – without ever bothering to open your app. They’re still using your service, but not in a traditional “opening your app” sense.</p>
|
||
<p>These small and invisible interactions with your app really change the concept of what an app is. An app is now something that gives the user the information or interaction they need at the moment they need it – again, without ever needing to remember to open the actual app. This paradigm shift, while disruptive to apps that do rely on being opened (e.g. they have advertisements in there), also gives a lot of new ways of interacting and building an even stronger brand with consumers. Your app is now everywhere! Not just stuck inside your app waiting to be opened!</p>
|
||
<p>This is why for the <a href="https://github.com/tryswift/trySwiftAppFinal" target="_blank">try! Swift app</a>, I really focused on setting it up for features instead of adding new functionality to the app in time for <a href="https://www.tryswift.co/tokyo/en" target="_blank">try! Swift Tokyo</a>.</p>
|
||
<p>The key here was decoupling the data layer, including Realm, from the iOS app. The same data layer would be re-used for the Watch App, Apple TV App (in the future maybe), the iOS app, and iOS extensions (such as a Today’s Widget and Interactive Notifications).</p>
|
||
<p>Unfortunately, that’s much harder to do than it should be. I recommend the article <a href="http://basememara.com/creating-cross-platform-swift-frameworks-ios-watchos-tvos-via-carthage-cocoapods/" target="_blank">Creating Cross-Platform Swift Frameworks for iOS, watchOS, and tvOS via Carthage and CocoaPods</a> by Basem Emra to get started.</p>
|
||
<p>I personally have never made a CocoaPod, especially one with a dependency on Realm, so I had a really hard time getting everything to link and work together. Luckily, <a href="https://twitter.com/aaalveee" target="_blank">@aaalveee</a> helped us get started, <a href="https://twitter.com/k_katsumi" target="_blank">@k_katsumi</a> helped make the pod work across extensions, and <a href="https://twitter.com/TimOliverAU" target="_blank">@TimOliverAU</a> did a lot of work to set the project up for <a href="https://realm.io/products/realm-mobile-platform/" target="_blank">Realm Mobile Platform</a> (still some work to do there).</p>
|
||
<p>It was a lot of work (and still a lot of work left to do), but I’m super happy with the result – you can see the<strong> trySwiftData framework</strong> <a href="https://github.com/tryswift/trySwiftData" target="_blank">on Github here</a>. The best part is that the data framework includes a lot of complicated data formatting code – such as session titles based on speakers vs announcements – that used to be duplicated in code both in iOS and watchOS apps. We were even able to add a Today’s Widget with limited time, using the same data pod.</p>
|
||
<p>I’m looking forward to now move a lot quicker with adding new features, including Interactive Notifications and App Search in time for the next try! Swift in NYC <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/architecting-for-features/feed/</wfw:commentRss>
|
||
<slash:comments>0</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6553</post-id> </item>
|
||
<item>
|
||
<title>How to Reuse Paging Interface Controllers in watchOS</title>
|
||
<link>https://www.natashatherobot.com/how-to-reuse-paging-interfacecontrollers-in-watchos/</link>
|
||
<comments>https://www.natashatherobot.com/how-to-reuse-paging-interfacecontrollers-in-watchos/#comments</comments>
|
||
<pubDate>Mon, 26 Sep 2016 17:10:02 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[iOS10]]></category>
|
||
<category><![CDATA[WatchOS]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6525</guid>
|
||
<description><![CDATA[I've been trying to figure out how to reuse the same interface controller code for all my interface controllers in a watchOS paging navigation, and finally did. But this solution comes at a price... ]]></description>
|
||
<content:encoded><![CDATA[<p>watchOS is currently very non-dynamic. You have a storyboard and you have to put all your Interface Controllers in it, even when they’re all pretty much the same, like in this Italian Food Apple Watch app:</p>
|
||
<div style="width: 276px;" class="wp-video"><!--[if lt IE 9]><script>document.createElement('video');</script><![endif]-->
|
||
<video class="wp-video-shortcode" id="video-6525-1" width="276" height="390" preload="metadata" controls="controls"><source type="video/mp4" src="https://www.natashatherobot.com/wp-content/uploads/WatchOSPaging.mp4?_=1" /><a href="https://www.natashatherobot.com/wp-content/uploads/WatchOSPaging.mp4">https://www.natashatherobot.com/wp-content/uploads/WatchOSPaging.mp4</a></video></div>
|
||
<p>Each Interface Controller simply has an image and a label:</p>
|
||
<p align="center"><img class="alignnone wp-image-6527" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.17.07-PM.png" alt="WatchOS Paging Storyboard" width="500" height="199" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.17.07-PM.png 1822w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.17.07-PM-300x120.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.17.07-PM-768x306.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.17.07-PM-1024x408.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.17.07-PM-140x56.png 140w" sizes="(max-width: 500px) 100vw, 500px" /></p>
|
||
<p>Even though these are exactly the same and have the exact same logic in the Interface Controllers, for the past year, I haven’t been able to figure out how to re-use one Interface Controller for each of these! </p>
|
||
<p>So I had three different controllers where I copied and pasted all the code and only changed the model-layer details for the image and label data. But alas, after searching once again, I finally stumbled on a horrible solution that actually works.</p>
|
||
<h2>The Model</h2>
|
||
<p>First, here is the simple <strong>FoodItem</strong> model that I’m using to populate the data for the Interface Controllers:</p><pre class="crayon-plain-tag">struct FoodItem {
|
||
let title: String
|
||
let imageName: String
|
||
}
|
||
|
||
extension FoodItem {
|
||
|
||
static let foodItems = [
|
||
FoodItem(title: "Camogliese al Rum", imageName: "comogli"),
|
||
FoodItem(title: "Pesto alla Genovese", imageName: "pasta"),
|
||
FoodItem(title: "Focaccia di Recco", imageName: "recco"),
|
||
]
|
||
}</pre><p></p>
|
||
<h2>The Storyboard</h2>
|
||
<p>The next step is to create the reusable Interface Controller, let’s name it <strong>FoodItemInterfaceController</strong>, and to assign it as the class for every single Interface Controller in the storyboard:</p>
|
||
<p align="center"><img class="alignnone wp-image-6528" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.26.41-PM.png" alt="FoodItemInterfaceController" width="497" height="142" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.26.41-PM.png 2536w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.26.41-PM-300x86.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.26.41-PM-768x220.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.26.41-PM-1024x293.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-6.26.41-PM-140x40.png 140w" sizes="(max-width: 497px) 100vw, 497px" /></p>
|
||
<p>Next, create and connect the IBOutlets for the image and label in the FoodItemInterfaceController:</p>
|
||
<p align="center"><img class="alignnone wp-image-6529" src="https://www.natashatherobot.com/wp-content/uploads/FoodItemInterfaceController_swift_—_Edited.png" alt="fooditeminterfacecontroller_swift_-_edited" width="497" height="100" srcset="https://www.natashatherobot.com/wp-content/uploads/FoodItemInterfaceController_swift_—_Edited.png 2292w, https://www.natashatherobot.com/wp-content/uploads/FoodItemInterfaceController_swift_—_Edited-300x60.png 300w, https://www.natashatherobot.com/wp-content/uploads/FoodItemInterfaceController_swift_—_Edited-768x154.png 768w, https://www.natashatherobot.com/wp-content/uploads/FoodItemInterfaceController_swift_—_Edited-1024x206.png 1024w, https://www.natashatherobot.com/wp-content/uploads/FoodItemInterfaceController_swift_—_Edited-140x28.png 140w" sizes="(max-width: 497px) 100vw, 497px" /></p>
|
||
<p>Finally, you have to add a unique identifier for each of your Interface Controllers in the Storyboard:</p>
|
||
<p align="center"><img class="alignnone wp-image-6531" src="https://www.natashatherobot.com/wp-content/uploads/Interface_storyboard_—_Edited-1.png" alt="interface_storyboard_-_edited" width="498" height="176" srcset="https://www.natashatherobot.com/wp-content/uploads/Interface_storyboard_—_Edited-1.png 1712w, https://www.natashatherobot.com/wp-content/uploads/Interface_storyboard_—_Edited-1-300x106.png 300w, https://www.natashatherobot.com/wp-content/uploads/Interface_storyboard_—_Edited-1-768x272.png 768w, https://www.natashatherobot.com/wp-content/uploads/Interface_storyboard_—_Edited-1-1024x362.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Interface_storyboard_—_Edited-1-140x50.png 140w" sizes="(max-width: 498px) 100vw, 498px" /></p>
|
||
<h2>The Interface Controller</h2>
|
||
<p>Now comes the ugly part… When the first interface controller loads, you have to trick it into loading all the others instead…</p><pre class="crayon-plain-tag">import WatchKit
|
||
|
||
class FoodItemInterfaceController: WKInterfaceController {
|
||
|
||
@IBOutlet var image: WKInterfaceImage!
|
||
@IBOutlet var label: WKInterfaceLabel!
|
||
|
||
// you have to keep track of whether this is the first load...
|
||
static var first = true
|
||
|
||
override func awake(withContext context: Any?) {
|
||
super.awake(withContext: context)
|
||
|
||
// if first load...
|
||
if FoodItemInterfaceController.first {
|
||
// then reload with the data for all 3 controllers...
|
||
// the Names are the storyboard identifiers
|
||
// the Context is the data
|
||
if FoodItemInterfaceController.first {
|
||
WKInterfaceController.reloadRootControllers(
|
||
withNames: ["FoodItem1", "FoodItem2", "FoodItem3"],
|
||
contexts: FoodItem.foodItems)
|
||
FoodItemInterfaceController.first = false
|
||
}
|
||
|
||
// the data is in the context that's passed into this method
|
||
if let foodItem = context as? FoodItem {
|
||
// set the proper data into the image and label
|
||
image.setImage(UIImage(named: foodItem.imageName))
|
||
label.setText(foodItem.title)
|
||
}
|
||
}
|
||
}</pre><p></p>
|
||
<h2>Conclusion</h2>
|
||
<p>First, this is slower than just hardcoding all the Interface Controllers, since the first time the Interface Controller loads, it has to reload everything. But at least the code is in one place, right? </p>
|
||
<p>Also, there is no way to my knowledge to have a dynamic data set (e.g. you get the variable food item array data from the server and want to display it in more than 3 pages. Although in that case, you can use a table instead of paging interface. </p>
|
||
<p>Oh, and of course, you still have to duplicate the Interface Controllers in the Storyboard, so even though these all have the same-sized images and labels with the same layout and fonts, if you make a change to one, you have to remember to make it to all so they all look the same at the end. I forgot to do this a few times even for this demo…</p>
|
||
<p>You can view the <a href="https://github.com/NatashaTheRobot/WatchReusablePagingExample" target="_blank">full source code on Github here</a>. </p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/how-to-reuse-paging-interfacecontrollers-in-watchos/feed/</wfw:commentRss>
|
||
<slash:comments>1</slash:comments>
|
||
<enclosure url="https://www.natashatherobot.com/wp-content/uploads/WatchOSPaging.mp4" length="963555" type="video/mp4" />
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6525</post-id> </item>
|
||
<item>
|
||
<title>Creating a Framework for both iOS and watchOS</title>
|
||
<link>https://www.natashatherobot.com/creating-a-framework-for-both-ios-and-watchos/</link>
|
||
<comments>https://www.natashatherobot.com/creating-a-framework-for-both-ios-and-watchos/#comments</comments>
|
||
<pubDate>Mon, 26 Sep 2016 09:18:39 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[iOS10]]></category>
|
||
<category><![CDATA[architecture]]></category>
|
||
<category><![CDATA[framework]]></category>
|
||
<category><![CDATA[ios]]></category>
|
||
<category><![CDATA[Swift]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6517</guid>
|
||
<description><![CDATA[The try! Swift app runs on both iOS and watchOS, and I’d like to expand it to support fancy extensions and possibly something with iMessage. While I was ok duplicating the Model layer between the two platforms initially, as more extensions and platforms are something I want to code quickly on, it was time to move that code out into a framework.]]></description>
|
||
<content:encoded><![CDATA[<p>The <a href="https://github.com/tryswift/trySwiftNYC" target="_blank">try! Swift app</a> runs on both iOS and watchOS, and I’d like to expand it to support fancy extensions and possibly something with iMessage. And maybe I’ll want to have a tvOS app eventually to hook up to the tv monitors around one of my conferences.</p>
|
||
<p>While I was ok duplicating the Model layer between the two platforms initially, as more extensions and platforms are something I want to code quickly on, it was time to move that code out into a framework.</p>
|
||
<p>I haven’t made a framework before, but it’s super easy. The hard part was realizing that there there isn’t an option to create a cross-platform framework. I was forced to choose between iOS, macOS, tvOS, and watchOS. Which makes a lot of sense. Apple frameworks that are available on iOS are not available on watchOS, etc.</p>
|
||
<p>But in my case, I just had super simple Model-layer code, so I wanted to share this code between my iOS and watchOS platforms. After searching around for how to do it, I finally found a simple solution that works.</p>
|
||
<p>I just created a Models folder for shared files, and put it in a folder outside the two frameworks:</p>
|
||
<p align="center"><img class="alignnone wp-image-6518" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.07.29-AM.png" alt="shared framework code" width="483" height="207" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.07.29-AM.png 818w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.07.29-AM-300x128.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.07.29-AM-768x329.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.07.29-AM-140x60.png 140w" sizes="(max-width: 483px) 100vw, 483px" /></p>
|
||
<p>Then I just dragged the files into to the correct framework targets (do NOT select “Copy as needed”).</p>
|
||
<p align="center"><img class="alignnone wp-image-6519" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.09.49-AM.png" alt="Model Duplication" width="213" height="544" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.09.49-AM.png 512w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.09.49-AM-117x300.png 117w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.09.49-AM-401x1024.png 401w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-26-at-11.09.49-AM-55x140.png 55w" sizes="(max-width: 213px) 100vw, 213px" /></p>
|
||
<p>The key here is that if you want to add something to your model layer that only pertains to the specific framework, you have to make a new file with an extension for that model. Oh, and of course, the files have the exact same reference, so any change you make to one framework, you make to the other. </p>
|
||
<p>So it’s definitely a bit dangerous and not a great solution, but the only one I’ve found unfortunately. Now I finally feel like I can move fast with other app extensions I have in mind! </p>
|
||
<p>If anyone has better solutions, happy to hear about them in the comments!</p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/creating-a-framework-for-both-ios-and-watchos/feed/</wfw:commentRss>
|
||
<slash:comments>11</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6517</post-id> </item>
|
||
<item>
|
||
<title>Swift 3.0 Refactoring Cues</title>
|
||
<link>https://www.natashatherobot.com/swift-3-0-refactoring-cues/</link>
|
||
<comments>https://www.natashatherobot.com/swift-3-0-refactoring-cues/#comments</comments>
|
||
<pubDate>Tue, 20 Sep 2016 10:03:42 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[Swift]]></category>
|
||
<category><![CDATA[generics]]></category>
|
||
<category><![CDATA[iosdev]]></category>
|
||
<category><![CDATA[Protocols]]></category>
|
||
<category><![CDATA[refactor]]></category>
|
||
<category><![CDATA[swiftlang]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6505</guid>
|
||
<description><![CDATA[I’ve been upgrading the try! Swift app to Swift 3.0 for what feels like 3 days now. But anyway, this morning my BUILD SUCCEEDED!! And although the process was definitely frustrating, a lot of the things that needed fixing were very repetitive and a good opportunity to refactor... ]]></description>
|
||
<content:encoded><![CDATA[<p>I’ve been upgrading the <a href="https://github.com/tryswift/trySwiftNYC" target="_blank">try! Swift app</a> to Swift 3.0 for what feels like 3 days now (I’ve had to take a lot of breaks to be in the right patient mindset, especially with not knowing how many more errors will come up after the immediate ones were fixed). </p>
|
||
<p>But anyway, this morning my BUILD SUCCEEDED!! I fixed the final warnings (almost) and tried out the app to see that it works generally. I still have time to fix any bugs in the future before the next version goes in production.</p>
|
||
<p>And although the process was definitely frustrating, a lot of the things that needed fixing were very repetitive. For example, the String API has added a new (and very ugly IMHO) describing parameter: </p>
|
||
<p></p><pre class="crayon-plain-tag">// covered here: https://www.natashatherobot.com/nsstringfromclass-in-swift/
|
||
String(MyTableViewCell)
|
||
|
||
// is now this
|
||
// * notice the required .self as well! *
|
||
String(describing: MyTableViewCell.self)</pre><p></p>
|
||
<p>Well, I have A LOT of TableViewCell’s in my app, and when combined with registering Nibs or Dequeuing Cells, it was just an ugly train wreck: </p>
|
||
<p></p><pre class="crayon-plain-tag">// Swift 3.0 registering a cell example
|
||
tableView.register(UINib(nibName: String(describing: TextTableViewCell.self), bundle: nil), forCellReuseIdentifier: String(describing: TextTableViewCell.self))
|
||
|
||
// Swift 3.0 dequeuing a cell example
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TextTableViewCell.self), for: indexPath) as! TextTableViewCell</pre><p></p>
|
||
<p>I had to fix these with the describing and .self over and over again during my Swift 3.0 upgrading process. As soon as I finished the upgrade, I knew I had to fix this issue. After all, hopefully the String API will be fixed back to having no describing parameter in Swift 4 – I want to refactor this repetitive change in only one place in the future! </p>
|
||
<h2>The Refactor</h2>
|
||
<p>Luckily, in this case, I knew just the solution! In fact, I talked about this solution in my <a href="http://www.slideshare.net/natashatherobot/practial-protocolorientedprogramming" target="_blank">POP talk</a>… I should definitely have implemented this earlier, but when I first started making the app, I didn’t know about it, and it hasn’t been a big enough issue to actually do the refactor until this painful Swift 3.0 upgrade <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f62c.png" alt="😬" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
|
||
<p>All it took was a few minutes and a few protocols! A timely reminder of how awesome Swift actually is! For a more detailed explanation, make sure to <a href="https://realm.io/news/appbuilders-natasha-muraschev-practical-protocol-oriented-programming/" target="_blank">watch my talk</a> or read about it from <a href="https://medium.com/@gonzalezreal/ios-cell-registration-reusing-with-swift-protocol-extensions-and-generics-c5ac4fb5b75e#.q2cztagj8" target="_blank">the source here</a>. Otherwise, here is the quick version: </p>
|
||
<p>First, create a Protocol with a default variable for generating the nib name string from the class name:</p><pre class="crayon-plain-tag">protocol NibLoadableView: class { }
|
||
|
||
extension NibLoadableView where Self: UIView {
|
||
|
||
static var nibName: String {
|
||
// notice the new describing here
|
||
// now only one place to refactor if describing is removed in the future
|
||
return String(describing: self)
|
||
}
|
||
|
||
}
|
||
|
||
// Now all UITableViewCells have the nibName variable
|
||
// you can also apply this to UICollectionViewCells if you have those
|
||
// Note that if you have some cells that DO NOT load from a Nib vs some that do,
|
||
// extend the cells individually vs all of them as below!
|
||
// In my project, all cells load from a Nib.
|
||
extension UITableViewCell: NibLoadableView { }</pre><p></p>
|
||
<p>Next, do the same thing to generate the reuse identifier string from the cell class:</p><pre class="crayon-plain-tag">protocol ReusableView: class {}
|
||
|
||
extension ReusableView where Self: UIView {
|
||
|
||
static var reuseIdentifier: String {
|
||
return String(describing: self)
|
||
}
|
||
|
||
}
|
||
|
||
extension UITableViewCell: ReusableView { }</pre><p></p>
|
||
<p>Now the good stuff! You can take advantage of the above protocols to simplify the nib registration and cell dequeuing: </p>
|
||
<p></p><pre class="crayon-plain-tag">// UITableViewExtension.swift
|
||
|
||
extension UITableView {
|
||
|
||
func register<T: UITableViewCell>(_: T.Type) where T: ReusableView, T: NibLoadableView {
|
||
|
||
let nib = UINib(nibName: T.nibName, bundle: nil)
|
||
register(nib, forCellReuseIdentifier: T.reuseIdentifier)
|
||
}
|
||
|
||
func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: IndexPath) -> T where T: ReusableView {
|
||
guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
|
||
fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)")
|
||
}
|
||
|
||
return cell
|
||
}
|
||
}</pre><p></p>
|
||
<p>Now time to refactor! </p>
|
||
<p></p><pre class="crayon-plain-tag">// Swift 3.0 original registering a cell example
|
||
tableView.register(UINib(nibName: String(describing: TextTableViewCell.self), bundle: nil), forCellReuseIdentifier: String(describing: TextTableViewCell.self))
|
||
|
||
// Swift 3.0 refactored registering cell example:
|
||
tableView.register(TextTableViewCell.self)
|
||
|
||
// Swift 3.0 original dequeuing a cell example
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TextTableViewCell.self), for: indexPath) as! TextTableViewCell
|
||
|
||
// Swift 3.0 refactored dequeuing a cell example
|
||
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as TextTableViewCell</pre><p></p>
|
||
<p>Here is <a href="https://github.com/tryswift/trySwiftNYC/commit/70e950687cb2bc13e6713ae015612246fb96c7d2" target="_blank">my full commit</a>. I was very happy with this result and look forward to less repetition when upgrading to Swift 4.0 in the future! </p>
|
||
<p>So although the Swift 3.0 upgrade is very painful, it is also a good time to notice all those repetitive things in your code and refactor. </p>
|
||
<p>Happy upgrading and remember to Breathe!</p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/swift-3-0-refactoring-cues/feed/</wfw:commentRss>
|
||
<slash:comments>6</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6505</post-id> </item>
|
||
<item>
|
||
<title>CloudKit: What is it good for?</title>
|
||
<link>https://www.natashatherobot.com/cloudkit-what-is-it-good-for/</link>
|
||
<comments>https://www.natashatherobot.com/cloudkit-what-is-it-good-for/#comments</comments>
|
||
<pubDate>Sun, 18 Sep 2016 14:29:02 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[iPhone]]></category>
|
||
<category><![CDATA[architecture]]></category>
|
||
<category><![CDATA[CloudKit]]></category>
|
||
<category><![CDATA[ios]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6491</guid>
|
||
<description><![CDATA[This blog post is more of a general architecture overview of CloudKit for a use-case where it came in handy...]]></description>
|
||
<content:encoded><![CDATA[<p>When I first created the <a href="https://github.com/tryswift/trySwiftApp" target="_blank">try! Swift app</a> in haste in time for my first conference in Tokyo earlier this year, I hard-coded everything. After all, what could possibly change two weeks before the conference?!!!</p>
|
||
<p>Well, turned out that one speaker had to cancel last minute and we had to make some last-minute speaker / presentation changes. While, it’s not the biggest deal to have one talk information wrong, I really hated that I couldn’t update any information remotely to accommodate for any last-minute changes without having to go through the app review process again (at that time, it was still two weeks). So I made that the priority feature for the <a href="https://github.com/tryswift/trySwiftNYC" target="_blank">try! Swift NYC</a> app in time for <a href="http://www.tryswiftnyc.com/" target="_blank">my latest conference in early September</a>.</p>
|
||
<p>The obvious way to accomplish this that came to mind was via some type of JSON payload. In fact, <a href="https://twitter.com/BasThomas" target="_blank">@BasThomas</a> actually <a href="https://github.com/tryswift/trySwiftNYC/commit/4642d0806d25ac69d381959d8348c2dee891e73e" target="_blank">built it</a>! However, when I mentioned this to <a href="https://twitter.com/danielboedewadt" target="_blank">@danielboedewadt</a> while catching up during WWDC, he recommended using CloudKit, especially since it has the silent notifications feature. I was immediately excited to play with something new and shiny, so when it came time for me to work on the try! Swift NYC app, I selfishly deleted Bas’s JSON work and started implementing CloudKit instead.</p>
|
||
<h2>The Business Use-case</h2>
|
||
<p>From the business perspective, the reality is that the conference schedule would mostly stay the same. In addition, many of our attendees are from abroad with varying levels of internet access. I also had no idea how bad the wifi would be in the venue (although it turned out to be great!).</p>
|
||
<p>From that perspective, hard-coding / having as much information as possible on load was still necessary. I wanted users to open the app and have everything load right away without waiting for any kind of annoying spinners while waiting for updates from the cloud.</p>
|
||
<h2>Persistence</h2>
|
||
<p>In the try! Swift Japan app, I hard-coded everything into arrays and dictionaries to load every time (after all, it wasn’t that much information). However, to be able to make updates from CloudKit in the try! Swift NYC app, I knew I needed a more robust local persistence layer.</p>
|
||
<p><a href="https://realm.io/docs/swift/latest/" target="_blank">Realm</a> has been on my list of things I wanted to try but haven’t gotten to, so I decided to try it out instead of CoreData. Pretty happy I did! But that’s another topic.</p>
|
||
<h2>Data Duplication?</h2>
|
||
<p>When I started working with CloudKit, my mindset was still stuck in the typical architecture where I would have to sync all the data between the cloud and my local persistence layer. I was planning on saving hard-coded data into Realm on the first app load (since most of it was static and good enough after-all in case there is no internet connection), and then having a mirror database with all the data in CloudKit that I would sync with my local Realm database.</p>
|
||
<p>However, the more I thought about my business use-case and how rare it is to have the data actually change, and how expensive it would be to query everything and figure out all the changes, I came up with a much lighter use of CloudKit!</p>
|
||
<h2>Ch-ch-ch-ch-Changes</h2>
|
||
<p>Instead of duplicating my entire database layer in the Cloud, I created a more generic Change table in my CloudKit database:</p>
|
||
<p align="center"><img class="alignnone wp-image-6493" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.49.49-PM.png" alt="CloudKit Change Table" width="500" height="457" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.49.49-PM.png 1276w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.49.49-PM-300x275.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.49.49-PM-768x703.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.49.49-PM-1024x937.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.49.49-PM-140x128.png 140w" sizes="(max-width: 500px) 100vw, 500px" /></p>
|
||
<p>So for example, if the Speaker bio changed (which is the only change that happened using this system), I would create the following Change object:</p>
|
||
<p><img class="alignnone wp-image-6494" src="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.52.58-PM.png" alt="CloudKit Change Create" width="500" height="468" srcset="https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.52.58-PM.png 1278w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.52.58-PM-300x281.png 300w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.52.58-PM-768x720.png 768w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.52.58-PM-1024x960.png 1024w, https://www.natashatherobot.com/wp-content/uploads/Screen-Shot-2016-09-18-at-3.52.58-PM-140x131.png 140w" sizes="(max-width: 500px) 100vw, 500px" /></p>
|
||
<p>So basically, the Speaker with id 7 (in Realm) needs to have their “bio” field changed to the new value provided.</p>
|
||
<h2>Subscribe to Changes</h2>
|
||
<p>On the app side, we just subscribe to any Creation changes in CloudKit to get notifications to make the update!</p>
|
||
<p></p><pre class="crayon-plain-tag">class AppDelegate: UIResponder, UIApplicationDelegate {
|
||
|
||
var window: UIWindow?
|
||
|
||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||
insertDefaultData()
|
||
|
||
let notificationSettings = UIUserNotificationSettings(types: UIUserNotificationType(), categories: nil)
|
||
application.registerUserNotificationSettings(notificationSettings)
|
||
application.registerForRemoteNotifications()
|
||
|
||
subscribeToCloudChangeNotifications()
|
||
|
||
// sync for changes on app load
|
||
configureData()
|
||
|
||
// configure other stuff here
|
||
|
||
return true
|
||
}
|
||
|
||
// handle the silent notification when a Change is made
|
||
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
|
||
{
|
||
ChangeManager.syncChanges()
|
||
completionHandler(.noData)
|
||
}
|
||
|
||
// Subscribing to Change notifications
|
||
func subscribeToCloudChangeNotifications() {
|
||
let defaults = UserDefaults.standard
|
||
DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.default).async {
|
||
if !defaults.bool(forKey: "SubscribedToCloudChanges") {
|
||
let predicate = NSPredicate(value: true)
|
||
|
||
let subscription = CKSubscription(recordType: "Change", predicate: predicate, options: .firesOnRecordCreation)
|
||
|
||
let notificationInfo = CKNotificationInfo()
|
||
notificationInfo.shouldSendContentAvailable = true
|
||
|
||
subscription.notificationInfo = notificationInfo
|
||
|
||
let publicDB = CKContainer.default().publicCloudDatabase
|
||
publicDB.save(subscription, completionHandler: { subscription, error in
|
||
if let _ = subscription {
|
||
defaults.set(true, forKey: "SubscribedToCloudChanges")
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// sync any changes on app load
|
||
func configureData() {
|
||
let defaults = UserDefaults.standard
|
||
|
||
let appSubmitionDate = Date.date(year: 2016, month: 8, day: 16, hour: 5, minute: 0, second: 0)
|
||
if defaults.object(forKey: ChangeManager.lastChangedDataNotification) == nil {
|
||
defaults.setObject(appSubmitionDate, forKey: ChangeManager.lastChangedDataNotification)
|
||
}
|
||
|
||
ChangeManager.syncChanges()
|
||
}
|
||
|
||
// other stuff here
|
||
}</pre><p></p>
|
||
<p>Now, when a Change happens, the change can be synced with the local Realm database as follows: </p>
|
||
<p></p><pre class="crayon-plain-tag">import RealmSwift
|
||
import CloudKit
|
||
|
||
struct ChangeManager {
|
||
|
||
static let lastChangedDataNotification = "LastChangedDataNotification"
|
||
|
||
static func syncChanges() {
|
||
let defaults = UserDefaults.standard
|
||
DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async {
|
||
let publicDB = CKContainer.default().publicCloudDatabase
|
||
guard let lastChangeDate = defaults.object(forKey: ChangeManager.lastChangedDataNotification) as? Date else {
|
||
let appSubmitionDate = Date.date(year: 2016, month: 8, day: 16, hour: 5, minute: 0, second: 0)
|
||
defaults.setObject(appSubmitionDate, forKey: ChangeManager.lastChangedDataNotification)
|
||
return
|
||
}
|
||
|
||
let predicate = NSPredicate(format: "creationDate > %@", lastChangeDate as CVarArg)
|
||
let query = CKQuery(recordType: "Change", predicate: predicate)
|
||
publicDB.perform(query, inZoneWith: nil) { result, error in
|
||
|
||
guard let result = result else {
|
||
// will update again on future launch
|
||
return
|
||
}
|
||
|
||
result.forEach {
|
||
updateRecord($0)
|
||
}
|
||
defaults.set(Date(), forKey: ChangeManager.lastChangedDataNotification)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private extension ChangeManager {
|
||
|
||
static func updateRecord(_ record: CKRecord) {
|
||
guard let object = record["object"] as? String,
|
||
let id = record["id"] as? Int,
|
||
let field = record["field"] as? String,
|
||
let newValue = record["newValue"] as? String else {
|
||
return
|
||
}
|
||
|
||
|
||
let realm = try! Realm()
|
||
if object == "Speaker" {
|
||
if let speaker = realm.objects(Speaker).filter("id == \(id)").first {
|
||
if field == "imagePath" {
|
||
guard let imageAsset = record["image"] as? CKAsset else {
|
||
return
|
||
}
|
||
|
||
try! realm.write {
|
||
speaker["imageName"] = nil
|
||
speaker[field] = imageAsset.fileURL.path
|
||
}
|
||
|
||
} else {
|
||
try! realm.write {
|
||
speaker[field] = newValue
|
||
}
|
||
}
|
||
}
|
||
} else if object == "Presentation" {
|
||
if let presentation = realm.objects(Presentation).filter("id == \(id)").first {
|
||
try! realm.write {
|
||
presentation[field] = newValue
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}</pre><p></p>
|
||
<p>Not the most beautiful code, but it works… </p>
|
||
<h2>Conclusion</h2>
|
||
<p>One of the things I was disappointed with for CloudKit was how Stringly-typed it is – given that this is a relatively new native solution from Apple. I was hoping to see something closer to what Parse did. But it ended up working well with Realm subscripts that way. I think the code can definitely be improved to be less Stringy, but I was happy with this initial architecture. </p>
|
||
<p>I was able to create a super light updating layer with a mixture of hard-coded pre-loaded data and silent notification updates included without having to install yet another third-party solution. However, if I needed something more heavy-duty, I would have to consider <a href="https://firebase.google.com/docs/ios/setup" target="_blank">Firebase</a> or just a plain old JSON API. </p>
|
||
<p><em>Note: This blog post is more of a general architecture overview of CloudKit. For full code example, see the <a href="https://github.com/tryswift/trySwiftNYC" target="_blank">try! Swift NYC app source code here</a>. </em></p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/cloudkit-what-is-it-good-for/feed/</wfw:commentRss>
|
||
<slash:comments>1</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6491</post-id> </item>
|
||
<item>
|
||
<title>Swift: What are Protocols with Associated Types?</title>
|
||
<link>https://www.natashatherobot.com/swift-what-are-protocols-with-associated-types/</link>
|
||
<comments>https://www.natashatherobot.com/swift-what-are-protocols-with-associated-types/#comments</comments>
|
||
<pubDate>Thu, 28 Jul 2016 15:17:27 +0000</pubDate>
|
||
<dc:creator><![CDATA[Natasha Murashev]]></dc:creator>
|
||
<category><![CDATA[Swift]]></category>
|
||
|
||
<guid isPermaLink="false">https://www.natashatherobot.com/?p=6473</guid>
|
||
<description><![CDATA[I recently gave a talk about Protocols with Associated Types (PATs) – I was worried that my audience would already know everything about PATs, but turns out the opposite is true. So here is an explanation...]]></description>
|
||
<content:encoded><![CDATA[<p>I recently gave a talk about <a href="http://www.slideshare.net/natashatherobot/practical-protocols-with-associated-types" target="_blank">Protocols with Associated Types</a> (PATs) – I was worried that my audience would already know everything about PATs, but turns out the opposite is true. </p>
|
||
<p>Many didn’t know what PATs were – which I should have anticipated in hindsight, since it took me a while to learn about them myself. So I wanted to explain them right here, especially since they are challenging to understand and there aren’t that many great explanations that I’ve found.</p>
|
||
<p>The best explanation that really helped me was by Gwendolyn Weston at the <a href="http://www.tryswiftnyc.com/" target="_blank">try! Swift conference</a> in Tokyo (<a href="https://realm.io/news/tryswift-gwendolyn-weston-type-erasure/" target="_blank">video here</a>), so the example is inspired by her talk. There will be Pokemon…</p>
|
||
<h2>Before PATs</h2>
|
||
<p>I’m currently Level 9 in Pokemon Go, and I learned (thanks to my personal trainer <a href="https://twitter.com/ayanonagon" target="_blank">@ayanonagon</a>) that all Pokemon have some common traits, such as the power to attack. </p>
|
||
<p>Coming from Objective-C or other object-oriented languages, it might be tempting to have a Pokemon subclass for all the common functionality. Since each Pokemon attacks with a different power – lightning or water or fire, etc – we can use the power of Generics in our subclass: </p>
|
||
<p></p><pre class="crayon-plain-tag">// we have to ensure that the generic Power
|
||
// has an init function
|
||
protocol Initializable {
|
||
init()
|
||
}
|
||
|
||
// Pokemon subclass
|
||
// Each Pokemon has a different Power,
|
||
// so Power is a generic
|
||
class Pokemon<Power: Initializable> {
|
||
|
||
func attack() -> Power {
|
||
return Power()
|
||
}
|
||
}</pre><p></p>
|
||
<p>At this point, we would have different Powers modeled: </p>
|
||
<p></p><pre class="crayon-plain-tag">// power types
|
||
struct <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f327.png" alt="🌧" class="wp-smiley" style="height: 1em; max-height: 1em;" />: Initializable { // implementation }
|
||
struct <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f329.png" alt="🌩" class="wp-smiley" style="height: 1em; max-height: 1em;" />: Initializable { // implementation }
|
||
struct <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" />: Initializable { // implementation }</pre><p></p>
|
||
<p>Now, other Pokemon can subclass from our base Pokemon class, and they will automatically have the <code>attack</code> function!</p>
|
||
<p></p><pre class="crayon-plain-tag">class Pikachu: Pokemon<<img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f329.png" alt="🌩" class="wp-smiley" style="height: 1em; max-height: 1em;" />> {}
|
||
class Vaporeon: Pokemon<<img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f327.png" alt="🌧" class="wp-smiley" style="height: 1em; max-height: 1em;" />> {}
|
||
|
||
let pikachu = Pikachu()
|
||
pikachu.attack() // <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f329.png" alt="🌩" class="wp-smiley" style="height: 1em; max-height: 1em;" />
|
||
|
||
let vaporeon = Vaporeon()
|
||
vaporeon.attack() // <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f327.png" alt="🌧" class="wp-smiley" style="height: 1em; max-height: 1em;" /></pre><p></p>
|
||
<p>The problem here is that we’re subclassing. If you’ve watched Dave Abrahams’ WWDC talk on <a href="https://developer.apple.com/videos/play/wwdc2015/408/" target="_blank">Protocol-Oriented Programming in Swift</a>, you should be seeing the face of Crusty in your head right now… </p>
|
||
<p>The problem with subclassing is that while it starts out with great intentions, eventually things get a lot messier as exceptions arise (e.g. Pokemon Eggs can’t attack). I highly recommend reading <a href="http://matthijshollemans.com/2015/07/22/mixins-and-traits-in-swift-2/" target="_blank">Matthijs Hollemans’s Mixins and Traits in Swift 2.0</a> to understand this more.</p>
|
||
<p>After all, as Dave Abrahams stated, Swift is a Protocol-Oriented Language, so we need to change our Object-Oriented mindset…</p>
|
||
<h2>Hello, PATs</h2>
|
||
<p>Instead of subclassing, let’s do the same thing with PATs! Instead of subclassing all the things, we can create a Protocol that focuses on the Pokemon’s ability to attack. Remember, since each Pokemon has a different Power, we need to make that a Generic: </p>
|
||
<p></p><pre class="crayon-plain-tag">protocol PowerTrait {
|
||
// That's it! An Associated Type is just
|
||
// different syntax for Generics in protocols
|
||
associatedtype Power: Initializable
|
||
|
||
func attack() -> Power
|
||
}
|
||
|
||
extension PowerTrait {
|
||
// with protocol extensions,
|
||
// we can now have a default attack function
|
||
func attack() -> Power {
|
||
return Power()
|
||
}
|
||
}</pre><p></p>
|
||
<p>Now, any Pokemon that conforms to the <code>PowerTrait</code> protocols will have the attack functionality no subclassing necessary!</p>
|
||
<p></p><pre class="crayon-plain-tag">struct Pikachu: PowerTrait {
|
||
// since we're using the default attack functionality
|
||
// we have to specify the type of the associated type
|
||
// just like we did when subclassing with a generic
|
||
// * note that this is still called typealias, but will be changed
|
||
// to associatedtype in future versions of Swift
|
||
associatedtype Power = <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f329.png" alt="🌩" class="wp-smiley" style="height: 1em; max-height: 1em;" />
|
||
}
|
||
let pikachu = Pikachu()
|
||
pikachu.attack() //<img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f329.png" alt="🌩" class="wp-smiley" style="height: 1em; max-height: 1em;" />
|
||
|
||
struct Vaporeon: PowerTrait {
|
||
// when the attack function is overwritten
|
||
// <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f327.png" alt="🌧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> is inferred as the associated type
|
||
// based on the method signature
|
||
func attack() -> <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f327.png" alt="🌧" class="wp-smiley" style="height: 1em; max-height: 1em;" /> {
|
||
// custom attack logic
|
||
return <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f327.png" alt="🌧" class="wp-smiley" style="height: 1em; max-height: 1em;" />()
|
||
}
|
||
}
|
||
let vaporeon = Vaporeon()
|
||
vaporeon.attack() //<img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f327.png" alt="🌧" class="wp-smiley" style="height: 1em; max-height: 1em;" /></pre><p></p>
|
||
<h2>Conclusion</h2>
|
||
<p>That’s it! A protocol with associated type is just a fancy term to a protocol that has generics in it. By using PATs (and protocols in general), we have a powerful tool that promotes really nice composition in favor of messy inheritance. </p>
|
||
<p>To learn more about the limitations of PATs and go deeper, I highly recommend <a href="https://www.youtube.com/watch?v=XWoNjiSPqI8" target="_blank">Alexis Gallagher’s talk here</a>. </p>
|
||
<p>Happy catching!</p>
|
||
<p><em><a href="http://www.tryswiftnyc.com/" target="_blank">Join me</a> for a Swift Community Celebration <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> in New York City on September 1st and 2nd. Use code <strong>NATASHATHEROBOT</strong> to get $100 off!</em></p>
|
||
]]></content:encoded>
|
||
<wfw:commentRss>https://www.natashatherobot.com/swift-what-are-protocols-with-associated-types/feed/</wfw:commentRss>
|
||
<slash:comments>5</slash:comments>
|
||
<post-id xmlns="com-wordpress:feed-additions:1">6473</post-id> </item>
|
||
</channel>
|
||
</rss>
|