{ "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "items": [ { "originId": "com.sixcolors.931113_features_of_ios_13_files_improvements", "fingerprint": "b321d04b", "id": "Ai/HDbZBn/DqS4YSFb8RbnuS8su16El+mi83Mpt/WqQ=_16d88a4dca2:50e96:18991ffa", "author": "Jason Snell", "summary": { "direction": "ltr", "content": "
\nThe Finder places files and folders at the center of the Mac, but on iOS, apps are at the center. Still, managing documents is a fact of life in many cases, and over the past few years Apple has been evolving the Files app to become a more full-featured file browser utility. In iOS 13, Files takes a huge step forward in numerous areas\u2026 though there\u2019s still more to be done.
\nPerhaps most important is the simple fact that Files can now see destinations that aren\u2019t cloud services or other apps. You can add local SMB file servers to Files by tapping the ellipsis icon in the Browse pane and choosing Connect to Server, then entering the address of your SMB server. While you\u2019re connected, that server will appear in the Shared segment of the Browse pane. (Strangely, Files doesn\u2019t use Bonjour to detect nearby servers and display them, as Finder does.) I have a Mac mini on my home network that I use as a file server, and it\u2019s been a delight to access files on it, directly, from within Files and apps that use Apple\u2019s file interface.
\n\nUSB drives are also supported. It\u2019s kind of hard to believe that I\u2019m celebrating USB disk access in late 2019, but here we are. You can attach USB drives to any device running iOS 13, but this feature certainly feels best when you plug a USB-C cable or thumbdrive directly into an iPad Pro. As an iPad Pro user, that\u2019s a moment that really makes the iPad Pro feel like it\u2019s been welcomed into the community of personal computers. And if you\u2019re someone who has ever been handed a thumbdrive by a colleague who expects you to access it on your iPad, well, now you can do that instead of sheepishly admitting that it\u2019s completely useless to you. I\u2019ve used this feature to attach my portable audio recorder directly to my iPad to import recordings, something I previously had to use a breakout box to accomplish.
\nYou can even create new folders now. Yes. It\u2019s true. And there\u2019s a new Column View, which is an approach to file browsing that I\u2019ve never liked on macOS, but actually makes more sense to me on iOS for some reason.
\n\niOS 13 also lets users perform many more actions on files than ever before by tapping and holding on an icon to reveal a contextual menu. Among the actions found here are options to compress files into an archive, decompress zip files, edit tags, preview a file in Quick Look, and display an Info pane with detailed information about a file\u2019s attributes\u2014basically, the stuff you\u2019d expect from a file browser is mostly there. (It\u2019s a bit strange that you can\u2019t set items from Shortcuts to display directly in this contextual menu, as you can in the share sheets elsewhere on iOS 13. Instead, you have to tap and hold on a file, choose Share, and then pick a Shortcuts item.
\nFiles separates iOS storage into two buckets, On My iPad/iPhone and iCloud Drive. On My iPad is basically what you\u2019d consider \u201cthe hard drive\u201d on a Mac\u2014it\u2019s local storage that is not synced over the cloud. If you want to save a huge file on your iPad and not have it swamp your current connection in an attempt to sync all that data to the cloud, put it in On My iPad/iPhone. If you want it available everywhere, put it in iCloud Drive.
\nAlas, not all is sunshine and roses in the land of iOS file access. Files is still a remarkably immature app. It sometimes fails to update file listings, frequently stalls out and provides me with a blank or incomplete listing, and, most frustratingly, the Save to Files extension for third-party apps fails to provide any feedback about the progress of a file transfer. That unreliability, combined with a slow file transfer to a remote server, leads to some pretty uncomfortable moments when you have no idea if your file is going to arrive or if the whole thing has failed silently.
\nApple\u2019s taken a few cues from the Mac in building up Files, so it\u2019s time to take a few more. Progress indicators are vital. Allowing the user to get a detailed view about what\u2019s transferring and how long it will take are must-have features, but when I try to save items within third-party apps, all I get is an endless spinner with no feedback. I realize this isn\u2019t an issue with the Files app itself\u2014it does show a little circular upload/download progress bar\u2014but it is an issue with Apple\u2019s greater approach to file transfers.
\nStill, Files has come a long way. It has gone from being an iCloud Drive client app to a neither-fish-nor-fowl representation of Apple\u2019s ambivalence to file management on iOS to what it is today\u2014a pretty capable file browser that\u2019s still got plenty of room for improvement. Files in iOS 13 is a major upgrade\u2014I just hope Apple doesn\u2019t consider the job done.
" }, "alternate": [ { "href": "https://sixcolors.com/post/2019/10/13-features-of-ios-13-files-improvements/", "type": "text/html" } ], "crawled": 1569955568802, "title": "13 Features of iOS 13: Files improvements", "published": 1569952320000, "origin": { "streamId": "feed/http://feedpress.me/sixcolors", "htmlUrl": "https://www.sixcolors.com/", "title": "Six Colors" }, "unread": false, "readTime": 4981, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815", "label": "Macintosh" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" } ], "actionTimestamp": 1572579193115 }, { "originId": "tag:daringfireball.net,2019://1.36188", "fingerprint": "ae19d945", "id": "ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16e052937dc:173f1:d4506071", "updated": 1572042321000, "author": "John Gruber", "summary": { "direction": "ltr", "content": "What\u2019s the point of having only two buttons and all that unused whitespace on the left side?" }, "alternate": [ { "href": "https://daringfireball.net/2019/10/mails_message_action_toolbar_in_ios_13", "type": "text/html" } ], "crawled": 1572044617692, "title": "\u2605 The Curious Design of Mail\u2019s Message Action Toolbar in iOS 13", "published": 1572042320000, "origin": { "streamId": "feed/http://daringfireball.net/index.xml", "htmlUrl": "https://daringfireball.net/", "title": "Daring Fireball" }, "content": { "direction": "ltr", "content": "David Ingram, writing for NBC News:
\n\n\nShannon Watts always replied to emails the same way: by touching\nthe reply icon, tucked in a familiar spot near the bottom right\ncorner of her iPhone. Then, one day a few weeks ago, the icon\nwasn\u2019t there\u2009\u2014\u2009and neither was the email. It was deleted by\naccident.
\nIt\u2019s happened dozens of times since, frustrating Watts and many\nother iPhone users who\u2019ve been tripped up by a minor change rolled\nout last month by Apple, a company renowned for its\nforward-thinking design.
\nIn the newest version of the iPhone email app, the trash icon is\nnow where the reply icon used to be. And they\u2019re too close\ntogether for some people.
\n
The change is perfectly illustrated and summarized in this tweet by Craig Hockenberry:
\n\n\nMuscle memory is a bitch.
\n\n
Things worth noting:
\nThe new toolbar in iOS 13 Mail is just strange. The old toolbar had discrete buttons for Flag, Move, Trash/Archive, Reply, and New Message. Now it\u2019s just Trash and Reply, with all of the other functionality stashed in the new Reply action sheet, pictured here half-height and full-height. That new \u201cReply\u201d action sheet is really a \u201cDo Something With This Message\u201d sheet\u2009\u2014\u2009I\u2019m not sure what the icon for this should be, but the Reply icon seems like an odd choice. I know a few people who assumed that iOS 13 removed the ability to move messages to other mailboxes because the folder button was removed from the toolbar. They\u2009\u2014\u2009reasonably!\u2009\u2014\u2009never thought to look for it by tapping what clearly looks like the old familiar Reply icon.
\nThe Print command has long been stashed in the Reply action sheet\u2009\u2014\u2009so arguably it\u2019s always been more of a \u201cDo Something With This Message\u201d button than just a \u201cReply or Forward\u201d button. But the iOS 13 Mail toolbar takes this to an extreme. It\u2019s one thing to put new features (for which there\u2019s no room on the toolbar) in the Reply action sheet; it\u2019s another to move commands like Flag and Move that already had positions on the toolbar.
\nI like the new \u201cDo Something With This Message\u201d action sheet in and of itself a lot\u2009\u2014\u2009it\u2019s an interesting design to fit more functionality in the limited screen real estate of the iPhone. There are a lot of apps that have run out of space in their toolbars that could borrow from this design. I particularly like that in the new action sheet, all the actions are labeled with words in addition to icons. But iOS 13 should have included a first-run explainer showing users where these features moved to.
\nAnd it just seems odd to me that they moved all these features there in the first place. The iPhone really only has room for five toolbar buttons. Flag, Move, Trash, Reply, and New Message seemed like good ones. What\u2019s the point of having only two buttons and all that unused whitespace on the left side? In addition to the fact that it\u2019s not intuitive to look for Flag and Move commands behind a button that clearly looks like \u201cReply\u201d, it\u2019s also a bit frustrating to me that there\u2019s no longer a way to just create a new message from this screen\u2009\u2014\u2009you have to go back one level in the navigation controller to the list of messages to create a new (non-reply) message.
\nAt the very least, if the toolbar is only going to have these two buttons, why not place the Trash button on the far left, and put the whitespace between the two buttons? That would eliminate inadvertent taps on the Trash button from either pre-iOS 13 muscle memory or from proximity to the Reply button.
" }, "visual": { "url": "http://b.vimeocdn.com/ts/451/794/451794956_1280.jpg", "width": 1280, "height": 720, "contentType": "image/jpeg" }, "unread": false, "readTime": 3129, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" } ], "actionTimestamp": 1572579174490 }, { "originId": "tag:daringfireball.net,2019:/linked//6.36197", "recrawled": 1572296763725, "updateCount": 1, "fingerprint": "298625a9", "id": "ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16e13f9b1a7:19a2c:d4506071", "updated": 1572296240000, "author": "John Gruber", "alternate": [ { "href": "https://www.apple.com/airpods-pro/", "type": "text/html" } ], "crawled": 1572293161383, "title": "Apple\u2019s AirPods Pro Web Page: Scrolljacking Hell", "published": 1572289670000, "origin": { "streamId": "feed/http://daringfireball.net/index.xml", "htmlUrl": "https://daringfireball.net/", "title": "Daring Fireball" }, "content": { "direction": "ltr", "content": "The AirPods Pro \u201coverview\u201d web page is a strange beast. It pegs my 2015 MacBook Pro\u2019s CPU\u2009\u2014\u2009even when I\u2019m not scrolling. I closed the tab a few minutes ago and my fan is still running. The animation is very jerky and scrolling feels so slow. There\u2019s so much scrolljacking that you have to scroll or page down several times just to go to the next section of the page. The animation is at least smooth on my iPad and iPhone, but even there, it feels like a thousand swipes to get to the bottom of the page. It\u2019s a design that makes it feel like they don\u2019t want you to keep reading.
\nDisable JavaScript (easily toggled if you enable Safari\u2019s Develop menu) and the page is easy to read and looks great. I can\u2019t recall an example where scrolljacking makes a website so much worse.
\nUpdate: Nick Heer (of Pixel Envy fame) messaged me to point out that the iPad Pro product page gives the AirPods Pro page a run for its money for top spot in the Scrolljacking Hall of Shame. The iPad Pro page doesn\u2019t peg my MacBook Pro\u2019s CPU, but it scrolls the view horizontally while you scroll vertically.
\n" }, "visual": { "url": "http://cdn1.sbnation.com/entry_photo_images/9188405/LG_G_Flex-3_large_verge_medium_landscape.jpg", "width": 640, "height": 426, "contentType": "image/jpg" }, "unread": false, "readTime": 6570, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" } ], "actionTimestamp": 1572579162176 }, { "originId": "tag:daringfireball.net,2019:/linked//6.36201", "fingerprint": "dec1a24f", "id": "ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16e15437a6d:19e93:d4506071", "updated": 1572313520000, "author": "John Gruber", "alternate": [ { "href": "https://popular.info/p/facebook-allows-prominent-right-wing", "type": "text/html" } ], "crawled": 1572314774125, "title": "Facebook Allows Prominent Right-Wing Website to Break the Rules", "published": 1572313519000, "origin": { "streamId": "feed/http://daringfireball.net/index.xml", "htmlUrl": "https://daringfireball.net/", "title": "Daring Fireball" }, "content": { "direction": "ltr", "content": "Judd Legum, writing for Popular Info:
\n\n\nThe Daily Wire, the right-wing website founded by pundit Ben\nShapiro, is a cesspool of misogyny, bigotry, and misinformation.\nIts toxic content is also fantastically successful on Facebook,\nwith each story reaching more people than any other major media\noutlet. A Popular Information investigation reveals some of this\nsuccess is attributable to a clandestine network of 14 large\nFacebook pages that purport to be independent but exclusively\npromote content from The Daily Wire in a coordinated fashion.
\nThis kind of \u201cinauthentic coordinated behavior\u201d violates\nFacebook\u2019s rules. Facebook has taken down smaller and less\ncoordinated networks that promoted liberal content. But Facebook\ntold Popular Information that it will continue to allow this\nnetwork to operate and amplify The Daily Wire\u2019s content.
\n
As a complete sidenote to the main point of this\u2009\u2014\u2009that Facebook is a right-wing company\u2009\u2014\u2009notice how nice and clean and fast the Popular Info website is. The best websites these days aren\u2019t from web publishers\u2009\u2014\u2009they\u2019re from mailing list publishers with websites.
\n" }, "unread": false, "readTime": 1124, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572579157406 }, { "originId": "tag:daringfireball.net,2019://1.36209", "recrawled": 1572448047706, "updateCount": 2, "fingerprint": "3576a6cd", "id": "ZTHt7g74IlVC5A2IgEvcn/aop5teo99gzFaGU2TCGxs=_16e1b466c6b:1b876:d4506071", "updated": 1572447167000, "author": "John Gruber", "summary": { "direction": "ltr", "content": "Apple invited a few dozen media folks to New York today for a briefing and early access to the new AirPods Pro. My initial impression: I like them." }, "alternate": [ { "href": "https://daringfireball.net/2019/10/airpods_pro_first_impressions", "type": "text/html" } ], "crawled": 1572415630443, "title": "\u2605 AirPods Pro First Impressions", "published": 1572407940000, "origin": { "streamId": "feed/http://daringfireball.net/index.xml", "htmlUrl": "https://daringfireball.net/", "title": "Daring Fireball" }, "content": { "direction": "ltr", "content": "Apple invited a few dozen media folks to New York today for a briefing and early access to the new AirPods Pro. My initial impression: I like them.
\nI left for home around 2:30 in the afternoon, and wore the AirPods Pro for the next three hours: on the subway in Manhattan, waiting (briefly, mercifully) in the cacophonous Penn Station, on the train ride home to Philadelphia, walking home through Center City Philadelphia, and then in my house. The subway, a train ride, and busy city streets are pretty good tests for noise cancellation.
\nNoise cancellation worked really well for me. I own a pair of Bose over-the-ear noise canceling wireless headphones, but almost exclusively wear them only on airplanes and trains. Wearing noise-canceling earbuds on the subway and walking through the city is going to take some getting used to. It\u2019s so good you really do lose sense of your surrounding aural environment.
\nI was a dummy and didn\u2019t take my Bose headphones on my trip today, so I can\u2019t say how they compare side-by-side on the train, but there\u2019s no question how AirPods Pro compare to regular AirPods. The difference is like night and day. Amtrak trains are pretty noisy\u2009\u2014\u2009especially at what we in the U.S. so adorably consider \u201chigh speeds\u201d\u2009\u2014\u2009but with AirPods Pro the clackety-clack rumble was effectively blocked out.
\nThe \u201cTransparency\u201d mode is interesting and a little mind-bending. It really does make it possible to conduct a conversation while still enjoying the benefits of noise cancellation. Because the silicone tips seal against your inner ear, when you turn AirPods Pro noise cancellation completely off, you really can\u2019t hear much around you. They\u2019re like earplugs. Transparency lets you hear parts of the world around you. One obvious use case for this: jogging or running and maybe just plain walking on streets where you want to hear the sounds of traffic.
\nMy corner store has a noisy refrigeration unit. With AirPods Pro on\u2009\u2014\u2009playing nothing\u2009\u2014\u2009I couldn\u2019t hear it at all. I couldn\u2019t tell that my dishwasher was running even though I was sitting right across from it in my kitchen. As someone who doesn\u2019t generally write while listening to music, I\u2019m likely to use AirPods Pro, playing nothing, just to tune out the world around me in a noisy space.
\nThe force sensor\u2009\u2014\u2009the flat section on the earbuds stem that faces forward when in your ear\u2009\u2014\u2009is effectively a button. But it\u2019s not a button. It doesn\u2019t actually move, and it doesn\u2019t provide haptic feedback. But it acts like a button and\u2009\u2014\u2009most importantly\u2009\u2014\u2009sounds like a button. When you press it, the AirPod Pro plays a click. I use the singular AirPod there because the click only plays in the bud whose force sensor you pressed. The effect is uncannily like clicking a real button. In a similar way to how force touch trackpads on modern MacBooks and Touch ID iPhone home buttons feel like they truly click, the AirPods Pro force sensors feel like actual clicking buttons. They actually have more of a premium clicky feel than the truly clicking buttons on Apple\u2019s wired EarPods, even though they don\u2019t actually click. It\u2019s uncanny, and Apple at its best.
\nAnother nice Apple-at-its-best touch: in Control Center on iOS, you can long-press the volume control while wearing AirPods Pro to get a nice little three-way selector to choose between noise cancellation, off, and transparency. The selection indicator animates nicely, the sounds are delightful (although you can\u2019t hear them in the movie linked above), and you can change the setting both by tapping another option or by dragging the selection indicator. It\u2019s a simple little interaction done exquisitely well.
\nForce sensor actions:
\nBy default, press-and-hold toggles between regular noise cancellation and transparency modes. That means, by default, the only way to invoke Siri is through the \u201cHey Siri\u201d verbal command. But if you want to invoke Siri through a long-press, you can change that in the Bluetooth section of Settings on your iPhone or iPad. And, you can change it per-ear\u2009\u2014\u2009so you can have your left AirPod Pro toggle transparency and the right one invoke Siri.
\nAlso in the Bluetooth settings is the Ear Tip Fit Test. It\u2019s very easy. Put the AirPods Pro in your ears, and start the test. It plays a song for about five seconds and decides whether you have a good fit with the current size tips. There\u2019s nothing \u201csmart\u201d about the silicone tips themselves\u2009\u2014\u2009the AirPods Pro don\u2019t \u201cknow\u201d which size tips you\u2019re currently wearing. The Fit Test just tells you if the current ones in your ear are a good fit. For me, the default medium tips feel best and the Ear Tip Fit Test consistently agrees. For my son, the medium tips felt uncomfortable, and the Fit Test agreed they weren\u2019t a good fit. For him, the small tips felt better and the Fit Test agreed. According to Apple, many people have differently-shaped ears and might need a different tip size for each ear, and if that\u2019s the case the Fit Test will suggest it.
\nSwapping the tips is easy, but it takes a bit more pull than I expected to pop them off. Don\u2019t be afraid\u2009\u2014\u2009the tips seem rugged. And replacement tips from Apple will cost only $4\u2009\u2014\u2009truly cheap.
\nThe AirPods Pro case is about 15% larger by volume than the regular AirPods case. That\u2019s unfortunate, but it\u2019s not noticeable in a regular pants pocket, and it still fits in the fifth pocket of a pair of Levi\u2019s 501 jeans.
\nBattery life, so far, is exactly in line with Apple\u2019s stated specs. My review unit started at 75% (both the buds and the case). After three straight hours of use, the buds were down to about 10%. So if three hours of use consumed two-thirds of the battery, a full charge should last about 4.5 hours\u2009\u2014\u2009which is exactly Apple\u2019s claim.
\nComfort-wise, my ears felt fine after those three consecutive hours of use. It\u2019s a very different feeling compared to regular AirPods, but I like it. I\u2019ve never had a problem with regular AirPods falling out of my ears, but AirPods Pro feel way more secure. Without question, how they feel is subjective\u2009\u2014\u2009so the good news is you can request a try-on in any Apple Store.
" }, "visual": { "url": "http://cdn1.sbnation.com/entry_photo_images/9188405/LG_G_Flex-3_large_verge_medium_landscape.jpg", "width": 640, "height": 426, "contentType": "image/jpg" }, "unread": false, "readTime": 4178, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" } ], "actionTimestamp": 1572579133239 }, { "originId": "tag:blogger.com,1999:blog-8954608646904080796.post-8772112161384908748", "fingerprint": "80075e70", "thumbnail": [ { "url": "https://1.bp.blogspot.com/-pMon836tLyo/XatsZ0Tv9MI/AAAAAAAADTs/90dBB5-ohkk2yOW4--zh7M0p_JicFuC4QCLcBGAsYHQ/s72-c/017.jpg", "width": 72, "height": 72 } ], "id": "v0v+7Ya8tssIZvd3/pcnFRr3HwvY/5YK3FGc2t65c0Y=_16de5ccc6bb:ffbc:d4506071", "updated": 1571515782582, "author": "Edward Feser", "alternate": [ { "href": "http://edwardfeser.blogspot.com/2019/10/masculinity-and-marvel-movies.html", "type": "text/html" } ], "crawled": 1571518465723, "title": "Masculinity and the Marvel movies", "published": 1571515740000, "origin": { "streamId": "feed/http://edwardfeser.blogspot.com/feeds/posts/default", "htmlUrl": "http://edwardfeser.blogspot.com/", "title": "Edward Feser" }, "content": { "direction": "ltr", "content": "\nUnder the argument for an iPhone subscription, which some people call Apple Prime after the Amazon program of the same name, Apple would bundle hardware upgrades with services like iCloud storage or Apple TV+ content and hardware for a single monthly fee. This would let it switch iPhone sales from a transactional model to a subscription model, potentially driving the stock price up without having to increase product sales or prices dramatically.\n\nAnd:\n
\n\u2033In terms of hardware as a service or as a bundle, if you will, there are customers today that essentially view the hardware like that because they\u2019re on upgrade plans and so forth,\u201d Cook said during an earnings call. \u201cSo to some degree that exists today.\u201d\n\nAnd, most importantly:\n
\n\u201cMy perspective is that will grow in the future to larger numbers. It will grow disproportionately\u201d\n\nI had the chance to be on John Gruber's show (recorded yesterday, guessing it'll drop today or tomorrow, assuming John is not too horrified with the results), and we were talking about this, peripherally. John mentioned the future possibility of Apple Prime, a concept similar to Amazon Prime. From the article:\n
\nUnder the argument for an iPhone subscription, which some people call Apple Prime after the Amazon program of the same name, Apple would bundle hardware upgrades with services like iCloud storage or Apple TV+ content and hardware for a single monthly fee.\n\nI suspect we'll all eventually be subscribing from a menu of services, including column A, software, column B, traditional services, and column C, hardware. Intriguing.\u221e Read this on The Loop" }, "alternate": [ { "href": "https://www.cnbc.com/2019/10/30/apple-lays-groundwork-for-iphone-or-apple-prime-subscription.html", "type": "text/html" } ], "crawled": 1572535435674, "title": "Apple is laying the groundwork for an iPhone subscription", "published": 1572532001000, "origin": { "streamId": "feed/http://www.loopinsight.com/feed/", "htmlUrl": "https://www.loopinsight.com", "title": "The Loop" }, "content": { "direction": "ltr", "content": "
Kif Leswing, CNBC:
\n\n\nUnder the argument for an iPhone subscription, which some people call Apple Prime after the Amazon program of the same name, Apple would bundle hardware upgrades with services like iCloud storage or Apple TV+ content and hardware for a single monthly fee. This would let it switch iPhone sales from a transactional model to a subscription model, potentially driving the stock price up without having to increase product sales or prices dramatically.
\n
And:
\n\n\n\u2033In terms of hardware as a service or as a bundle, if you will, there are customers today that essentially view the hardware like that because they\u2019re on upgrade plans and so forth,\u201d Cook said during an earnings call. \u201cSo to some degree that exists today.\u201d
\n
And, most importantly:
\n\n\n\u201cMy perspective is that will grow in the future to larger numbers. It will grow disproportionately\u201d
\n
I had the chance to be on John Gruber\u2019s show (recorded yesterday, guessing it\u2019ll drop today or tomorrow, assuming John is not too horrified with the results), and we were talking about this, peripherally. John mentioned the future possibility of Apple Prime, a concept similar to Amazon Prime. From the article:
\n\n\nUnder the argument for an iPhone subscription, which some people call Apple Prime after the Amazon program of the same name, Apple would bundle hardware upgrades with services like iCloud storage or Apple TV+ content and hardware for a single monthly fee.
\n
I suspect we\u2019ll all eventually be subscribing from a menu of services, including column A, software, column B, traditional services, and column C, hardware. Intriguing.
\n" }, "visual": { "url": "https://cdn.vox-cdn.com/thumbor/fhKs50EFS0SJPjSjfCR7lXwcMfs=/0x0:2040x1360/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59667903/acastro_180508_1777_google_IO_0002.0.jpg", "width": 1310, "height": 873, "contentType": "image/jpeg" }, "unread": false, "readTime": 20737, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572578952687 }, { "keywords": [ "Apple" ], "originId": "https://9to5mac.com/?p=617610", "fingerprint": "48affe9c", "id": "BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16e1de23321:1c6e5:d4506071", "author": "Chance Miller", "summary": { "direction": "ltr", "content": "\nMicrosoft is planning several updates to its Outlook app for iPhone and iPad. As explained by The Verge, Microsoft will roll out features such as iPad Split View, smart folders, and more to the Outlook app over the coming weeks.
\n\nThe post Microsoft Outlook for iPhone and iPad adding Split View, Do Not Disturb, more appeared first on 9to5Mac.
" }, "alternate": [ { "href": "https://9to5mac.com/2019/10/30/microsoft-outlook-split-view-more/", "type": "text/html" } ], "crawled": 1572459393825, "title": "Microsoft Outlook for iPhone and iPad adding Split View, Do Not Disturb, more", "published": 1572458509000, "origin": { "streamId": "feed/http://9to5mac.com/feed/", "htmlUrl": "https://9to5mac.com", "title": "9to5Mac" }, "unread": false, "readTime": 13858, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815", "label": "Macintosh" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572578905515 }, { "keywords": [ "Miscellaneous" ], "originId": "https://nshipster.com/device-identifiers", "fingerprint": "5e66b947", "id": "08l+9ftpGejQ9f/2DZ6dom5rSnNJJO9OCox6I3nUnWg=_16e22ccc11a:1e495:d4506071", "updated": 1572505200000, "author": "Mattt", "summary": { "direction": "ltr", "content": "For every era, there\u2019s a monster that embodies the anxieties of the age.
" }, "alternate": [ { "href": "https://nshipster.com/device-identifiers/", "type": "text/html" } ], "crawled": 1572541874458, "title": "Device Identifiers and Fingerprinting on iOS", "published": 1572505200000, "origin": { "streamId": "feed/http://nshipster.com/feed.xml", "htmlUrl": "https://nshipster.com/", "title": "NSHipster" }, "content": { "direction": "ltr", "content": "For every era,\nthere\u2019s a monster that embodies the anxieties of the age.
\nAt the dawn of the Holocene,\nour ancestors traced the contours of shadows cast by the campfire\nas they kept watch over the darkness.\nOnce we learned to read the night sky for navigation,\nsailors swapped stories of sea creatures like\nLeviathan and\nSiren\nto describe the dangers of open ocean\n(and the perils to be found on unfamiliar shores).
\nFrankenstein\u2019s monster\nwas as much the creation of Mary Shelley\nas it was a spiritual collaboration with\nLuigi Galvani.\nAnd Bram Stoker\u2019s\nfictionalized account of the mummy\u2019s curse\nwas more a response to the\nEgyptomania\nand European colonialism\nof the nineteenth century\nthan any personal account of the Middle Kingdom.
\nMore recently,\nthe \u201cmonster ruins a beach party\u201d\ntrope of the 1960s\narose from concerns of teenager morality.\nWhile the\nMartians\nwho invaded those same drive-in double features\nserved as a proxy for Cold War fears at the height of the\nSpace Race.
\nAll of which begs the question:\n\u201cWhat monster best exemplifies our present age?\u201d
\nConsider the unnamed monster from the film\nIt Follows:\na formless, supernatural being that relentlessly pursues its victims\nanywhere on the planet.
\nSounds a bit like the state of\nad tech\nin 2019, no?
\n\nThis week on NSHipster \u2014\nin celebration of our favorite holiday\n\ud83c\udf83 \u2014\nwe\u2019re taking a look at the myriad ways that\nyou\u2019re being tracked on iOS,\nboth sanctioned and unsanctioned,\nhistorically and presently.\nSo gather around the campfire,\nand allow us to trace the contours of the unseen, formless monsters\nthat stalk us under cover of Dark Mode.
\nContrary to our intuitions about natural selection in the marketplace,\nhistory is littered with examples of\ninferior-but-better-marketed products winning out over superior alternatives:\nVHS vs. Betamax,\nWindows vs. Macintosh,\netc.\n(According to the common wisdom of business folks, at least.)\nRegardless,\nmost companies reach a point where\n\u201cif you build it, they will come\u201d\nceases to be a politically viable strategy,\nand someone authorizes a marketing budget.
\nMarketers are tasked with growing market share\nby identifying and communicating with as many potential customers as possible.\nAnd many \u2014\neither out of a genuine belief or formulated as a post hoc rationalization \u2014\ntake the potential benefit of their product\nas a license to flouting long-established customs of personal privacy.\nSo they enlist the help of one or more\nadvertising firms,\nwho promise to maximize their allocated budget and\nprovide some accountability for their spending\nby using technology to\ntarget,\ndeliver, and\nanalyze\nmessaging to consumers.
\nEach of these tasks is predicated on a consistent identity,\nwhich is why advertisers go to such great lengths to track you.
\nApple\u2019s provided various APIS to facilitate user identification\nfor various purposes:
\nIn the early days of iOS,\nApple provided a unique
property on UIDevice
\u2014\naffectionately referred to as a\nUDID\n(not to be confused with a UUID).\nAlthough such functionality seems unthinkable today,\nthat property existed until iOS 5,\nuntil it was\ndeprecated and replaced by identifier
in iOS 6.
Starting in iOS 6,\ndevelopers can use the\nidentifier
property on UIDevice
\nto generate a unique identifier that\u2019s shared across apps and extensions\ncreated by the same vendor\n(IDFV).
import UIKit\n let idfv = UIDevice.current.identifierForVendor // BD43813E-CFC5-4EEB-ABE2-94562A6E76CA\n
\n\nAlong with identifier
came the introduction of a new\nAdSupport framework,\nwhich Apple created to help distinguish\nidentification necessary for app functionality\nfrom anything in the service of advertising.
The resulting\nadvertisingidentifier
property\n(affectionately referred to as\nIDFA by its associates)\ndiffers from identifier
\nby returning the same value for everyone.\nThe value can change, for example,\nif the user resets their Advertising Identifier\nor erases their device.
import AdSupport \n let idfa = ASIdentifierManager .shared().advertisingIdentifier \n
\nIf advertising tracking is limited,\nthe property returns a zeroed-out UUID instead.
\nidfa.uuidString == "00000000-0000-0000-0000-000000000000" // true if the user has limited ad tracking\n
\n\n\nidentifier
and advertising
\nprovide all the same functionality as the unique
property\nthey replaced in iOS 6,\nsave for one:\nthe ability to persist across device resets and app uninstalls.
In iOS 11,\nApple quietly introduced the\nDeviceCheck framework,\nwhich allows developers to assign two bits of information\nthat are persisted by Apple\nuntil the developer manually removes them.
\nInteracting with the DeviceCheck framework should be familiar to\nanyone familiar with APNS:\nafter setting things up on App Store Connect and your servers,\nthe client generates tokens on the device,\nwhich are sent to your servers to set or query two bits of information:
\nimport DeviceCheck \n let device = DCDevice.current\n if device.isSupported {\n device.generateToken { data, error in\n if let token = data?.base64EncodedString () {\n send token to your server\n }\n }\n }\n
\nBased on the device token and other information sent by the client,\nthe server tells Apple to set each bit value\nby sending a JSON payload like this:
\n{\n"device_token": "QTk4QkFDNEItNTBDMy00Qjc5LThBRUEtMDQ5RTQzRjNGQzU0Cg==" ,\n"transaction_id": "D98BA630-E225-4A2F-AFEC-BE3A3D591708",\n"timestamp": 1572531720,\n"bit0": true,\n"bit1": false\n}\n
\nTo retrieve those two bits at a later point in time,\nthe server sends a payload without bit0
and bit1
fields:
{\n"device_token": "QTk4QkFDNEItNTBDMy00Qjc5LThBRUEtMDQ5RTQzRjNGQzU0Cg==" ,\n"transaction_id": "D98BA630-E225-4A2F-AFEC-BE3A3D591708",\n"timestamp": 1572532500\n}\n
\nIf everything worked,\nApple\u2019s servers would respond with a 200
status code\nand the following JSON payload:
{\n"bit0" : true\n"bit1" : false,\n"last_update_time" : "2019-10"\n}\n
\n\nDespite these affordances by Apple,\nadvertisers have continued to work to circumvent user privacy protections\nand use any and all information at their disposal\nto identify users by other means.
\nOver the years,\nApple\u2019s restricted access to information about\ndevice hardware,\ninstalled apps,\nnearby WiFi networks.\nThey\u2019ve required apps to request permission to\nget your current location,\naccess your camera and microphone,\nflip through your contacts, and\nfind and connect to Bluetooth accessories.\nThey\u2019ve taken bold steps to prevent user tracking in Safari.
\nFor lack of this information,\ncompanies have had to get creative,\nlooking to forge unique identities from the scraps of what\u2019s still available.\nThis process of identification by a combination of external factors\nis known as fingerprinting.
\nThe unfortunate reality is that we can be uniquely identified\nby vanishingly small amounts of information.\nFor example,\nindividuals within a population can be singled out by as few as\nfour timestamped coordinates\n(de Montjoye, Hidalgo, Verleysen, & Blondel, 2013)\nor little more than a birthday and a ZIP code\n(Sweeney, 2000).
\nEvery WWDC since 2012 has featured a session about Privacy,\nbut the only mention of fingerprinting specifically was\na brief discussion in 2014\nabout how to avoid doing it.
\nBy our count,\na determined party could use conventional, unrestricted APIs\nto generate a few dozen bits of randomness:
\nLocale information is the greatest source of identifying information\non Apple platforms.\nThe combination of your\npreferred languages, region, calendar, time zone,\nand which keyboards you have installed\nsay a lot about who you are \u2014\nespecially if you have less conventional preferences.
\nimport Foundation\n Locale.current.languageCode \n log2(Double(Locale.isoLanguageCodes .count)) // 9.217 bits\n Locale.current.regionCode \n log2(Double(Locale.isoRegionCodes .count)) // 8 bits\n Locale.current.calendar.identifier\n // ~2^4 (16) Calendars\n TimeZone .current.identifier\n log2(Double(TimeZone .knownTimeZoneIdentifiers .count)) // 8.775 bits\n UserDefaults .standard.object(forKey : "AppleKeyboards" )\n // ~2^6 (64) iOS keyboards \n
\n\nAccessibility preferences also provide a great deal of information,\nwith each individual setting contributing a single potential bit:
\nUIAccessibility.isBoldTextEnabled \n UIAccessibility.isShakeToUndoEnabled \n UIAccessibility.isReduceMotionEnabled \n UIAccessibility.isDarkerSystemColorsEnabled \n UIAccessibility.isReduceTransparencyEnabled \n UIAccessibility.isAssistiveTouchRunning \n
\nOf the approximately ~25% of users who take advantage of\nDynamic Type\nby configuring a preferred font size,\nthat selection may also be used to fingerprint you:
\nlet application = UIApplication.shared\n application.preferredContentSizeCategory \n
\nAlthough most of the juiciest bits have been locked down\nin OS updates over the years,\nthere\u2019s just enough to contribute a few more bits for purposes of identification.
\nOn iOS,\nyou can get the current model and amount of storage of a user\u2019s device:
\nimport UIKit\n let device = UIDevice.current\n device.name // "iPhone 11 Pro" \n let fileManager = FileManager .default\n if let path = fileManager .urls(for: .libraryDirectory , in: .systemDomainMask ).last?.path,\n let systemSize = try? fileManager .attributesOfFileSystem (forPath : path)[.systemSize ] as? Int\n {\n Measurement<UnitInformationStorage >(value: Double(systemSize ), unit: .bytes)\n .converted(to: .gigabytes) // ~256GB\n }\n
\nWith 14 supported iOS devices,\nmost having 3 configurations each,\nlet\u2019s say that this contributes about 32 possibilities, or 5 bits.
\nYou can go a few steps further on macOS,\nto further differentiate hardware by its processor count and amount of RAM:
\nprocessInfo .processorCount // 8\n Measurement<UnitInformationStorage >(value: Double(processInfo .physicalMemory ),\n unit: .bytes)\n .converted(to: .gigabytes) // 16GB\n
\nIt\u2019s hard to get a sense of\nhow many different Mac models are in use,\nbut a reasonable estimate would be on the order of 26 or 27.
\nKnowing whether someone\u2019s phone is on Verizon or Vodaphone\ncan also be factored into a fingerprint.\nYou can use the CTTelephony
class from the\nCoreTelephony framework\nto lookup the providers for devices with cellular service:
import CoreTelephony \n let networkInfo = CTTelephonyNetworkInfo ()\n let carriers = networkInfo .serviceSubscriberCellularProviders ?.values\n carriers?.map { ($0.mobileNetworkCode , $0.mobileCountryCode ) }\n
\nThe number of providers varies per country,\nbut using the 4 major carriers in United States\nas a guideline,\nwe can say carrier information would contribute about 2 bits\n(or more if you have multiple SIM cards installed).
\nMore generally,\neven knowing whether someone can send texts or email at all\ncan be factored into a fingerprint.\nThis information can be gathered without permissions via\nthe MessageUI framework.
\nimport MessageUI \n MFMailComposeViewController .canSendMail ()\n MFMessageComposeViewController .canSendText ()\n
\nIf the use of digital fingerprinting seems outlandish,\nthat\u2019s just scratching the surface of how companies and researchers\nhave figured out how to circumvent your privacy.
\nAlthough access to geolocation through conventional APIs\nrequires explicit authorization,\nthird parties may be able to get a general sense of where you are in the world\nbased on how you access the Internet.
\nGeolocation by source IP address\nis used extensively for things like region locking and localization.\nYou could also combine this information with\nping-time measurements\nto hosts in known locations\nto get a more accurate pinpoint on location (Weinberg, Cho, Christin, Sekar, & Gill, 2018):
\nping -c 5 99.24.18.13 # San Francisco, USA\n \n--- 99.24.18.13 ping statistics ---\n5 packets transmitted, 5 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 11.900/12.184/12.895/0.380 ms\nping -c 5 203.47.10.37 # Adelaide, Australia\n \n--- 203.47.10.37 ping statistics ---\n5 packets transmitted, 5 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 202.122/202.433/203.436/0.504 ms\n
\nIt\u2019s unclear whether this is a concern in iOS,\nbut depending on how precise the results of UIDevice
battery APIs are,\nyou may be able to use them to identify a device by its battery health.\n(Olejnik, Acar, Castelluccia, & Diaz, 2016)
var timestampedBatteryLevels : [(Date, Float)] = []\n if UIDevice.current.isBatteryMonitoringEnabled {\n timestampedBatteryLevels .append((Date(), UIDevice.current.batteryLevel ))\n }\n
\n\nEverything from your heartbeat, to your gait, to your\nbutt shape\nseem capable of leaking your identity.\nIt can all be quite overwhelming.
\nI mean,\nif a motivated individual can find your home address by\ncross-referencing the reflection in your eyes against Google Street view,\nhow can we even stand a chance out there?
\nMuch as we may bemoan the current duopoly of mobile operating systems,\nwe might take some solace in the fact that\nat least one of the players actually cares about user privacy.\nThough it\u2019s unclear whether that\u2019s a fight that can ever be won.
\nAt times,\nour fate of being tracked and advertised to\nmay seem as inevitable as the victims in It Follows.
\nBut let\u2019s not forget that,\nas technologists, as people with a voice,\nwe\u2019re in a position to fight back.
\n" }, "visual": { "url": "https://cdn.vox-cdn.com/thumbor/BkK9uk46u1ujrjGO0Jav3PCQ3g0=/0x225:5363x3800/1310x873/cdn.vox-cdn.com/uploads/chorus_image/image/59608177/PIA22227_full.0.jpg", "width": 1310, "height": 873, "contentType": "image/jpeg" }, "unread": true, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/885f2e01-d314-4e63-abac-17dcb063f5b5", "label": "Programming" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/7badfd54-6d65-421b-9a23-93dec726a308", "label": "Test for NetNewsWire" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572578876975 }, { "keywords": [ "Miscellaneous" ], "originId": "https://nshipster.com/device-identifiers", "fingerprint": "2f52765d", "id": "CmHb1hXBWguYpGAhzgwJM9xvPVSYJFbt7KLqF3nqYQ0=_16e22eb0ef0:1e551:d4506071", "updated": 1572505200000, "author": "Mattt", "summary": { "direction": "ltr", "content": "For every era, there\u2019s a monster that embodies the anxieties of the age.
" }, "alternate": [ { "href": "http://feedproxy.google.com/~r/NSHipster/~3/hXb7k2avhFE/", "type": "text/html" } ], "canonical": [ { "href": "https://nshipster.com/device-identifiers/", "type": "text/html" } ], "crawled": 1572543860464, "title": "Device Identifiers and Fingerprinting on iOS", "published": 1572505200000, "origin": { "streamId": "feed/http://feeds.feedburner.com/NSHipster", "htmlUrl": "https://nshipster.com/", "title": "NSHipster" }, "content": { "direction": "ltr", "content": "For every era,\nthere\u2019s a monster that embodies the anxieties of the age.
\nAt the dawn of the Holocene,\nour ancestors traced the contours of shadows cast by the campfire\nas they kept watch over the darkness.\nOnce we learned to read the night sky for navigation,\nsailors swapped stories of sea creatures like\nLeviathan and\nSiren\nto describe the dangers of open ocean\n(and the perils to be found on unfamiliar shores).
\nFrankenstein\u2019s monster\nwas as much the creation of Mary Shelley\nas it was a spiritual collaboration with\nLuigi Galvani.\nAnd Bram Stoker\u2019s\nfictionalized account of the mummy\u2019s curse\nwas more a response to the\nEgyptomania\nand European colonialism\nof the nineteenth century\nthan any personal account of the Middle Kingdom.
\nMore recently,\nthe \u201cmonster ruins a beach party\u201d\ntrope of the 1960s\narose from concerns of teenager morality.\nWhile the\nMartians\nwho invaded those same drive-in double features\nserved as a proxy for Cold War fears at the height of the\nSpace Race.
\nAll of which begs the question:\n\u201cWhat monster best exemplifies our present age?\u201d
\nConsider the unnamed monster from the film\nIt Follows:\na formless, supernatural being that relentlessly pursues its victims\nanywhere on the planet.
\nSounds a bit like the state of\nad tech\nin 2019, no?
\n\nThis week on NSHipster \u2014\nin celebration of our favorite holiday\n\ud83c\udf83 \u2014\nwe\u2019re taking a look at the myriad ways that\nyou\u2019re being tracked on iOS,\nboth sanctioned and unsanctioned,\nhistorically and presently.\nSo gather around the campfire,\nand allow us to trace the contours of the unseen, formless monsters\nthat stalk us under cover of Dark Mode.
\nContrary to our intuitions about natural selection in the marketplace,\nhistory is littered with examples of\ninferior-but-better-marketed products winning out over superior alternatives:\nVHS vs. Betamax,\nWindows vs. Macintosh,\netc.\n(According to the common wisdom of business folks, at least.)\nRegardless,\nmost companies reach a point where\n\u201cif you build it, they will come\u201d\nceases to be a politically viable strategy,\nand someone authorizes a marketing budget.
\nMarketers are tasked with growing market share\nby identifying and communicating with as many potential customers as possible.\nAnd many \u2014\neither out of a genuine belief or formulated as a post hoc rationalization \u2014\ntake the potential benefit of their product\nas a license to flouting long-established customs of personal privacy.\nSo they enlist the help of one or more\nadvertising firms,\nwho promise to maximize their allocated budget and\nprovide some accountability for their spending\nby using technology to\ntarget,\ndeliver, and\nanalyze\nmessaging to consumers.
\nEach of these tasks is predicated on a consistent identity,\nwhich is why advertisers go to such great lengths to track you.
\nApple\u2019s provided various APIS to facilitate user identification\nfor various purposes:
\nIn the early days of iOS,\nApple provided a unique
property on UIDevice
\u2014\naffectionately referred to as a\nUDID\n(not to be confused with a UUID).\nAlthough such functionality seems unthinkable today,\nthat property existed until iOS 5,\nuntil it was\ndeprecated and replaced by identifier
in iOS 6.
Starting in iOS 6,\ndevelopers can use the\nidentifier
property on UIDevice
\nto generate a unique identifier that\u2019s shared across apps and extensions\ncreated by the same vendor\n(IDFV).
import UIKit\n let idfv = UIDevice.current.identifierForVendor // BD43813E-CFC5-4EEB-ABE2-94562A6E76CA\n
\n\nAlong with identifier
came the introduction of a new\nAdSupport framework,\nwhich Apple created to help distinguish\nidentification necessary for app functionality\nfrom anything in the service of advertising.
The resulting\nadvertisingidentifier
property\n(affectionately referred to as\nIDFA by its associates)\ndiffers from identifier
\nby returning the same value for everyone.\nThe value can change, for example,\nif the user resets their Advertising Identifier\nor erases their device.
import AdSupport \n let idfa = ASIdentifierManager .shared().advertisingIdentifier \n
\nIf advertising tracking is limited,\nthe property returns a zeroed-out UUID instead.
\nidfa.uuidString == "00000000-0000-0000-0000-000000000000" // true if the user has limited ad tracking\n
\n\n\nidentifier
and advertising
\nprovide all the same functionality as the unique
property\nthey replaced in iOS 6,\nsave for one:\nthe ability to persist across device resets and app uninstalls.
In iOS 11,\nApple quietly introduced the\nDeviceCheck framework,\nwhich allows developers to assign two bits of information\nthat are persisted by Apple\nuntil the developer manually removes them.
\nInteracting with the DeviceCheck framework should be familiar to\nanyone familiar with APNS:\nafter setting things up on App Store Connect and your servers,\nthe client generates tokens on the device,\nwhich are sent to your servers to set or query two bits of information:
\nimport DeviceCheck \n let device = DCDevice.current\n if device.isSupported {\n device.generateToken { data, error in\n if let token = data?.base64EncodedString () {\n send token to your server\n }\n }\n }\n
\nBased on the device token and other information sent by the client,\nthe server tells Apple to set each bit value\nby sending a JSON payload like this:
\n{\n"device_token": "QTk4QkFDNEItNTBDMy00Qjc5LThBRUEtMDQ5RTQzRjNGQzU0Cg==" ,\n"transaction_id": "D98BA630-E225-4A2F-AFEC-BE3A3D591708",\n"timestamp": 1572531720,\n"bit0": true,\n"bit1": false\n}\n
\nTo retrieve those two bits at a later point in time,\nthe server sends a payload without bit0
and bit1
fields:
{\n"device_token": "QTk4QkFDNEItNTBDMy00Qjc5LThBRUEtMDQ5RTQzRjNGQzU0Cg==" ,\n"transaction_id": "D98BA630-E225-4A2F-AFEC-BE3A3D591708",\n"timestamp": 1572532500\n}\n
\nIf everything worked,\nApple\u2019s servers would respond with a 200
status code\nand the following JSON payload:
{\n"bit0" : true\n"bit1" : false,\n"last_update_time" : "2019-10"\n}\n
\n\nDespite these affordances by Apple,\nadvertisers have continued to work to circumvent user privacy protections\nand use any and all information at their disposal\nto identify users by other means.
\nOver the years,\nApple\u2019s restricted access to information about\ndevice hardware,\ninstalled apps,\nnearby WiFi networks.\nThey\u2019ve required apps to request permission to\nget your current location,\naccess your camera and microphone,\nflip through your contacts, and\nfind and connect to Bluetooth accessories.\nThey\u2019ve taken bold steps to prevent user tracking in Safari.
\nFor lack of this information,\ncompanies have had to get creative,\nlooking to forge unique identities from the scraps of what\u2019s still available.\nThis process of identification by a combination of external factors\nis known as fingerprinting.
\nThe unfortunate reality is that we can be uniquely identified\nby vanishingly small amounts of information.\nFor example,\nindividuals within a population can be singled out by as few as\nfour timestamped coordinates\n(de Montjoye, Hidalgo, Verleysen, & Blondel, 2013)\nor little more than a birthday and a ZIP code\n(Sweeney, 2000).
\nEvery WWDC since 2012 has featured a session about Privacy,\nbut the only mention of fingerprinting specifically was\na brief discussion in 2014\nabout how to avoid doing it.
\nBy our count,\na determined party could use conventional, unrestricted APIs\nto generate a few dozen bits of randomness:
\nLocale information is the greatest source of identifying information\non Apple platforms.\nThe combination of your\npreferred languages, region, calendar, time zone,\nand which keyboards you have installed\nsay a lot about who you are \u2014\nespecially if you have less conventional preferences.
\nimport Foundation\n Locale.current.languageCode \n log2(Double(Locale.isoLanguageCodes .count)) // 9.217 bits\n Locale.current.regionCode \n log2(Double(Locale.isoRegionCodes .count)) // 8 bits\n Locale.current.calendar.identifier\n // ~2^4 (16) Calendars\n TimeZone .current.identifier\n log2(Double(TimeZone .knownTimeZoneIdentifiers .count)) // 8.775 bits\n UserDefaults .standard.object(forKey : "AppleKeyboards" )\n // ~2^6 (64) iOS keyboards \n
\n\nAccessibility preferences also provide a great deal of information,\nwith each individual setting contributing a single potential bit:
\nUIAccessibility.isBoldTextEnabled \n UIAccessibility.isShakeToUndoEnabled \n UIAccessibility.isReduceMotionEnabled \n UIAccessibility.isDarkerSystemColorsEnabled \n UIAccessibility.isReduceTransparencyEnabled \n UIAccessibility.isAssistiveTouchRunning \n
\nOf the approximately ~25% of users who take advantage of\nDynamic Type\nby configuring a preferred font size,\nthat selection may also be used to fingerprint you:
\nlet application = UIApplication.shared\n application.preferredContentSizeCategory \n
\nAlthough most of the juiciest bits have been locked down\nin OS updates over the years,\nthere\u2019s just enough to contribute a few more bits for purposes of identification.
\nOn iOS,\nyou can get the current model and amount of storage of a user\u2019s device:
\nimport UIKit\n let device = UIDevice.current\n device.name // "iPhone 11 Pro" \n let fileManager = FileManager .default\n if let path = fileManager .urls(for: .libraryDirectory , in: .systemDomainMask ).last?.path,\n let systemSize = try? fileManager .attributesOfFileSystem (forPath : path)[.systemSize ] as? Int\n {\n Measurement<UnitInformationStorage >(value: Double(systemSize ), unit: .bytes)\n .converted(to: .gigabytes) // ~256GB\n }\n
\nWith 14 supported iOS devices,\nmost having 3 configurations each,\nlet\u2019s say that this contributes about 32 possibilities, or 5 bits.
\nYou can go a few steps further on macOS,\nto further differentiate hardware by its processor count and amount of RAM:
\nprocessInfo .processorCount // 8\n Measurement<UnitInformationStorage >(value: Double(processInfo .physicalMemory ),\n unit: .bytes)\n .converted(to: .gigabytes) // 16GB\n
\nIt\u2019s hard to get a sense of\nhow many different Mac models are in use,\nbut a reasonable estimate would be on the order of 26 or 27.
\nKnowing whether someone\u2019s phone is on Verizon or Vodaphone\ncan also be factored into a fingerprint.\nYou can use the CTTelephony
class from the\nCoreTelephony framework\nto lookup the providers for devices with cellular service:
import CoreTelephony \n let networkInfo = CTTelephonyNetworkInfo ()\n let carriers = networkInfo .serviceSubscriberCellularProviders ?.values\n carriers?.map { ($0.mobileNetworkCode , $0.mobileCountryCode ) }\n
\nThe number of providers varies per country,\nbut using the 4 major carriers in United States\nas a guideline,\nwe can say carrier information would contribute about 2 bits\n(or more if you have multiple SIM cards installed).
\nMore generally,\neven knowing whether someone can send texts or email at all\ncan be factored into a fingerprint.\nThis information can be gathered without permissions via\nthe MessageUI framework.
\nimport MessageUI \n MFMailComposeViewController .canSendMail ()\n MFMessageComposeViewController .canSendText ()\n
\nIf the use of digital fingerprinting seems outlandish,\nthat\u2019s just scratching the surface of how companies and researchers\nhave figured out how to circumvent your privacy.
\nAlthough access to geolocation through conventional APIs\nrequires explicit authorization,\nthird parties may be able to get a general sense of where you are in the world\nbased on how you access the Internet.
\nGeolocation by source IP address\nis used extensively for things like region locking and localization.\nYou could also combine this information with\nping-time measurements\nto hosts in known locations\nto get a more accurate pinpoint on location (Weinberg, Cho, Christin, Sekar, & Gill, 2018):
\nping -c 5 99.24.18.13 # San Francisco, USA\n \n--- 99.24.18.13 ping statistics ---\n5 packets transmitted, 5 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 11.900/12.184/12.895/0.380 ms\nping -c 5 203.47.10.37 # Adelaide, Australia\n \n--- 203.47.10.37 ping statistics ---\n5 packets transmitted, 5 packets received, 0.0% packet loss\nround-trip min/avg/max/stddev = 202.122/202.433/203.436/0.504 ms\n
\nIt\u2019s unclear whether this is a concern in iOS,\nbut depending on how precise the results of UIDevice
battery APIs are,\nyou may be able to use them to identify a device by its battery health.\n(Olejnik, Acar, Castelluccia, & Diaz, 2016)
var timestampedBatteryLevels : [(Date, Float)] = []\n if UIDevice.current.isBatteryMonitoringEnabled {\n timestampedBatteryLevels .append((Date(), UIDevice.current.batteryLevel ))\n }\n
\n\nEverything from your heartbeat, to your gait, to your\nbutt shape\nseem capable of leaking your identity.\nIt can all be quite overwhelming.
\nI mean,\nif a motivated individual can find your home address by\ncross-referencing the reflection in your eyes against Google Street view,\nhow can we even stand a chance out there?
\nMuch as we may bemoan the current duopoly of mobile operating systems,\nwe might take some solace in the fact that\nat least one of the players actually cares about user privacy.\nThough it\u2019s unclear whether that\u2019s a fight that can ever be won.
\nAt times,\nour fate of being tracked and advertised to\nmay seem as inevitable as the victims in It Follows.
\nBut let\u2019s not forget that,\nas technologists, as people with a voice,\nwe\u2019re in a position to fight back.
\n\n" }, "visual": { "url": "http://www.blogcdn.com/www.engadget.com/media/2013/10/irlbanner-1382819058.jpg", "width": 620, "height": 194, "contentType": "image/jpeg" }, "unread": false, "readTime": 5062, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366", "label": "Weblogs" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572578867134 }, { "originId": "https://inessential.com/2019/10/31/amateurs", "fingerprint": "a55a55f8", "id": "+jHfsXnBCVfCstSIW1WDumAyigT4rnsUPnI5WFxgnAU=_16e23877a2b:1e9e9:d4506071", "summary": { "direction": "ltr", "content": "One thing I\u2019m weirdly proud of is my position as an amateur programmer.
\nWhen I point that out, people say, \u201cWell, but\u2026\u201d \u2014\u00a0and I know where they\u2019re going, that after 25 years of professional experience I\u2019m not what you think of when you think of \u201camateur.\u201d
\nAnd yet, it\u2019s still true. It\u2019s just that I\u2019ve come out the other side, and now I get to work on exactly what I want to, the way I want to, without any thoughts of trying to make money at it.
\nI can take risks! I can work with anybody who shows up! It\u2019s a pure thrill. It\u2019s like writing single-malt apps.
\nAnd I would wish for more people to find themselves in this position \u2014 eventually, anyway \u2014\u00a0because I want to see what they would make.
\nPS The Dictionary app on my Mac says of the origin of the word \u201camateur\u201d:
\n\n\nlate 18th century: from French, from Italian amatore, from Latin amator \u2018lover\u2019, from amare \u2018to love\u2019.
\n
Spot-on.
" }, "alternate": [ { "href": "https://inessential.com/2019/10/31/amateurs", "type": "text/html" } ], "crawled": 1572554111531, "title": "Amateurs", "published": 1572553200000, "origin": { "streamId": "feed/http://ranchero.com/xml/rss.xml", "htmlUrl": "https://inessential.com/", "title": "inessential.com" }, "visual": { "url": "none" }, "unread": true, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/b35dbac9-ed46-4c6f-ab82-6798caa6b43d", "label": "Another for NetNewsWire" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572578844326 }, { "originId": "tag:blogger.com,1999:blog-8954608646904080796.post-6562845574025332924", "fingerprint": "57441f5a", "thumbnail": [ { "url": "https://1.bp.blogspot.com/-nSjfvVEYsOE/XZ6cEycVw3I/AAAAAAAADTI/UMdV1Seh7R8c0GdV2RgwjuAoJLW47it1gCLcBGAsYHQ/s72-c/074.jpg", "width": 72, "height": 72 } ], "id": "v0v+7Ya8tssIZvd3/pcnFRr3HwvY/5YK3FGc2t65c0Y=_16db6126dbf:685a:d4506071", "updated": 1570675865985, "author": "Edward Feser", "alternate": [ { "href": "http://edwardfeser.blogspot.com/2019/10/transubstantiation-and-hylemorphism.html", "type": "text/html" } ], "crawled": 1570717724095, "title": "Transubstantiation and hylemorphism", "published": 1570675860000, "origin": { "streamId": "feed/http://edwardfeser.blogspot.com/feeds/posts/default", "htmlUrl": "http://edwardfeser.blogspot.com/", "title": "Edward Feser" }, "content": { "direction": "ltr", "content": "At WWDC this year, Apple announced a coordinated effort between Xcode 11 and iOS 13 to bring new insights to developers about how their apps are performing in the field.
" }, "alternate": [ { "href": "http://feedproxy.google.com/~r/NSHipster/~3/o2-j6xKjBrA/", "type": "text/html" } ], "canonical": [ { "href": "https://nshipster.com/metrickit/", "type": "text/html" } ], "crawled": 1571854910253, "title": "MetricKit", "published": 1571641200000, "origin": { "streamId": "feed/http://feeds.feedburner.com/NSHipster", "htmlUrl": "https://nshipster.com/", "title": "NSHipster" }, "content": { "direction": "ltr", "content": "As an undergraduate student,\nI had a radio show called\n\u201cGoodbye, Blue Monday\u201d\n(I was really into Vonnegut at the time).\nIt was nothing glamorous \u2014\njust a weekly, 2-hour slot at the end of the night\nbefore the station switched into automation.
\nIf you happened to be driving through the hills of Pittsburgh, Pennsylvania\nlate at night with your radio tuned to\nWRCT 88.3,\nyou\u2019d have heard an eclectic mix of\nContemporary Classical,\nAcid Jazz,\nItalian Disco, and\nBebop.\nThat, and the stilting, dulcet baritone of\na college kid doing his best impersonation of\nTony Mowod.
\nSitting there in the booth,\nwaiting for tracks to play out before launching into an\nFCC-mandated\nPSA\nor on-the-hour\nstation identification,\nI\u2019d wonder:\nIs anyone out there listening?\nAnd if they were, did they like it?\nI could\u2019ve been broadcasting static the whole time and been none the wiser.
\nThe same thoughts come to mind whenever I submit a build to App Store Connect\u2026\nbut then I\u2019ll remember that, unlike radio,\nyou can actually know these things!\nAnd the latest improvements in Xcode 11 make it easier than ever\nto get an idea of how your apps are performing in the field.
\nWe\u2019ll cover everything you need to know in this week\u2019s NSHipster article.\nSo as they say on the radio:\n\u201cDon\u2019t touch that dial (it\u2019s got jam on it)\u201d.
\nMetricKit is a new framework in iOS 13\nfor collecting and processing battery and performance metrics.\nIt was announced at WWDC this year\nalong with XCTest Metrics and the Xcode Metrics Organizer\nas part of a coordinated effort to bring new insights to developers\nabout how their apps are performing in the field.
\n\nApple automatically collects metrics from apps installed on the App Store.\nYou can view them in Xcode 11\nby opening the Organizer (\u2325\u2318\u21e7O)\nand selecting the new Metrics tab.
\nMetricKit complement Xcode Organizer Metrics by providing a programmatic way to\nreceive daily information about how your app is performing in the field.\nWith this information,\nyou can collect, aggregate, and analyze on your own in greater detail\nthan you can through Xcode.
\nMetrics can help uncover issues you might not have seen while testing locally,\nand allow you to track changes across different versions of your app.\nFor this initial release,\nApple has focused on the two metrics that matter most to users:\nbattery usage and performance.
\nBattery life depends on a lot of different factors.\nPhysical aspects like the age of the device and\nthe number of charge cycles are determinative,\nbut the way your phone is used matters, too.\nThings like CPU usage,\nthe brightness of the display and the colors on the screen,\nand how often radios are used to fetch data or get your current location \u2014\nall of these can have a big impact.\nBut the main thing to keep in mind is that\nusers care a lot about battery life.
\nAside from how good the camera is,\nthe amount of time between charges\nis the deciding factor when someone buys a new phone these days.\nSo when their new, expensive phone doesn\u2019t make it through the day,\nthey\u2019re going to be pretty unhappy.
\nUntil recently,\nApple\u2019s taken most of the heat on battery issues.\nBut since iOS 12 and its new\nBattery Usage screen in Settings,\nusers now have a way to tell when their favorite app is to blame.\nFortunately,\nwith iOS 13 you now have everything you need to make sure\nyour app doesn\u2019t run afoul of reasonable energy usage.
\nPerformance is another key factor in the overall user experience.\nNormally, we might look to stats like\nprocessor clock speed or frame rate\nas a measure of performance.\nBut instead,\nApple\u2019s focusing on less abstract and more actionable metrics:
\nFrom the perspective of an API consumer,\nit\u2019s hard to imagine how MetricKit could be easier to incorporate.\nAll you need is for some part of your app to serve as\na metric subscriber\n(an obvious choice is your App
),\nand for it to be added to the shared MXMetric
:
import UIKit\n import MetricKit \n @UIApplicationMain \n class AppDelegate : UIResponder, UIApplicationDelegate {\n func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions : [UIApplication.LaunchOptionsKey : Any]?) -> Bool {\n MXMetricManager .shared.add(self)\n return true\n }\n func applicationWillTerminate (_ application: UIApplication) {\n MXMetricManager .shared.remove(self)\n }\n }\n extension AppDelegate : MXMetricManagerSubscriber {\n func didReceive (_ payloads: [MXMetricPayload ]) {\n ...\n }\n }\n
\niOS automatically collects samples while your app is being used,\nand once per day (every 24 hours),\nit\u2019ll send an aggregated report with those metrics.
\nTo verify that your MXMetric
\nis having its delegate method called as expected,\nselect Simulate MetricKit Payloads from the Debug menu\nwhile Xcode is running your app.
In addition to the baseline statistics collected for you,\nyou can use the\nmx
function\nto collect metrics around the most important parts of your code.\nThis signpost-backed API\ncaptures CPU time, memory, and writes to disk.
For example,\nif part of your app did post-processing on audio streams,\nyou might annotate those regions with metric signposts\nto determine the energy and performance impact of that work:
\nlet audioLogHandle = MXMetricManager .makeLogHandle (category: "Audio")\n func processAudioStream () {\n mxSignpost (.begin, log: audioLogHandle , name: "ProcessAudioStream" )\n ...\n mxSignpost (.end, log: audioLogHandle , name: "ProcessAudioStream" )\n }\n
\nNow that you have this information,\nwhat do you do with it?\nHow do we fill that ...
placeholder in our implementation of did
?
You could pass that along to some paid analytics or crash reporting service,\nbut where\u2019s the fun in that?\nLet\u2019s build our own web service to collect these for further analysis:
\nThe MXMetric
objects received by metrics manager subscribers\nhave a convenient\njson
method\nthat generates something like this:
{\n"locationActivityMetrics" : {\n"cumulativeBestAccuracyForNavigationTime" : "20 sec",\n"cumulativeBestAccuracyTime" : "30 sec",\n"cumulativeHundredMetersAccuracyTime" : "30 sec",\n"cumulativeNearestTenMetersAccuracyTime" : "30 sec",\n"cumulativeKilometerAccuracyTime" : "20 sec",\n"cumulativeThreeKilometersAccuracyTime" : "20 sec"\n},\n"cellularConditionMetrics" : {\n"cellConditionTime" : {\n"histogramNumBuckets" : 3,\n"histogramValue" : {\n"0": {\n"bucketCount" : 20,\n"bucketStart" : "1 bars",\n"bucketEnd" : "1 bars"\n},\n"1": {\n"bucketCount" : 30,\n"bucketStart" : "2 bars",\n"bucketEnd" : "2 bars"\n},\n"2": {\n"bucketCount" : 50,\n"bucketStart" : "3 bars",\n"bucketEnd" : "3 bars"\n}\n}\n}\n},\n"metaData" : {\n"appBuildVersion" : "0",\n"osVersion" : "iPhone OS 13.1.3 (17A878)" ,\n"regionFormat" : "US",\n"deviceType" : "iPhone9,2" \n},\n"gpuMetrics" : {\n"cumulativeGPUTime" : "20 sec"\n},\n"memoryMetrics" : {\n"peakMemoryUsage" : "200,000 kB" ,\n"averageSuspendedMemory" : {\n"averageValue" : "100,000 kB" ,\n"standardDeviation" : 0,\n"sampleCount" : 500\n}\n},\n"signpostMetrics" : [\n{\n"signpostIntervalData" : {\n"histogrammedSignpostDurations" : {\n"histogramNumBuckets" : 3,\n"histogramValue" : {\n"0": {\n"bucketCount" : 50,\n"bucketStart" : "0 ms",\n"bucketEnd" : "100 ms"\n},\n"1": {\n"bucketCount" : 60,\n"bucketStart" : "100 ms",\n"bucketEnd" : "400 ms"\n},\n"2": {\n"bucketCount" : 30,\n"bucketStart" : "400 ms",\n"bucketEnd" : "700 ms"\n}\n}\n},\n"signpostCumulativeCPUTime" : "30,000 ms",\n"signpostAverageMemory" : "100,000 kB" ,\n"signpostCumulativeLogicalWrites" : "600 kB" \n},\n"signpostCategory" : "TestSignpostCategory1" ,\n"signpostName" : "TestSignpostName1" ,\n"totalSignpostCount" : 30\n},\n{\n"signpostIntervalData" : {\n"histogrammedSignpostDurations" : {\n"histogramNumBuckets" : 3,\n"histogramValue" : {\n"0": {\n"bucketCount" : 60,\n"bucketStart" : "0 ms",\n"bucketEnd" : "200 ms"\n},\n"1": {\n"bucketCount" : 70,\n"bucketStart" : "201 ms",\n"bucketEnd" : "300 ms"\n},\n"2": {\n"bucketCount" : 80,\n"bucketStart" : "301 ms",\n"bucketEnd" : "500 ms"\n}\n}\n},\n"signpostCumulativeCPUTime" : "50,000 ms",\n"signpostAverageMemory" : "60,000 kB" ,\n"signpostCumulativeLogicalWrites" : "700 kB" \n},\n"signpostCategory" : "TestSignpostCategory2" ,\n"signpostName" : "TestSignpostName2" ,\n"totalSignpostCount" : 40\n}\n],\n"displayMetrics" : {\n"averagePixelLuminance" : {\n"averageValue" : "50 apl",\n"standardDeviation" : 0,\n"sampleCount" : 500\n}\n},\n"cpuMetrics" : {\n"cumulativeCPUTime" : "100 sec"\n},\n"networkTransferMetrics" : {\n"cumulativeCellularDownload" : "80,000 kB" ,\n"cumulativeWifiDownload" : "60,000 kB" ,\n"cumulativeCellularUpload" : "70,000 kB" ,\n"cumulativeWifiUpload" : "50,000 kB" \n},\n"diskIOMetrics" : {\n"cumulativeLogicalWrites" : "1,300 kB" \n},\n"applicationLaunchMetrics" : {\n"histogrammedTimeToFirstDrawKey" : {\n"histogramNumBuckets" : 3,\n"histogramValue" : {\n"0": {\n"bucketCount" : 50,\n"bucketStart" : "1,000 ms",\n"bucketEnd" : "1,010 ms"\n},\n"1": {\n"bucketCount" : 60,\n"bucketStart" : "2,000 ms",\n"bucketEnd" : "2,010 ms"\n},\n"2": {\n"bucketCount" : 30,\n"bucketStart" : "3,000 ms",\n"bucketEnd" : "3,010 ms"\n}\n}\n},\n"histogrammedResumeTime" : {\n"histogramNumBuckets" : 3,\n"histogramValue" : {\n"0": {\n"bucketCount" : 60,\n"bucketStart" : "200 ms",\n"bucketEnd" : "210 ms"\n},\n"1": {\n"bucketCount" : 70,\n"bucketStart" : "300 ms",\n"bucketEnd" : "310 ms"\n},\n"2": {\n"bucketCount" : 80,\n"bucketStart" : "500 ms",\n"bucketEnd" : "510 ms"\n}\n}\n}\n},\n"applicationTimeMetrics" : {\n"cumulativeForegroundTime" : "700 sec",\n"cumulativeBackgroundTime" : "40 sec",\n"cumulativeBackgroundAudioTime" : "30 sec",\n"cumulativeBackgroundLocationTime" : "30 sec"\n},\n"timeStampEnd" : "2019-10-22 06:59:00 +0000",\n"applicationResponsivenessMetrics" : {\n"histogrammedAppHangTime" : {\n"histogramNumBuckets" : 3,\n"histogramValue" : {\n"0": {\n"bucketCount" : 50,\n"bucketStart" : "0 ms",\n"bucketEnd" : "100 ms"\n},\n"1": {\n"bucketCount" : 60,\n"bucketStart" : "100 ms",\n"bucketEnd" : "400 ms"\n},\n"2": {\n"bucketCount" : 30,\n"bucketStart" : "400 ms",\n"bucketEnd" : "700 ms"\n}\n}\n}\n},\n"appVersion" : "1.0.0",\n"timeStampBegin" : "2019-10-21 07:00:00 +0000"\n}\n
\nAs you can see,\nthere\u2019s a lot baked into this representation.\nDefining a schema for all of this information would be a lot of work,\nand there\u2019s no guarantee that this won\u2019t change in the future.\nSo instead,\nlet\u2019s embrace the NoSQL paradigm\n(albeit responsibly, using Postgres)\nby storing payloads in a JSONB
column:
CREATE TABLE IF NOT EXISTS metrics (\n id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n payload JSONB NOT NULL\n );\n
\nSo easy!
\nWe can extract individual fields from payloads\nusing JSON operators\nlike so:
\nSELECT (payload -> 'applicationTimeMetrics' \n ->> 'cumulativeForegroundTime' )::INTERVAL\n FROM metrics;\n -- interval\n -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n -- @ 11 mins 40 secs\n -- (1 row)\n
\n\nJSON operators in PostgreSQL can be cumbersome to work with \u2014\nespecially for more complex queries.\nOne way to help with that is to create a view\n(materialized or otherwise)\nto project the most important information to you\nin the most convenient representation:
\nCREATE VIEW key_performance_indicators AS\n SELECT\n id,\n (payload -> 'appVersion' ) AS app_version,\n (payload -> 'metaData' ->> 'deviceType' ) AS device_type,\n (payload -> 'metaData' ->> 'regionFormat' ) AS region,\n (payload -> 'applicationTimeMetrics' \n ->> 'cumulativeForegroundTime' \n )::INTERVAL AS cumulative_foreground_time,\n parse_byte_count(\n payload -> 'memoryMetrics' \n ->> 'peakMemoryUsage' \n ) AS peak_memory_usage_bytes\n FROM metrics;\n
\nWith views,\nyou can perform\naggregate queries\nover all of your metrics JSON payloads\nwith the convenience of a schema-backed relational database:
\nSELECT avg(cumulative_foreground_time)\n FROM key_performance_indicators;\n -- avg\n -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n -- @ 9 mins 41 secs\n SELECT app_version, percentile_disc(0.5)\n WITHIN GROUP (ORDER BY peak_memory_usage_bytes)\n AS median\n FROM key_performance_indicators\n GROUP BY app_version;\n -- app_version \u2502 median\n -- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n -- "1.0.1" \u2502 192500000\n -- "1.0.0" \u2502 204800000\n
\n\nIn this example,\nmost of the heavy lifting is delegated to Postgres,\nmaking the server-side implementation rather boring.\nFor completeness,\nhere are some reference implementations in\nRuby (Sinatra) and JavaScript (Express):
\nrequire 'sinatra/base'\n require 'pg'\n require 'sequel'\n class App < Sinatra::Base\n configure do\n DB = Sequel.connect(ENV['DATABASE_URL'])\n end\n post '/collect' do\n DB[:metrics].insert(payload: request.body.read)\n status 204\n end\n end\n
\nimport express from 'express';\n import { Pool } from 'pg';\n const db = new Pool(\n connectionString : process.env.DATABASE_URL,\n ssl: process.env.NODE_ENV === 'production'\n );\n const app = express();\n app.post('/collect', (request, response) => {\n db.query('INSERT INTO metrics (payload) VALUES ($1)', [request.body], (error, results) => {\n if (error) {\n throw error;\n }\n response.status(204);\n })\n });\n app.listen(process.env.PORT || 5000)\n
\nNow that we have everything set up,\nthe final step is to implement\nthe required MXMetric
delegate method did
\nto pass that information along to our web service:
extension AppDelegate : MXMetricManagerSubscriber {\n func didReceive (_ payloads: [MXMetricPayload ]) {\n for payload in payloads {\n let url = URL(string: "https://example.com/collect")!\n var request = URLRequest(url: url)\n request.httpMethod = "POST"\n request.httpBody = payload.jsonRepresentation ()\n let task = URLSession.shared.dataTask (with: request)\n task.priority = URLSessionTask .lowPriority \n task.resume()\n }\n }\n }\n
\nWhen you create something and put it out into the world,\nyou lose your direct connection to it.\nThat\u2019s as true for apps as it is for college radio shows.\nShort of user research studies or\ninvasive ad-tech,\nthe truth is that\nwe rarely have any clue about how people are using our software.
\n\nMetrics offer a convenient way to at least make sure that\nthings aren\u2019t too slow or too draining.\nAnd though they provide but a glimpse in the aggregate\nof how our apps are being enjoyed,\nit\u2019s just enough to help us honor both our creation and our audience\nwith a great user experience.
\n\n" }, "visual": { "url": "https://cdn.vox-cdn.com/thumbor/06e5FJWgUfUSmDaPJIEZoGF1XOs=/0x68:2040x1136/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/10378819/DSCF3031.jpg", "width": 1200, "height": 628, "contentType": "image/jpeg" }, "unread": false, "readTime": 4744, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/e31b3fcb-27f6-4f3e-b96c-53902586e366", "label": "Weblogs" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" } ], "actionTimestamp": 1572500250190 }, { "originId": "https://inessential.com/2019/10/22/netnewswire_5_0_3_for_mac_released", "fingerprint": "2c4d6d88", "id": "+jHfsXnBCVfCstSIW1WDumAyigT4rnsUPnI5WFxgnAU=_16df54dfcca:13322:d4506071", "summary": { "direction": "ltr", "content": "\nThe main things in this release are 1) enhanced performance and 2) importing subscriptions from NetNewsWire 3 (since it won\u2018t run on Catalina).
\nThere are also a bunch of bug fixes \u2014 including a fix for the space bar behavior on Catalina \u2014 and there\u2019s a new feature: you can type the s
key to star and unstar an article.
For more details, read the change notes on the NetNewsWire blog.
" }, "alternate": [ { "href": "https://inessential.com/2019/10/22/netnewswire_5_0_3_for_mac_released", "type": "text/html" } ], "crawled": 1571778591946, "title": "NetNewsWire 5.0.3 for Mac Released", "published": 1571775505000, "origin": { "streamId": "feed/http://ranchero.com/xml/rss.xml", "htmlUrl": "https://inessential.com/", "title": "inessential.com" }, "visual": { "url": "http://www.blogcdn.com/www.engadget.com/media/2013/10/nvidia-shield-console-mode.jpg", "width": 620, "height": 340, "contentType": "image/jpg" }, "unread": false, "readTime": 5621, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/b35dbac9-ed46-4c6f-ab82-6798caa6b43d", "label": "Another for NetNewsWire" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" } ], "actionTimestamp": 1572500226675 }, { "id": "AxO6mug+YPRclcA3EJcsykvvS1qcjXH62IXONGWCBII=_16db2add61b:afd:a4acdac", "originId": "58495.pz39s0 at https://www.imore.com", "fingerprint": "8fe463a6", "content": { "content": "An Apple Support rep apparently said, "I do not know how this could of happened."
\n\nWhen Apple Card debuted, one of its biggest draws was Apple's focus on security. On Apple's website, it says, "It's hard to steal a credit card number when you can't see it." But that's apparently what happened to one Apple Card user who reached out to 9to5Mac, claiming they were the victim of fraud.
\nThe Apple Card user said they reached out to Apple Support and received this response:
\n\n\nI do not know how this could of happened. It's very rare for your card to be in two places at one time. Since our physical cards have no number on it, it's very hard for someone to copy it.
\n
The Apple Card user confirmed the fraudulent charge after receiving an alert on his iPhone. The tricky thing is the purchase was apparently labeled as being nearby, but clicking on the map revealed it was hours away, 9to5Mac explained.
\nOn Apple's website, the company highlights the fact that the Apple Card doesn't have any numbers on it. "Not even a CVV. So that's one less thing to worry about when you hand over your card at a restaurant or store." But that doesn't guarantee it can't be stolen.
\n9to5Mac speculates that the Apple Card user may have been the victim of skimming, which can potentially affect all credit cards and debit cards. It's a reminder to be extra vigilant when swiping your card at a gas station or ATM. Better yet, use Apple Pay when possible.
\n\n\nMost Xcode users quickly become familiar with the basics of the Find Navigator panel.
\n\nWith it, you can find text, regular expressions, and perform search-and-replace, whether matching or ignoring case. But that\u2019s just scratching the surface of the Find Navigator.
\nI thought I\u2019d drop a few words today about search scopes. Controlled from the bottom left, \u00a0under the search field, you can create narrowed searches. This enables you to, for example, search only in Swift files or exclude files containing the word Test.
\nTo get started, click the icon (two lines with three squares on a line between them) and then New Scope (the plus icon). Here, you can name the scope, limit the search extent, and add criteria for exactly which files should be included or not.
\n\nThe logic is straightforward. You choose where to look (the project, a folder, or through the entire SDK), and whether to include all conditions or some conditions:
\n\nEach condition is based on the file name, path, extension, UTI (the kind of file, like image which is useful for finding vector assets), Workspace location (namely groups), or source control status (handy for finding newly applied changes.)
\nMost of my conditions are file-name-based. And for those, you get the following matching conditions. The \u201cends with\u201d is an obvious win for extensions (although you can also use UTIs for that), and \u201cstarts with\u201d can help for projects organized in hierarchical ways.
\n\nNow, interestingly enough, this list fails to offer \u201cdoes not contain\u201d but that\u2019s fairly easy to work around. Since Xcode supports regex matching, you can easily replicate \u201cdoes not contain\u201d with an appropriate regex:
\n\nChange the file name to a path to exclude source file directories.
\nYou can create as many search domains as you like. At least, I haven\u2019t found an upper bound yet. I haven\u2019t found a way to reorder the find scopes, although if you\u2019re really controlling about this, you can pop into \u00a0your workspace (ProjectName.xcodeproj/project.xcworkspace/xcuserdata/username.xcuserdatad
), convert your UserInterfaceState.xcuserstate
to xml (plutil -convert xml1
), and hand-edit it the way you need.
There are lots of wonderful little Xcode tweaks like these throughout this monster of an IDE. What are some of your faves? If I have time this week, I\u2019ll share some of mine, such as the four-square \u2014 another of my favorite tools \u2014 and a few great ways to connect your editor to the navigator.
" }, "unread": false, "readTime": 3406, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" } ], "actionTimestamp": 1572499929438 }, { "keywords": [ "Apple" ], "originId": "https://9to5mac.com/?p=617536", "recrawled": 1572455791719, "updateCount": 1, "fingerprint": "77224d8", "id": "BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16e1d065ad9:1c0f1:d4506071", "author": "Michael Potuck", "summary": { "direction": "ltr", "content": "\nSonos has announced a new initiative today that makes it easy for existing customers to trade in older Sonos products for a nice discount on new ones. For Apple customers, the Trade Up program is a neat opportunity to bring Sonos\u2019 AirPlay 2 compatible speakers into your home.
\n\nThe post Sonos pushing AirPlay 2 speaker lineup with new Trade Up program appeared first on 9to5Mac.
" }, "alternate": [ { "href": "https://9to5mac.com/2019/10/30/sonos-trade-in-program-airplay-2/", "type": "text/html" } ], "crawled": 1572444986073, "title": "Sonos pushing AirPlay 2 speaker lineup with new Trade Up program", "published": 1572442686000, "origin": { "streamId": "feed/http://9to5mac.com/feed/", "htmlUrl": "https://9to5mac.com", "title": "9to5Mac" }, "visual": { "url": "https://cdn.vox-cdn.com/thumbor/06e5FJWgUfUSmDaPJIEZoGF1XOs=/0x68:2040x1136/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/10378819/DSCF3031.jpg", "width": 1200, "height": 628, "contentType": "image/jpeg" }, "unread": false, "readTime": 4493, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815", "label": "Macintosh" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572499889443 }, { "originId": "tag:blogger.com,1999:blog-8954608646904080796.post-4991449931465752891", "fingerprint": "7d99be14", "thumbnail": [ { "url": "https://1.bp.blogspot.com/-rGjQOQb89a8/XbS25YYbgeI/AAAAAAAADUI/s8Q4e0pTtX4qSTP3wrkt13VGV53Q8J0tQCLcBGAsYHQ/s72-c/092.jpg", "width": 72, "height": 72 } ], "id": "v0v+7Ya8tssIZvd3/pcnFRr3HwvY/5YK3FGc2t65c0Y=_16e0a232509:17e32:d4506071", "updated": 1572124724480, "author": "Edward Feser", "alternate": [ { "href": "http://edwardfeser.blogspot.com/2019/10/john-paul-ii-in-defense-of-nation-and.html", "type": "text/html" } ], "crawled": 1572128105737, "title": "John Paul II in defense of the nation and patriotism", "published": 1572124680000, "origin": { "streamId": "feed/http://edwardfeser.blogspot.com/feeds/posts/default", "htmlUrl": "http://edwardfeser.blogspot.com/", "title": "Edward Feser" }, "content": { "direction": "ltr", "content": "Some people took my post No ETAs as if I were arguing against doing software estimates of any kind, ever.
\nI didn\u2019t actually mean that. If your boss, project manager, or person you\u2019re contracting with asks for an estimate, do your best to come up with something accurate. If you\u2019re writing enterprise software, you may even be contractually bound to provide estimates for when features will ship.
\nThere are ways to get pretty good at this. Pay attention to history and avoid wishful thinking. Don\u2019t assume perfect productivity. Allow for the unexpected, because there\u2019s always something.
\nWhat I\u2019m talking about is the case where you\u2019re writing a consumer-facing app \u2014\u00a0something that would get published on an app store, for instance \u2014\u00a0and customers or potential customers ask about an ETA for a given feature. Don\u2019t do it! (For the reasons stated in the article.)
" }, "alternate": [ { "href": "https://inessential.com/2019/10/30/etas_follow_up", "type": "text/html" } ], "crawled": 1572467543710, "title": "ETAs: Follow-Up", "published": 1572466202000, "origin": { "streamId": "feed/http://ranchero.com/xml/rss.xml", "htmlUrl": "https://inessential.com/", "title": "inessential.com" }, "unread": false, "readTime": 6552, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/b35dbac9-ed46-4c6f-ab82-6798caa6b43d", "label": "Another for NetNewsWire" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/66132046-6f14-488d-b590-8e93422723c8", "label": "THree" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572499788300 }, { "keywords": [ "Apple" ], "originId": "https://9to5mac.com/?p=617593", "fingerprint": "a1dafaae", "id": "BmoAzSEWHFzR01wyxBZAhNEo11Vy8oDR1qKDe+tKVEQ=_16e1de23321:1c6e3:d4506071", "author": "Guilherme Rambo", "summary": { "direction": "ltr", "content": "\nRumors about a new 16-inch MacBook Pro are not exactly new, with recent icon evidence found in macOS Catalina betas suggesting the redesign mentioned in some reports is not going to happen as we thought.
\n\nThe post Exclusive: 16-inch MacBook Pro Touch Bar and Touch ID layout confirmed appeared first on 9to5Mac.
" }, "alternate": [ { "href": "https://9to5mac.com/2019/10/30/exclusive-16-inch-macbook-pro-touch-bar-and-touch-id-layout-confirmed/", "type": "text/html" } ], "crawled": 1572459393825, "title": "Exclusive: 16-inch MacBook Pro Touch Bar and Touch ID layout confirmed", "published": 1572456327000, "origin": { "streamId": "feed/http://9to5mac.com/feed/", "htmlUrl": "https://9to5mac.com", "title": "9to5Mac" }, "unread": false, "readTime": 3947, "categories": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/category/5ca4d61d-e55d-4999-a8d1-c3b9d8789815", "label": "Macintosh" } ], "tags": [ { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.read", "label": "" }, { "id": "user/f2f031bd-f3e3-4893-a447-467a291c6d1e/tag/global.saved", "label": "Saved For Later" } ], "actionTimestamp": 1572499186128 } ] }