Administrative – Cocoanetics https://www.cocoanetics.com Our DNA is written in Swift Fri, 14 Jul 2023 15:20:48 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 39982308 BarCodeKit 1.4.0 https://www.cocoanetics.com/2023/07/barcodekit-1-4-0/ https://www.cocoanetics.com/2023/07/barcodekit-1-4-0/#comments Fri, 14 Jul 2023 15:20:47 +0000 https://www.cocoanetics.com/?p=10767 Next month marks the 10 year anniversary of BarCodeKit. It’s been like two years of slumber since the last release. It was available via Cocoapods and direct via GitHub, but lately several developers voiced interest for it to be available as Swift Package.

Like most of my open source frameworks on GitHub, BarCodeKit is written in Objective-C, but that doesn’t mean it couldn’t be available as Swift Package. Xcode automagically morphs everything to look Swift-native and so an implementer of such a package wouldn’t be the wiser.

To make it a Swift package I needed to clean up some ways how system frameworks are imported, what headers are imported where and most importantly I had to ditch the precompiled headers (PCH) which aren’t supported by SPM.

Once that was done I had a package that would build without complains with a simple swift build. But if you also have unit tests then you want those to be conformant with the SPM ecosystem as well. This involves mostly adding the resources – if any – required by the test cases and adding a test target to the package.swift.

I was making some changes and then running swift test, rinse and repeat. Every iteration you find a few more minor things you have to address. Until in the end both building and testing the package go without errors.

The outcome of this exercise – I like to tell myself to justify all this time spent – is that your importing and setup has become more robust.

So here we go. I submitted the new 1.4.0 version to Cocoapods, tagged it on GitHub as a release on the master branch, and finally submitted an addition requisition to the Swift Package Index. It’s been a while since I had done that last (couple of years actually) so I was confused initially by a GitHub bot stating that some additional review was required.

But that was resolved in the end by Mr. Dave Verwer himself approving the merge. Many thanks, I’m honored, Sir!

And in case you wonder why it says “unknown license” on Swift Package Index… this is because the license is a combination of my normal license and a full commercial license. Basically purchasing my book grants you a perpetual full commercial license. If you don’t want to do that there’s my standard open source license. Thereby you have to attribute to me when using BarCodeKit in your apps, or buy a non-attribution license on my parts store.

]]>
https://www.cocoanetics.com/2023/07/barcodekit-1-4-0/feed/ 4 10767
WWDC 2023: A Reflection on Apple’s “Spatial Computing” Journey https://www.cocoanetics.com/2023/06/wwdc-2023-a-reflection-on-apples-spatial-computing-journey/ https://www.cocoanetics.com/2023/06/wwdc-2023-a-reflection-on-apples-spatial-computing-journey/#respond Mon, 05 Jun 2023 20:22:17 +0000 https://www.cocoanetics.com/?p=10761 The highly-anticipated WWDC 2023 has drawn to a close, and as the dust settles, I find myself sifting through a barrage of new revelations and innovations from our friends at Apple.

As an ardent ChatGPT user, I was on tenterhooks to see what Apple had been concocting in their AI lab. But in classic Apple fashion, ‘AI’ has been stealthily replaced with the phrase ‘Machine Learning’. An interesting move, to say the least, and a clear indicator of Apple’s control over their narrative.

Turning to iOS 17, Apple has granted Autocorrection an upgrade. A shiny new transformer-based model is set to take on your personal writing style, predicting your next words, and even potentially completing well-known phrases for you. It’s a tantalising glimpse into the future of communication.

Adding to the mix, Apple has unveiled on-device audio transcription for voicemails and those infamous iMessage voice notes. No more listening to long-winded voice messages – instead, read the transcription and get the gist in mere seconds.

In a less dramatic, but still significant shift, ‘Hey Siri!’ has been reduced to just ‘Siri!’ It may not sound like a monumental change, but it shows Apple’s continuous pursuit of simplicity and efficiency. Additionally, Apple’s dedication to running all Machine Learning operations on-device could be a major stride towards preserving user privacy.

While I was half-expecting to hear about advances in generative AI, it seems Apple is keeping their cards close to their chest. I guess they’re watching how Microsoft’s foray into that space pans out before taking the leap themselves.

In terms of hardware, the transition of Mac Pro to Apple Silicon has been completed – a significant milestone. And while the change was inevitable, it feels a bit like saying goodbye to an old companion.

The grand reveal of the Apple Vision Pro marked Apple’s bold entry into the realm of Spatial Computing. Yes, the term you and I would typically call ‘AR’ has been transformed, showcasing Apple’s propensity for its own unique verbiage.

Despite joining the AR/VR party a tad late, the Vision Pro, with its myriad features, stands to make a strong impact. The intriguing lenticular screen on the outside of the headset, the collaboration with Zeiss for optimal visual clarity, all reiterate Apple’s dedication to user experience.

However, the unveiling of Vision Pro does pose a dilemma for me personally. Should I stick with my pair of Intel 27″ iMacs, invest in a Mac Studio and Studio Display, or lean into the future and embrace the Vision Pro?

Sadly, this conundrum won’t be resolved soon. The Vision Pro isn’t slated for release until next year (if you’re in the US), and it comes with a hefty $3500 price tag.

As the ripples of WWDC 2023 start to fade, one thing remains clear: Apple is charging into the future with unwavering commitment. It’s a bold, exciting new world, and Apple is beckoning us to join them on this journey. It’s going to be quite the ride!

]]>
https://www.cocoanetics.com/2023/06/wwdc-2023-a-reflection-on-apples-spatial-computing-journey/feed/ 0 10761
AI, find me some work… https://www.cocoanetics.com/2023/05/ai-find-me-some-work/ https://www.cocoanetics.com/2023/05/ai-find-me-some-work/#comments Tue, 16 May 2023 15:55:44 +0000 https://www.cocoanetics.com/?p=10749 I was asked by my wife to describe what I do so that she can send it to a friend who is apparently finding people for companies in Japan. It’s no secret that I have a love affair with GPT4.

So I sat down and gave GPT as much context as I could, adding a few more bits and pieces as I thought about them. Here’s the first version. Quite good, I think, couldn’t have written it better myself.

About Me:

  • Experience: Over 13 years of experience in iOS and Mac development.
  • Specialization: Swift, SwiftUI, AI Integration in app development, Git for source code management, and technical writing.
  • Location: Austria – available for remote work or short-term on-site projects.
  • Availability: Part-time, with current engagements requiring about a week per month.
  • Reputation: Known in the Apple developer community through my brand, Cocoanetics, and a substantial Twitter following of over 18,800 followers.

Skills & Experience:

  • Swift and SwiftUI: Extensive experience in building platform-independent code within the Apple ecosystem, including iOS, iPadOS, watchOS, macOS, tvOS.
  • AI Integration: Proficient in integrating OpenAI’s ChatGPT into app development, ranging from creating boilerplate code to building new SwiftUI views.
  • Git: Expert in source code management using Git, both on GitHub and my private GitLab instance.
  • Technical Writing: Maintained a strong online presence through frequent technical blog posts on my website, cocoanetics.com, and the authoring of “Barcodes with iOS”.
  • Open Source and Closed Source Projects: Managed numerous open source projects on GitHub, like DTCoreText and Kvitto, and ran a successful closed source “parts store” selling components to other developers.
  • Apple Liaison: Extensive experience in liaising with Apple, including addressing App Store rejection issues and interacting with Apple engineers.

Projects:

  • Independent Apps: Developed and launched several apps on the iOS App Store, such as iWoman, BabyCheck, babyforum.at, and SpeakerClock.
  • Collaborative Projects: Key contributor to GroupCal and 24me Smart Personal Assistant, rebuilding various components.
  • Business Process Tools: Created Mac apps and system tools like Scissorhands and Proofics for International Color Services, streamlining business processes.

Looking For:

  • Short-term Consulting Roles: Interested in projects requiring expertise in app architecture, bug-fixing, code reviewing, or mentoring.
  • Challenging Projects: Keen on engagements that present a challenge and require innovative solutions, particularly involving the latest Apple technologies and AI.

I am excited to bring my extensive experience, innovative thinking, and proven track record to new projects. Please do not hesitate to reach out to me if you have any opportunities where my expertise could be a good fit.

Contact Info:

Twitter: @Cocoanetics
Email: oliver@drobnik.com
Phone: +4369910010110

Best regards,
Oliver Drobnik

]]>
https://www.cocoanetics.com/2023/05/ai-find-me-some-work/feed/ 9 10749
DTCoreText 1.6.26 https://www.cocoanetics.com/2021/06/dtcoretext-1-6-26/ https://www.cocoanetics.com/2021/06/dtcoretext-1-6-26/#comments Wed, 30 Jun 2021 08:29:04 +0000 https://www.cocoanetics.com/?p=10702 This is a maintenance release mostly fixing build issues.

Changes

  • FIXED: Some weak support issues
  • FIXED: Some warnings
  • FIXED: Enabled bitcode
  • FIXED: Missing weak for DTAccessibilityElement and view proxy
  • FIXED: [Demo] About VC didn’t set correct content inset
  • FIXED: Select correct fallbackFont with matching traits when multiple are available
  • FIXED: Check for default font
  • NEW: [Demo] Demonstrate dark/light mode adjustment on About Box

Thanks to Marcin Ugarenko for his generous contributions.

The update has been tagged on GitHub, pushed to Cocoapods trunk and is also available via SPM.

]]>
https://www.cocoanetics.com/2021/06/dtcoretext-1-6-26/feed/ 23 10702
DTFoundation 1.7.18 https://www.cocoanetics.com/2021/06/dtfoundation-1-7-18/ https://www.cocoanetics.com/2021/06/dtfoundation-1-7-18/#comments Wed, 30 Jun 2021 08:20:41 +0000 https://www.cocoanetics.com/?p=10699 This is a maintenance release fixing mostly build issues.

Changes

  • FIXED: Enable building with bitcode
  • FIXED: reduce iOS deployment target to 9
  • FIXED: DTWeakSupport was sometimes not found when building as part of DTCoreText
  • FIXED: [DTASN1] Due to mismatch with protocol the parser was ignoring start end end messages for context

The update has been tagged on GitHub and was pushed to Cocoapods trunk. It’s also available via SPM.

]]>
https://www.cocoanetics.com/2021/06/dtfoundation-1-7-18/feed/ 12 10699
BarCodeKit 1.3.3 https://www.cocoanetics.com/2021/05/barcodekit-1-3-3/ https://www.cocoanetics.com/2021/05/barcodekit-1-3-3/#comments Wed, 05 May 2021 07:18:12 +0000 https://www.cocoanetics.com/?p=10697 Apparently I made a mistake in the previous release forgetting to include several commits mentioned in the release notes. It only took about 3 years for somebody to notice.

Changes

  • FIXED: some warnings related to duplicate entries in the encoding table for Code 39 and Code 93 Full ASCII
  • FIXED: Previous version was missing several commits mentioned in the release notes

I think what might have happened is that I got confused with the tags and versions. But now all the changes are included in 1.3.3.

Ivan asked:

On the site, I can see that you have mentioned that with the buying of your book the license for the library is free, is this still valid? What kind of license I will get if I buy your book?

Yes, that still works. The license says that you can use the library without attribution for your own apps. The library is open source so everybody can use it, but they have to include the BSD License text in the app settings. With the non-attribution license you don’t have to mention BarCodeKit or Cocoanetics at all. I normally charge 75 Euros for this kind of license, so the book is a really good deal. 🙂

The new version is tagged on GitHub as well as published on Cocoapods Trunk.

]]>
https://www.cocoanetics.com/2021/05/barcodekit-1-3-3/feed/ 15 10697
Some Statistics for Starters https://www.cocoanetics.com/2020/10/some-statistics-for-starters/ https://www.cocoanetics.com/2020/10/some-statistics-for-starters/#comments Tue, 27 Oct 2020 14:51:29 +0000 https://www.cocoanetics.com/?p=10685 As a hobby, I am working on a SwiftUI app on the side. It allows me to keep track of height and weight of my daughters and plot them on charts that allow me to see how “normal” my offspring are developing.

I’ve shied away from statistics at university, so it took me so time to research a few things to solve an issue I was having. Let me share how I worked towards a solution to this statistical problem. May you find it as instructive as I did.

Note: If you find any error of thought or fact in this article, please let me know on Twitter, so that I can understand what caused it.

Let me first give you some background as to what I have accomplished before today, so that you understand my statistical question.

Setup

The World Health Organization publishes tables that give the percentiles for length/height from birth to two years, to five years and to 19 years. Until two years of age the measurement is to be performed with the infant on its back, and called “length”. Beyond two years we measure standing up and then it is called “height”. That’s why there is a slight break in the published values at two years.

I also compiled my girls heights in a Numbers sheet which I fed from paediatrician visits initially and later by occasionally marking their height on a poster at the back of their bedroom door.

To get started I hard-coded the heights such:

import Foundation

struct ChildData
{
   let days: Int
   let height: Double
}

let elise = [ChildData(days: 0, height: 50),
	     ChildData(days: 6, height: 50),
	     ChildData(days: 49, height: 60),
	     ChildData(days: 97, height: 64),
	     ChildData(days: 244, height: 73.5),
	     ChildData(days: 370, height: 78.5),
	     ChildData(days: 779, height: 87.7),
	     ChildData(days: 851, height: 90),
	     ChildData(days: 997, height: 95),
	     ChildData(days: 1178, height: 97.5),
	     ChildData(days: 1339, height: 100),
	     ChildData(days: 1367, height: 101),
	     ChildData(days: 1464, height: 103.0),
	     ChildData(days: 1472, height: 103.4),
	     ChildData(days: 1544, height: 105),
	     ChildData(days: 1562, height: 105.2)
	    ]

let erika = [ChildData(days: 0, height: 47),
	     ChildData(days: 7, height: 48),
	     ChildData(days: 44, height: 54),
	     ChildData(days: 119, height: 60.5),
	     ChildData(days: 256, height: 68.5),
	     ChildData(days: 368, height: 72.5),
	     ChildData(days: 529, height: 80),
	     ChildData(days: 662, height: 82),
	     ChildData(days: 704, height: 84),
	     ChildData(days: 734, height: 85),
	     ChildData(days: 752, height: 86),
	    ]

The WHO defined one month as 30.4375 days and so I was able to have those values be plotted on a SwiftUI chart. The vertical lines you see on the chart are months with bolder lines representing full years. You can also notice the small step at the second year end.

It’s still missing some sort of labelling, but you can already see that my older daughter Elise (blue) was on the taller side during her first two years, whereas the second-born Erika (purple) was quite close to the “middle of the road”.

This chart gives you an eye-eye overview of where on the road my daughters are, but I wanted to be able to put your finger down on every place and have a pop up tell you the exact percentile value.

The Data Dilemma

A percentile value is basically giving the information how many percent of children are shorter than your child. So if your kid is on the 75th percentile, then 75th of children are shorter than it. The shades of green on the chart represent the steps in the raw data provided by the WHO.

Thery give you P01, P1, P3, P5, P10, P15, P25, P50, P75, P85, P90, P95, P97, P99, P999. P01 is the 0.1th percentile, P999 is the 99.9th percentile. At the extremes the percentiles are very close together, but in the middle there is a huge jump from 25 to 50 to 75.

I wanted to show percentile values at those arbitrary times that are at least full integers. i.e. say 47th percentile instead of “between 25 and 50” and probably show this position with a colored line on the distribution curve those percentile values represent.

It turns out, those height values are “normally distributed”, on a curve that looks a bit like a bell, thus the term “bell curve”. To me as a programmer, I would say that I understand that a a form a data compression where you only need to to know the mean value and the standard deviation and from that you can draw the curve, as opposed to interpolating between the individual percentile values.

The second – smaller – issue is that WHO provides data for full months only. To determine the normal distribution curve for arbitrary times in between the months we need to interpolate between the month data before and after the arbitrary value.

With these questions I turned to Stack Overflow and Math Stack Exchange hoping that somebody could help out me statistics noob. Here’s what I posted…

The Problem

Given the length percentiles data the WHO has published for girls. That’s length in cm at for certain months. e.g. at birth the 50% percentile is 49.1 cm.

Month    L   M   S   SD  P01 P1  P3  P5  P10 P15 P25 P50 P75 P85 P90 P95 P97 P99 P999
0    1   49.1477 0.0379  1.8627  43.4    44.8    45.6    46.1    46.8    47.2    47.9    49.1    50.4    51.1    51.5    52.2    52.7    53.5    54.9
1    1   53.6872 0.0364  1.9542  47.6    49.1    50  50.5    51.2    51.7    52.4    53.7    55  55.7    56.2    56.9    57.4    58.2    59.7
2    1   57.0673 0.03568 2.0362  50.8    52.3    53.2    53.7    54.5    55  55.7    57.1    58.4    59.2    59.7    60.4    60.9    61.8    63.4
3    1   59.8029 0.0352  2.1051  53.3    54.9    55.8    56.3    57.1    57.6    58.4    59.8    61.2    62  62.5    63.3    63.8    64.7    66.3

P01 is the 0.1% percentile, P1 the 1% percentile and P50 is the 50% percentile.

Say, I have a certain (potentially fractional) month, say 2.3 months. (a height measurement would be done at a certain number of days after birth and you can divide that by 30.4375 to get a fractional month)

How would I go about approximating the percentile for a specific height at a fraction month? i.e. instead of just seeing it “next to P50”, to say, well that’s about “P62”

One approach I thought of would be to do a linear interpolation, first between month 2 and month 3 between all fixed percentile values. And then do a linear interpolation between P50 and P75 (or those two percentiles for which there is data) values of those time-interpolated values.

What I fear is that because this is a bell curve the linear values near the middle might be too far off to be useful.

So I am thinking, is there some formula, e.g. a quad curve that you could use with the fixed percentile values and then get an exact value on this curve for a given measurement?

This bell curve is a normal distribution, and I suppose there is a formula by which you can get values on the curve. The temporal interpolation can probably still be done linear without causing much distortion. 

My Solution

I did get some responses ranging from useless to a level where they might be correct, but to me as a math outsider they didn’t help me achieve my goal. So I set out to research how to achieve the result myself.

I worked through the question based on two examples, namely my two daughters.

ELISE at 49 days
divide by 30.4375 = 1.61 months
60 cm

So that’s between month 1 and month 2:

Month  P01 P1  P3  P5  P10 P15 P25 P50 P75 P85 P90 P95 P97 P99 P999
1 47.6 49.1 50 50.5 51.2 51.7 52.4 53.7 55 55.7 56.2 56.9 57.4 58.2 59.7
2 50.8 52.3 53.2 53.7 54.5 55 55.7 57.1 58.4 59.2 59.7 60.4 60.9 61.8 63.4

Subtract the lower month: 1.61 – 1 = 0.61. So the value is 61% the way to month 2. I would get a percentile row for this by linear interpolation. For each percentile I can interpolate values from the month row before and after it.

// e.g. for P01
p1 = 47.6
p2 = 50.8

p1 * (1.0 - 0.61) + p2 * (0.61) = 18.564 + 30.988 = 49.552  

I did that in Numbers to get the values for all percentile columns.

Month P01 P1 P3 P5 P10 P15 P25 P50 P75 P85 P90 P95 P97 P99 P999
1.6 49.552 51.052 51.952 52.452 53.213 53.713 54.413 55.774 57.074 57.835 58.335 59.035 59.535 60.396 61.957

First, I tried the linear interpolation:

60 cm is between  59,535 (P97) and 60,396 (P99).
0.465 away from the lower, 0.396 away from the higher value. 
0.465 is 54% of the distance between them (0,861)

(1-0.54) * 97 + 0.54 * 99 = 44.62 + 53.46 = 98,08
// rounded P98

Turns out that this is a bad example.

At the extremes the percentiles are very closely spaced so that linear interpolation would give similar results. Linear interpolation in the middle would be too inaccurate.

Let’s do a better example. This time with my second daughter.

ERIKA 
at 119 days
divide by 30.4375 = 3.91 months
60.5 cm

We interpolate between month 3 and month 4:

Month P01 P1 P3 P5 P10 P15 P25 P50 P75 P85 P90 P95 P97 P99 P999
3 53.3 54.9 55.8 56.3 57.1 57.6 58.4 59.8 61.2 62.0 62.5 63.3 63.8 64.7 66.3
4 55.4 57.1 58.0 58.5 59.3 59.8 60.6 62.1 63.5 64.3 64.9 65.7 66.2 67.1 68.8
3.91 55.211 56.902 57.802 58.302 59.102 59.602 60.402 61.893 63.293 64.093 64.684 65.484 65.984 66.884 68.575

Again, let’s try with linear interpolation:

60.5 cm is between 60.402 (P25) and 61.893 (P50)
0.098 of the distance 1.491 = 6.6%

P = 25 * (1-0.066) + 50 * 0.066 = 23.35 + 3.3 = 26.65 
// rounds to P27

To compare that to approximating it on a bell curve, I used an online calculator/plotter. This needed a mean and a standard deviation, which I think I found on the percentile table left-most columns. But I also need to interpolate these for month 3.91:

Month L M S SD
3 1.0 59.8029 0.0352 2.1051
4 1.0 62.0899 0.03486 2.1645
3.91 1.0 61.88407 0.0348906 2.159154

I have no idea what L and S mean, but M probably means MEAN and SD probably means Standard Deviation.

Plugging those into the online plotter…

μ = 61.88407
σ = 2.159154
x = 60.5

The online plotter gives me a result of P(X < x) = 0.26075, rounded P26

This is far enough from the P27 I arrived at by linear interpolation, warranting a more accurate approach.

Z-Scores Tables

Searching around, I found that if you can convert a length value into a z-score you can then lookup the percentile in a table. I also found this great explanation of Z-Scores.

Z-Score is the number of standard deviation from the mean that a certain data point is. 

So I am trying to achieve the same result as above with the formula:

(x - M) / SD
(60.5 - 61.88407) / 2.159154 = -0.651

Then I was able to convert that into a percentile by consulting a z-score table.

Looking up -0.6 on the left side vertically and then 0.05 horizontally I get to 0.25785 – So that rounds to be also P26, although I get an uneasy feeling that it is ever so slightly less than the value spewed out from the calculator.

How to do that in Swift?

Granted that it would be simple enough to implement such a percentile look up table in Swift, but the feeling that I can get a more accurate result coupled with less work pushed me to go searching for a Swift package.

Indeed, Sigma Swift Statistics seems to provide the needed statistics function “normal distribution”, described as:

Returns the normal distribution for the given values of x, μ and σ. The returned value is the area under the normal curve to the left of the value x.

I couldn’t find anything mentioned percentile as result, but I added the Swift package and I tried it out for the second example, to see what result I would get for this value between P25 and P50:

let y = Sigma.normalDistribution(x: 60, μ: 55.749061, σ: 2.00422)
// result 0.2607534748851712

That seems very close enough to P26. It is different than the value from the z-tables, `0.25785` but it rounds to the same integer percentile value.

For the first example, between P97 and P99, we also get within rounding distance of P98.

let y = Sigma.normalDistribution(x: 60, μ: 55.749061, σ: 2.00422)
// result 0.9830388548349042

As a side note, I found it delightful to see the use of greek letters for the parameters, a feature possible due to Swifts Unicode support.

Conclusion

Math and statistics were the reason why I aborted my university degree in computer science. I couldn’t see how those would have benefitted me “in real life” as a programmer.

Now – many decades later – I occasionally find that a bit more knowledge in these matters would allow me to understand such rare scenarios more quickly. Thankfully, my internet searching skills can make up for what I lack in academic knowledge.

I seem to have the ingredients assembled to start working on this normal distribution chart giving interpolated percentile values for specific days between the month boundaries. I’ll give an update when I have built that, if you are interested.

]]>
https://www.cocoanetics.com/2020/10/some-statistics-for-starters/feed/ 2 10685
Adding Swift Package Manager Support – Part 2 https://www.cocoanetics.com/2020/10/adding-swift-package-manager-support-part-2/ https://www.cocoanetics.com/2020/10/adding-swift-package-manager-support-part-2/#comments Sat, 10 Oct 2020 12:12:07 +0000 https://www.cocoanetics.com/?p=10657 In the previous post I looked at some of the history of how we packaged up our library code for use by our fellow developers. We looked at some of the benefits of static libraries versus dynamic frameworks which also come with headers needed by the integrator.

Now let’s dive into the steps that were necessary for me to enable SPM support on the first few libraries DTCoreText, DTFoundation and Kvitto. It took me several days to iron out all the kinks and I’d love to share with you what I learned in the process.

We are used to using Xcode to describe what goes into a build: Which files to compile, what external libraries to link to, what resources are needed and also general build settings like the range and types of supported platforms. More precisely, these settings are contained in the project.pbxproj file inside your xcodeproj bundle.

With SwiftPM there is no such project file. Rather everything is defined in human-readable form in the Package.swift file.

For some basic terminology: we define certain products (i.e. static library, dynamic framework, app bundle etc, resource bundle, unit test bundle), that relate to a number of targets (a bucket for a bunch of source code files and resources). Here is a distinction from Xcode where target and product is used synonymously.

Package Definition

The first step, and most important one, is to add a package definition file to the root folder of the repository. It needs to be in this place because Swift Packages are referenced by the repository URL and SwiftPM will only look at the top folder for Package.swift.

Here’s the definition for Kvitto, for reference. This has all elements you might encounter, including a dependency on another package, a couple of resources on top of the definition of one product and multiple target.

// swift-tools-version:5.3

import PackageDescription

let package = Package(
    name: "Kvitto",
    platforms: [
        .iOS(.v9),         //.v8 - .v13
        .macOS(.v10_10),    //.v10_10 - .v10_15
        .tvOS(.v9),        //.v9 - .v13
    ],
    products: [
        .library(
            name: "Kvitto",
            targets: ["Kvitto"]),
    ],
    dependencies: [
        .package(url: "https://github.com/Cocoanetics/DTFoundation.git", 
		from: "1.7.15"),
    ],
    targets: [
        .target(
            name: "Kvitto",
            dependencies: [
                .product(name: "DTFoundation", 
				package: "DTFoundation"),
            ],
            path: "Core",
            exclude: ["Info.plist"]),
        .testTarget(
            name: "KvittoTests",
            dependencies: ["Kvitto"],
            path: "Test",
            exclude: ["Info.plist"],
            resources: [.copy("Resources/receipt"),
                        .copy("Resources/sandboxReceipt")]),
    ]
)

The first line might only look like a comment to you, but it is important for the swift tools to determine what syntax elements are supported. Version 5.3 is required if you have resources in any target. If you set that to something lower you get syntax errors regarding the resource definitions. If you set that to 5.3 but don’t specify resource definitions (for non-standard resources) you will get warnings about unknown files that you should either exclude or define as resources.

I found myself conflicted about that, as I had mentioned in the previous article. All code would work on Swift 5.0 and up and only the test target has resources. I could get more green checkmarks on Swift Package Index if I removed the .testTarget definition.

On the other side the swift tools let you run thusly defined unit tests from the command line and functioning unit tests also should count as a sign of good library quality. Finally, everybody should be using Swift 5.3 anyway as that’s the baseline standard since the release of Xcode 12.

That’s why I chose to leave it at that.

The basic setup of the package definition is straightforward. You have the package name, then some minimum platform versions. Note that those minimum OS versions don’t mean that that could restrict the the package to specific platforms.

The products section defines what kind of library comes out of the build process. The default setting (invisible) is to produce a static library, by specifying type: .dynamic you get a dynamic framework instead. The targets array specifies which targets will get merged into the final product.

I thought for a second that that might be good to have the resources be added to the framework instead of a separate resource bundle, like we are used to. But alas the handling of resources stays the same and they get bundled into a Product_Target.bundle. So therefore I’d rather have the static library – which will get merged into the app binary – rather than having yet another separate framework bundle inside the app bundle.

As I explained in the previous article, dynamic frameworks should be avoided if the source code for libraries is public. So we are happy with the static library default.

The dependencies section lists the external reference to other packages. You specify the repository URL and the minimum versions. The shown way with from and a version would accept all 1.x.x versions from and including 1.7.15. There are also other ways to specify an exact number or certain ranges.

Last come the targets. We have a regular target for the package and a test target for all the unit tests. If you don’t specify a path then SwiftPM expects the source code in the Sources folder underneath the target’s folder and resources in a Resources folder. I have a different structure, so I specified a custom path.

I have to exclude the Info.plist for both targets because this is used by two targets defined inside the Xcode project. And for the test target I specify two resources to be copied with the path relative to the target custom path. These copy instructions are necessary because the contained resources don’t have a type that Xcode knows how to handle. For things like strings files or XIBs you don’t have to specify anything.

Compare the dependencies key of both targets. On the one hand you see that I am referencing the external dependency of the main target. On the other hand the test target requires the main target to work. That’s also a difference to Xcode where the tested code resides inside a host application, where’s here it is compiled into the unit test bundle.

Target Considerations

You might be wondering why there is a distinction between products and targets in SPM. One reason for that you have already seen: there is no reason for the test target to be represented in a product. Simple packages will generally only have one product that might only consist of one target.

Although I already found two more reasons, to separate code out into more individual targets and then also products.

You might assume that Swift Package Manager would only all you to have code written in Swift. But you would be wrong, Any language goes, also Objective-C and other C dialects. But SPM doesn’t allow you to mix C-based languages with Swift in a single target.

In one project I had some Objective-C code for a function with a lot of ifs. I rewrote that in Swift only to find that compiling this would take more than a minute, compared to a few seconds in Objective-C. So I chose to leave the function as it was. The solution was to put it into a separate Objective-C target and refer that to an internal dependency from the main Swift target.

The other good reason for a separate target and product was to have some common data model code that would be used by internal targets and also via import in an app consuming my library. In places where the client would only need the shared definitions he would import the specific module for that. Elsewhere he would import other targets which in turn could also make use of those definitions internally.

Each product becomes its own module.

Resourcefulness

I mentioned above that you can let SPM do its own thing when it comes to standard resource types, like localised strings, XIBs, storyboards and asset catalogs. If you use string localisation though, you have to specify the project’s default language.

Other types you have to either specifically exclude or specify what should be done for it. You can either specify a .copy for each individual resource or also for the entire Resources folder. Since I have only two test files and that’s not going to change, it wasn’t too much work to add those individually.

SPM expects resources in the same folder that a target’s source files reside in (or a sub-folder thereof). The reason for that is again that there is no Xcode project file where you could specify membership of certain files to specific targets. You specify what belongs where by how it is laid out in the file system in combination of the package definition.

Say you have a single place where you have localised strings files downloaded from a translation site like POEditor but you want them to be included in different targets. A technique to achieve that is to create soft-links inside the target’s resource folders to the files. I wrote this shell script to create the lproj folders for all languages and then create the links.

#!/bin/sh

echo "Removing existing strings"
rm -rf ../TFMViews/Resources/*.lproj
rm -rf ../TFMExtension/Resources/*.lproj

PWD=`pwd`

for entry in *.lproj
do
  echo "Linking $entry..."

  mkdir ../TFMViews/Resources/$entry
  ln -s ../../../Strings/$entry/TFMViews.stringsdict \
     ../TFMViews/Resources/$entry
  ln -s ../../../Strings/$entry/TFMViews.strings \
     ../TFMViews/Resources/$entry

  mkdir ../TFMExtension/Resources/$entry
  ln -s ../../../Strings/$entry/TFMExtension.stringsdict \
     ../TFMExtension/Resources/$entry
  ln -s ../../../Strings/$entry/TFMExtension.strings \
     ../TFMExtension/Resources/$entry

done

The same technique of soft-links can also be employed for Objective-C based packages where you can link to all relevant public headers in an include folder.

Platform-specific Code

Since the package has no facility for limiting specific source code to specific platforms or OS versions, you will face the situation that certain code won’t compile for other platforms. A workaround for this limitation is the use of conditional compilation directives.

For example, everything that references UIKit cannot be compiled for macOS or watchOS, so I have a few places in DTCoreText or DTFoundation (both written in Objective-C) where the entire implementation is enclosed in:

#import <TargetConditionals.h>

#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
...
#endif

I also found that sometimes I had to also import the TargetConditionals header for the defines to work. In particular certain Objective-C category extensions in DTCoreText would not be visible in the public interface if I didn’t import this header. I have no explanation as to why, but adding the import for the header fixed it.

Inside the Xcode Project

The changes for conditional compilation aside, there is nothing you need to change in your Xcode project – unless you want to. The principal setup for the package happens in Package.swift. You can build the package with issuing swift build.

I found it convenient to add a reference to the package inside the Xcode project because this allows you to debug your code in the context of being compiled for a package. If you drag any folder (containing a package definition) into the project navigator pane, Xcode will add a local package reference for you, with a symbol of a cute little box.

In Xcode 12 there is a bug that if you do that for the project folder itself, it seems to work, but once you close the project and reopen it again, the reference becomes defunct. The way to fix it is to change the reference to “Relative to Project” and open the folder selector via the folder button and re-select the project root folder.

This also creates a scheme for building the package and the package’s products become available to link/embed to your app. Package products have an icon of a greek temple. If they are static libraries then they will get merged into the app binary, dynamic frameworks will be added to the app’s Frameworks folder.

Xcode also creates a scheme for the package, placing it in .swiftpm/xcode/xcshareddata/xcschemes/. I moved it into the shared schemes folder of the xcodeproj and renamed it to Kvitto-Package.xcscheme.

I had the watchOS platform builds on Swift Package Index fail because xcodebuild insists on building all targets, including the test target. This fails because unit tests require XCTest which does not excite for watchOS.

By providing an aptly named shared scheme it will only build the main target and I achieved green checkmarks for watchOS on SPI.

Library Unit Tests

To run the unit tests contained in the test target, all you need to do is to run swift test on the command line, from the repository root folder.

Result of running the Kvitto unit tests from the command line

Some magic was required to get that to work because test files required by the unit tests are not bundled in the .xctest bundle. For regular packages a resource bundle accessor is being automatically generated, which you can use with Bundle.module.

The accessor works by determining the path of the executable and constructing a bundle name from names of package and target. In the case of unit tests the executable is xcrun contained in the Xcode.app bundle where it has no chance of finding the Kvitto_KittoTests.bundle.

My ugly, but functional, workaround for this is as follows:

func urlForTestResource(name: String, ofType ext: String?) -> URL?
{
	let bundle = Bundle(for: type(of: self))
		
	#if SWIFT_PACKAGE
		
	// there is a bug where Bundle.module points to the path of xcrun inside the Xcode.app bundle, instead of the test bundle
	// that aborts unit tests with message:
	//   Fatal error: could not load resource bundle: /Applications/Xcode.app/Contents/Developer/usr/bin/Kvitto_KvittoTests.bundle: file KvittoTests/resource_bundle_accessor.swift, line 7
		
	// workaround: try to find the resource bundle at the build path
	let buildPathURL = bundle.bundleURL.deletingLastPathComponent()
		
	guard let resourceBundle = Bundle(url: buildPathURL.appendingPathComponent("Kvitto_KvittoTests.bundle")),
	   let path = resourceBundle.path(forResource: name, ofType: ext) else
	{
		return nil
	}
		
	return URL(fileURLWithPath: path)
		
	#else
		
	guard let path = bundle.path(forResource: name, ofType: ext) else
	{
		return nil
	}
		
	return URL(fileURLWithPath: path)
		
	#endif
}

This relies on the fact that the resource bundle will be created parallel to the xctest bundle, in the same build folder. The #if SWIFT_PACKAGE conditional compilation will only be added if this code is built as part of a swift package. With this workaround, the previous mechanisms of running the unit test scheme via Xcode continues to work.

The great thing about Swift being open source, is that we can also inspect the code for the resource accessor on GitHub. It turns out that the mentioned bug has already been addressed there. The fix was made too late to make it into Swift 5.3 in Xcode 12 but has been confirmed to be present in Xcode 12.2.

Conclusion

I find that the evolution of Swift Package Manager as progressed sufficiently to start adding support for it to my libraries. It is possible and advisable to do so in addition to other ways of integration, like Xcode subproject, Cocoapods or Carthage.

The most annoying limitation remaining is that you cannot limit targets to certain platforms or specify a range of supported OS versions per target. But those can easily be worked around with conditional compilation directives.

The quality criteria partially enforced by the Swift Package Index coupled with the discoverability of components also make it attractive for library vendors to consider supporting Swift Package Manager. Having the dependency management taken care of by Xcode is the greatest feature of all.

]]>
https://www.cocoanetics.com/2020/10/adding-swift-package-manager-support-part-2/feed/ 18 10657
Adding Swift Package Manager Support – Part 1 https://www.cocoanetics.com/2020/10/adding-swift-package-manager-support/ https://www.cocoanetics.com/2020/10/adding-swift-package-manager-support/#comments Thu, 08 Oct 2020 16:11:56 +0000 https://www.cocoanetics.com/?p=10647 As of Xcode 12, Apple has matured Swift Package Manger to a degree where it makes sense to add support for Swift packages to your libraries. There are still a few stumbling stones on the path which have no obvious solution. So I figure, I’d share with you how I got around them when I recently added SPM support to DTCoreText, DTFoundation and Kvitto.

Before SwiftPM, my general approach for a library would be to have all library code in a `Core` subfolder, with a `Source` folder containing code which gets compiled and a Resources folder for all kinds of resources, like for example asset catalogs or XIB files. 

A Bit of History

For the first 7 iOS versions the product of this product could only be a static library, Apple only introduced the ability to create dynamic frameworks for Objective-C as of iOS 8. With Swift it was the other way around: you could only have dynamic frameworks with Swift code. For the first 4 versions of Swift the ABI (Application Binary Interface) was too much in flux to allow a statically linked product. With Swift 5, in 2019, we finally got the required stability and thus Xcode gained the ability to produce static libraries containing Swift code. This is also the main reason why Xcode always added a bunch of dylibs to your apps, containing Swift wrappers to all the frameworks your app might be interfacing. Those dynamic libraries are the third kind of libraries we have encountered so far.

Oh boy, I remember all the hackery we had to do to produce a „fake“ framework that was essentially a fat static library (with slices for all supported processors) and all public headers. We would that so that somebody using our library could drop it easily into their project and have all exposed interfaces be visible. In Objective-C you would need to have the header files available for public functions and classes contained in the library. Those `.framework` bundles provided a nice encapsulation of that, so that it was almost like handling a single package adding a third-party framework to your app.

Dynamic frameworks – in real life, on device – actually don’t contain any headers any more as those become useless after compiling. The main benefit of first-party dynamic frameworks is that Apple can have their APIs and code shared between all apps installed on the device. The one and only UIKit framework – installed as part of iOS – is being accessed by and dynamically linked to all installed iOS apps. Only a single instance is present in RAM at any time. Custom frameworks cannot be shared between multiple apps due to all apps being contained in their own little sandbox. Every iOS app containing DTCoreText for example has to have its unique copy of it inside its app bundle. If an app has a great deal of third-party frameworks that process of loading all frameworks into memory and dynamically linking can noticeably slow down app launch.

Swift Never Had Headers

With the innovations brought with Swift also added the concept of modules to Xcode. The Swift Programming Language Website offers this definition of modules.

A module is a single unit of code distribution—a framework or application that is built and shipped as a single unit and that can be imported by another module with Swift’s import keyword. Each build target (such as an app bundle or framework) in Xcode is treated as a separate module in Swift.

When you import a module in your code, then Xcode somehow magically knows all about the public interfaces contained in it, without ever having to have a separate header file. I don’t know how exactly that works, but I am glad that it does!

It was the problem of discovering and integrating third-party libraries into your codebase, that Cocoapods was invented to solve. The first public release of it was almost exactly 9 years ago, in September 2011. With the default settings – not using frameworks – Cocoapods would compile the third-party code and merge it with your own, resulting in a single monolithic app binary. And of course it would manage all those Objective-C headers for you. If you added use_frameworks! to your Podfile then the strategy would change to instead create a framework/module per pod/library. And that would be the requirement for when you were using external libraries written in Swift, or so I thought …

I’ve always used that in apps I am working on which use Cocoapods for dependencies. Imagine me rambling on to a client of mine about the disadvantages of dynamic frameworks, trying to convince him of the benefits of Swift Package Manager. Imagine my surprise when we inspected his app’s bundle, only to find but a single framework in there. All the third party code he had ended up fused with the app binary, my library – written in Swift and integrated via git submodule and Xcode sub project – resulting in the only dynamic framework in his app.

By default, CocoaPods had been doing all along what we know to be the smarter choice: if third party code is available, to merge the object code it into the app binary. Of course closed-source frameworks which are only available as dynamic framework binaries leave you without this option. Personally I try to avoid those, like the devil avoids holy water.

Oh and I also will be the first to admit that I could never warm myself to Carthage. I have never looked at it. As far as I understand, the difference in approach versus CocoaPods is that Carthage only needs a repo URL to add a component, whereas CocoaPods needs a Podspec and will generate an Xcode workspace for you where all dependencies are set up in a Pods project. I believe it might be this workspace wizardry that might put some people off Cocoapods.

Resourceful Swift Packages

Before the current version 5.3 of SPM the two big remaining pain points have been the lack of handling of resources and no support for distributing binaries as packages. Those have now been remedied and what’s the best part is that Swift packages now have proper integration in Xcode 12.

Another big advantage that CocoaPods had over other dependency managers was the existence of the “trunk”, a centralised repository of available pods. There you could search and find libraries that would fulfil certain needs of yours. Another important aspect would be that for a version to be released on the CocoaPods trunk, you would have to “lint” your pod spec which would validate the syntax and make sure that the library builds without errors or warnings.

Apple (and the SwiftPM open source community) have worked on polishing the tool itself. But the central repository with validation aspect of package management was unfilled. Until Dave Verver stepped and established the Swift Package Index. In his own words:

The Swift Package Index is a search engine for packages that support the Swift Package Manager.

But this site isn’t simply a search tool. Choosing the right dependencies is about more than just finding code that does what you need. Are the libraries you’re choosing well maintained? How long have they been in development? Are they well tested? Picking high-quality packages is hard, and the Swift Package Index helps you make better decisions about your dependencies.

Dave launched the SwiftPM Library in the fall of 2019 which in June 2020 got re-engineered as the Swift Package Index which we use today.

It was this implementation of a central index, focussing on package quality, that pushed me over the edge to finally start embracing SPM. With CocoaPods it has been a tedium to set up a CI server to keep building your libraries for every change to make sure that nothing breaks. By contrast, SPI builds your package with Swift versions 4.0, 5.0, 5.1, 5.2, 5.3 for iOS, macOS Intel, macOS ARM, Linux, tvOS and watchOS and will then show on the package’s page where that worked.

This page gives a very nice overview by which developers can gain an idea as to the quality of this library. And for us project owners it provides an incentive to try to maximise the number of green checkmarks you see.

SPI still tracks 5.3 as “beta” although Xcode 12 has gone gold a month ago. The reason being that Apple has rushed out Xcode 12 and the finalised support for building universal apps that can also run on Apple Silicon will be in Xcode 12.2 – available later this year.

I also like how SPI tracks both the latest stable release (via tag on master) as well as the progress on the develop branch. I wished for those builds to be coming sooner, ideally right after pushing changes to the GitHub repo, but sometimes it can take a long time for the builds to be scheduled. Also a way to retry a failed build would be very nice, as we are used to from Travis-CI or GitLab-CI.

Conclusion

At this point I wanted to go into the things I learned so far from adding SPM to some of my libraries, but I am still fighting with SPI over some of those coveted checkmarks. Also this article has already turned out longer than I wanted it to, that I’ll do that in the next one.

Let me know if that is of interest to you, by dropping me a tweet. Are you considering adding SPM yourself? Which part did you struggle with?

Part 2 is here.

]]>
https://www.cocoanetics.com/2020/10/adding-swift-package-manager-support/feed/ 24 10647
DTFoundation 1.7.15 https://www.cocoanetics.com/2020/08/dtfoundation-1-7-15/ https://www.cocoanetics.com/2020/08/dtfoundation-1-7-15/#comments Mon, 24 Aug 2020 15:48:22 +0000 https://www.cocoanetics.com/?p=10633 User nostradani contributed support for Swift Package Manager. I don’t even know his/her full name, but by the looks of things this was a hell of a lot of work.

Honestly I haven’t even looked adding SPM-support to my frameworks yet, but I hear that if you have resources in your framework project you have to wait until Swift 5.3. anyway. DTFoundation doesn’t, so I am glad somebody stepped up and did the work.

But in a SwiftUI app of mine I am referencing two Swift packages. It is quite convenient having this be integrated into Xcode.

The new version is tagged on GitHub and also available on Cocoapods.

]]>
https://www.cocoanetics.com/2020/08/dtfoundation-1-7-15/feed/ 10 10633
First Quarter 2.0 https://www.cocoanetics.com/2016/03/first-quarter-2-0/ https://www.cocoanetics.com/2016/03/first-quarter-2-0/#comments Sun, 13 Mar 2016 13:14:54 +0000 https://www.cocoanetics.com/?p=10065 Since November 2015, I had been really busy because our most valued client International Color Services started a new project with us. Then, at the turn of the year, I become the new CEO of the family business, Drobnik KG, when my dear father retired.

Half of our revenue had come from real estate, but now all of that had been sold off.  What remains is a pure software company, dedicated to world-class level iOS and Mac development.

On my German company blog I referred to this transition as re-launch of the company because in my mind it is like we started a whole new business.

History Perspective

The original Drobnik KG was had been founded by me, my father and my brother on November 6th, 2001 because I had gotten the idea from author Robert Kiyosaki’s teachings that you should not be self-employed but a be a business owner. And that real estate is a great thing.

Exactly 8 years later, I was forced to go full time after having gotten laid of from Amdocs where I had been acting as their Windows helpdesk for the Vienna office. At that time, my one year old iOS contracting had grown to a level where I was pondering reducing my commitment to Amdocs from 5 to 4 days a week. But the decision was made for me, when I got laid off with 3 months pay.

So in December 2009, I started my Cocoanetics Parts Store and found that I was making enough money right from the start. The learning from this turn of events is that you sometimes have to jump into the deep end of the pool just to learn that you can swim.

People Person

In the 6 years since then, the income from software development came to be on par with those from real estate. The main reasons for this development were many, but the most important ones where establishing long term business relationships with ELO Digital Office (Germany) for developing the ELO app for iPhone and (later) iPad and then International Color Services (USA) for developing the iCatalog system.

But true to Robert Kiyosaki’s word, in both cases I found somebody to take over the day-to-day work for these clients to free myself up for new endeavors. The first one is René Pirringer (self-employed with ciqua e.U., started November 2011). The second one, my first full-time employee, is Stefan Gugarel whom I hired in April 2012)

After these happy accidents business stagnated for about 3 years, but at least it was stable enough to allow me to work on a book for Manning Publications. That made it impossible for me to do any personal contracting throughout 2014 as writing this tome took much longer than initially planned. Nevertheless I was excited to hold copies of my book that I had worked so long and hard for, in January 2015.

In April 2015 I co-founded two more startups. I founded ProductLayer with 3 other guys to establish a free-to-use product database on the Internet. And Buschenreiter KG was a friend’s trading business into which I invested. From this you infer a certain longing to be  surrounded with smart and successful people.

Ever since my being downsized 6 years ago, I never again wanted to be employed. Not by Google. Not by Facebook. Not by Badoo. Those where the company’s whose recruiters contacted me during this time.

Looking Up

In May 2015, Stefan Eipeltauer from Fokus Kind approached me about making a series of baby-related apps. Surprisingly, he ended up acquiring iWoman, which was the first app I ever had on the app store. And this was the beginning of the third important business relationship.

Fast forward to the turn of the year. I had an instinct that I might be able to hire a new guy after taking over the company, so I made a video ad to act on this feeling. In January I put together a business plan for 2016 where I planned all the probable incomings and outgoings and found that should be able to afford to hire a new junior developer.

Lukas Weinwurm become this new employee on February 1st, 2016. We comes out of the world of boring web development and started his journey to become an awesome Swift developer with me. I put him in charge of developing the apps  for the Fokus Kind, while keeping a watchful eye and lending a helping hand whenever necessary.

The Immediate Future

I like to be looking at advanced iOS development topics and to consult as an expert on areas that I have knowledge in. I like to share my experience and knowledge. I love to be building long-term fruitful partnerships. I love to be part of profitable businesses.

So far, after the first Quarter of Drobnik KG 2.0 has almost passed, things are looking very promising. Hopefully this trend continues to go upwards and I can continue to build my dream team of happy Apple experts, also known as Cocoanetics.

 

]]>
https://www.cocoanetics.com/2016/03/first-quarter-2-0/feed/ 1 10065
Video Job Pitch https://www.cocoanetics.com/2015/12/video-job-pitch/ https://www.cocoanetics.com/2015/12/video-job-pitch/#respond Tue, 29 Dec 2015 15:03:13 +0000 https://www.cocoanetics.com/?p=10033 Here’s a short video where I am trying to make a point of why you might like to be working in Vienna, Austria. My company Drobnik KG is looking for a Junior developer to join the team. And video is what young people respond to best these days, don’t they?

As I see it there is no greater combination of:

  • great place to live
  • great place to work
  • great people to work with and learn from
  • love for all things Apple

Wouldn’t you agree?

Links:

]]>
https://www.cocoanetics.com/2015/12/video-job-pitch/feed/ 0 10033
DTDownload 1.1.2 https://www.cocoanetics.com/2015/01/dtdownload-1-1-2/ https://www.cocoanetics.com/2015/01/dtdownload-1-1-2/#respond Fri, 30 Jan 2015 14:17:35 +0000 http://www.cocoanetics.com/?p=9468 This minor update for DTDownload adds the ability to affect the ordering of downloaded URLs.

Changes

  • ADDED – Support for downloading first added URLs first or last added URLs first
  • READDED – armv7s to lib building
  • CHANGED – Updated DTFoundation to require 1.7.4
  • UPDATED – Changed the way appledoc is built

The sole contributor to this update is Stefan Gugarel.

The update is available via Cocoapods and tagged on GitHub.

]]>
https://www.cocoanetics.com/2015/01/dtdownload-1-1-2/feed/ 0 9468
Looking Back and Forward https://www.cocoanetics.com/2013/12/looking-back-and-forward/ https://www.cocoanetics.com/2013/12/looking-back-and-forward/#respond Tue, 24 Dec 2013 14:53:49 +0000 http://www.cocoanetics.com/?p=8905 The older you get the quicker a year passes it seems. 2013 was a good year for us at Cocoanetics. Not really exceptional, we’re still waiting to get our great chance. But we cannot complain either, 2013 gets the “solid!” predicate.

Here at Cocoanetics there are three developers working on two projects for external clients and one working on our own products. We are thankful to our clients to keep putting their faith in our abilities as iOS developers and we feel that we put high quality work into those projects.

Long Term Contracts

This is not a given nowadays as many companies are looking to countries where iOS development can be gotten at a much cheaper rate then we have to charge being located here in central Europe.

We believe that in both cases our clients are happy with what we deliver, but looking forward, we are actively working to increase the amount of automated testing for all our projects as well as enhance manual testing where our resources permit that.

Having such long term contracts allowed me to hire a full time employee in April 2012 and my goal for 2013 was to bring him to WWDC. Even the quick sellout of the conference couldn’t keep me from making this plan a reality. In retrospect this was an amazing feat and I don’t dare to make a prediction for WWDC 2014. I would love to repeat this, but the “solid!” from the introduction means that there is little room to repeat such an extravaganza every year. We’ll see how it turns out.

Regarding the “own products”, the two top netting were Linguan, our Mac-based localisation tool and DTRichTextEditor, a rich text editing component. At the time of this writing both are about 2 years of age, give or take a few months. The monthly income that Linguan yielded was less then a single sale of the editor. A couple of hundred dollars per month were not enough to invest seriously into the mac app.

Linguan

The straw that broke the camel’s back was when Apple made app sandboxing mandatory in Spring 2013. From this point on you could only fix severe bugs. This was the time when it started to dawn on us that we had to sell Linguan or retire it. We waited to see what Apple would announce in terms of developer tools at WWDC but the only related announcement was that there would be a new XML-based XIB format. This meant that you could remove the need remote-control ibtool for parsing/modifying XIBs which was one of two problems for sandboxing.

So in August my partners and me decided to seriously go looking for somebody to acquire Linguan from us. I even tried to contact Daniel Pasco from Black Pixel, but never got any response, except a tweet that he is getting too many emails. Poor guy! When setting the sale price at first we went with about 3 years worth of sales because 2-3 years of profits seem to be the norm for determining the valuation of a business. That came to a round 10000 Euros.

Shortly later we dropped the price to 7500 Euros, about 2 years of profits. I did so with a heavy heart since Linguan was my brainchild and never lost my belief that Linguan could be great and net way more if only somebody gave it some serious love. Eventually I started to get some emails, some with low-ball offers, some proposing weird cooperation constructs. Finally one shining knight appeared who won the head of the princess. Ireland-based Peer Assembly acquired Linguan and my hopes are now that they will do the necessary work to make Linguan sandbox-compatible.

My hope is that in 2014 Linguan “will do the Phoenix” and reemerge from the development dungeons refreshed, renewed and even more awesome.

DTRichTextEditor

Right until right before WWDC I had always assumed that Apple would add something to iOS 7 that would make DTRichTextEditor obsolete. Then they did and they didn’t. TextKit being iOS7-only meant that if you wanted to use UITextView for editing HTML or other rich text you would have to forego supporting iOS 6 or less. Also there are many things missing from TextKit that DTRichTextEditor has: customisability, support for text attachments (images, videos, custom objects). The most important piece still missing being a way to create HTML from NSAttributedStrings.

I did see sales of the editor going down right in front and shortly after WWDC. No surprise there. Many of my other components on my parts store had great demand at a time only to fall to the wayside later. I’m suspecting that both open source projects as well as low-price commercial components are commoditizing components.

But did DTRichTextEditor fall into oblivion? Now that iOS 7 penetration hovers around the 74% mark? Actually it didn’t! My sales team (aka my wife) is reporting stronger sales of it than ever before.  So either people are unhappy with TextKit or there is something in my component that people need or want and cannot get otherwise.

DTCoreText

This ongoing demand is driving additions and improvements in DTCoreText my open source project that DTRichTextEditor is using for parsing HTML, rendering and creating HTML. In 2013 DTCoreText went from version 1.1.0 all the way to 1.6.10, more than two dozen releases!

It rarely happens that somebody is willing to sponsor enhancements for DTCoreText. I found that when I began to tag GitHub issues with “Sponsor Needed”.  But it seems to me that my clients are way more willing to pay for enhancements that are not “given away”.  So there were a couple of additions to DTRichTextEditor funded by the large corporations using it, and of course some of the things found their way back to DTCoreText if they were related to the core features there.

DTCoreText is also the most watched and most forked of all my open source projects. Many people seem to be using it and understanding it enough to send pull request with fixes and enhancements. The most wonderful addition in 2013 was the implementation of CSS style cascading. It took a couple of tries to get it working, but with the help of extensive unit testing we got this nicely stable in record time (versions 1.6.4 – 1.6.6).

Machine-readable Codes

I was ready to turn my back on anything rich text related around WWDC 2013, but while I keep making money with it I will keep supporting it. But my attention turned to another technology when broad support for it got announced at WWDC. I’m referring to barcodes, or as Apple keeps calling it: Machine-readable codes.

My inspiration was well explained in the most recent episode of the Cocoanetics podcast, but the gist of it is that I am seeing an enormous dormant opportunity arising from iOS adding such broad support for barcode scanning and generation. In the past you had to either license an expensive third-party scanning library or fight your way through cross-platform open source libraries. iOS 7 is a game changer there.

The built-in ability to scan the most common barcode symbologies will enable a new breed of mobile apps that bring together the digital and physical worlds. In the past barcodes only had value to cashiers at the point-of-sale because those guys had a database to access to retrieve product names and prices. Now every iOS developer will be able to make apps that provide some great value to their users for allowing them to query, inventory, organise and look up physical products via their barcodes.

This inspiration formed the basis for the Product Layer project which I and several of my friends are going to put some serious work into in 2014.

My Book

Loving to write it was always a given that one day I would be writing a book. I tried to get my foot in the door with Erica Sadun’s publisher by offering to act as tech reviewer for several of her books. The first book idea I pitched to them was about reverse-engineering, never got any response there. Still I reviewed chapters sent to Erica to her reviewer-fans and provided feedback and suggestions, pointed out issues and suggested better ways to state something.

By an amazing coincidence I got contacted by a book publisher in September. He told me that they are looking for some iOS experts to make half-size books (200-300 pages) to improve their offerings in the iOS area. Being full of ideas I immediately had 3 book ideas to offer which I used many words to describe to him. He then asked me which of these I felt the most passionate about and to this I truthfully responded: “the one about barcodes!”

I put down my initial experience about getting from a proposal to a book contract in a blog article. Since then I spent about 80 percent of my time working on the book.

The first feedback I got from my writing sample (a section from a longer chapter) was that my style is quite different from other books by the same publisher. I was writing more “tutorial style”, quite “terse” and for a higher skill level of iOS developer than is the norm. But I uttered a big sigh of relief when they came back to me and told me that this style is great for the intended audience.

I like to belief that my approach to this book is also different and intriguing. Instead of focussing on a single technology or API like most other books I am doing a “vertical tech book”. The idea is to use barcodes as the common thread across all chapters, but look at several different technologies that were added in iOS 7. Each such technology would be taught in the context of barcodes.

So even if you are not (yet) that much into barcodes, but were wondering about some of the hot new topics in iOS 7 then this book will interest you as well. The main advantage of this approach for myself is that this is making it way more interesting to research and write for me as well.

Being a novice book author who does not speak English natively it is very important to have a good publisher at your side who can teach you a few tricks on how to present the material in a way that people can do something with it. Over several meetings with an Instructional Designer and my Development Editor I learned about many things that I would have never discovered myself. Things that greatly increase the quality of my writing.

I am learning a great deal and I am looking forward to seeing the final result some time in the middle of 2014 printed on actual paper. You will be able to pre-order the book in a few months and then you will be able to access the already written chapters in their draft stage. I have one more chapter to deliver before this pre-order capability is started.

Other Plans

Amongst all these activities there were recruiters from Google and Facebook knocking at my door, some friends at Apple reiterated their question if I wouldn’t want to consider moving to Cupertino. To all of them I said the same thing: I like to live and work in Austria and I also like to do a little bit of enterprising. Google even offered to buy my business, but this only made me smile: one of my clients sees Google as their main competitor. I smiled because I imagined the kind of heart attack they would get if I sold out to Google. Isn’t it nice to be looking out for the health of my partners like that?

Honestly, I am hesitant to predict what precisely is going to happen to my business or products or activities in general in 2014. 2013 has shown that 3 people can live of the current level of activities, survive, but little more. If the level of income stay where it is now then we won’t have to worry about surviving 2014.

But I do have some dreams: I would like to work with even more people as part of the same company. Also it would be great to move from the current room I call my office to an actual office. Maybe have sufficient room to be even able to sub-let a part to friendly startups. I want to be around creative and enterprising people, hoping that this would make me personally a little less jaded.

Speaking of startups: maybe Product Layer is destined for greatness as well. We’ll know more when there is something to talk about publicly.

Either way I am hoping that you found 2013 to be good to you as well. Merry Christmas and a Happy New Year! May 2014 be even better to all of us!

]]>
https://www.cocoanetics.com/2013/12/looking-back-and-forward/feed/ 0 8905
Out of Office https://www.cocoanetics.com/2013/07/out-of-office-3/ https://www.cocoanetics.com/2013/07/out-of-office-3/#comments Mon, 15 Jul 2013 01:35:14 +0000 http://www.cocoanetics.com/?p=8501

We’re offline for “maintenance” until July 22nd with no way to receive emails or respond to your requests and orders. We mention this so that you know why you won’t hear from us until then. We haven’t forgotten about you, we’ve just remembered about ourselves.

So please be patient while we’re recharging.

]]>
https://www.cocoanetics.com/2013/07/out-of-office-3/feed/ 3 8501
Highlight: Pocket Informant Pro https://www.cocoanetics.com/2013/05/highlight-pocket-informant-pro/ https://www.cocoanetics.com/2013/05/highlight-pocket-informant-pro/#comments Sat, 18 May 2013 06:17:18 +0000 http://www.cocoanetics.com/?p=8208 Alex Kac, CEO/Founder of WebIS yesterday announced that Apple had approved their major new version 3 of Pocket Informant. We like to highlight satisfied clients who put our components to good use, Pocket Informant 3 uses DTRichTextEditor.

WebIS highlights “Rich Notes” as major new feature of the new version.

Rich Notes – we now have a full featured Rich Text editor for notes. We’ve got a lot more coming here, but today you can set styles, fonts, typefaces, lists, and more.

WebIS has been a key sponsor of the component helping us to enhance the feature set and API of the component with support for lists. This had long been on our roadmap, but without WebIS’ sponsorship we could not have brought it to fruition.

Pocket Informant Pro 3

WebIS integrated the cutting edge version 1.5 of DTRichTextEditor to enable this feature. Kac had this to say when asked for a testimonial:

“Working with Oliver has been a tremendous joy. We simply could not have shipped on-time and with the quality of rich text support as we did without him. The DTRichTextEditor is a fabulous control with lots of great tools, utilities that we were able to use such as DTHTMLWriter and other classes. I heartily recommend both the control and Oliver.”

The Pro Version of Pocket Informant will be released on Tuesday May 21st as a free update to existing 2.x customers. WebIS also offers a free Go! version which will be receiving the 3.0 treatment in the near future.

If you buy a license to DTRichTextEditor you receive full access to all source code residing on our private git server. To celebrate the launch of Pocket Informant please mention keyword PI10 for 10% discount when you buy a license until May 31st.

]]>
https://www.cocoanetics.com/2013/05/highlight-pocket-informant-pro/feed/ 3 8208
AutoIngest for Mac 0.4.1 https://www.cocoanetics.com/2013/05/autoingest-for-mac-0-4-1/ https://www.cocoanetics.com/2013/05/autoingest-for-mac-0-4-1/#comments Sat, 11 May 2013 12:44:46 +0000 http://www.cocoanetics.com/?p=8172 Sorry, but some bugs had crept into yesterday’s release of AutoIngest for Mac. So we fixed them.

Changes

  • FIXED: Crash if report download folder had spaces
  • FIXED: Auto-Organizer was watching non-existent folder
  • FIXED: Uncompressing files did not remove .gz extension
  • FIXED: Menu Option was not modified if Auto-Sync was occurring

We have already been able to verify that Newsstand report downloading works. Though we still are looking for somebody who has Opt-In reports to help us see if the downloading works for these as well.

I hope you are enjoying this new version AutoIngest for Mac! It is available via auto-update, direct download as well as full source on GitHub. The binary builds have Sparkle for OTA updating and are signed with Developer ID.

]]>
https://www.cocoanetics.com/2013/05/autoingest-for-mac-0-4-1/feed/ 4 8172
Component Clients: We Moved to GIT https://www.cocoanetics.com/2013/04/component-clients-we-moved-to-git/ https://www.cocoanetics.com/2013/04/component-clients-we-moved-to-git/#respond Thu, 04 Apr 2013 17:30:18 +0000 https://www.cocoanetics.com/?p=7957 If you have purchased components from us in the past you should have received a note from our sales team asking you for what credentials you would like on our new GitLab server.

As of this moment all Subversion access has been terminated. The components that we are still offering for sale have all been migrated to git. Of the discontinued products a few might be moved to DTFoundation are another open source project if there is any interest.

If you have no email from our sales team regarding your new credentials please email us.

]]>
https://www.cocoanetics.com/2013/04/component-clients-we-moved-to-git/feed/ 0 7957
We’ll be back soon https://www.cocoanetics.com/2013/03/well-be-back-soon-3/ https://www.cocoanetics.com/2013/03/well-be-back-soon-3/#respond Tue, 26 Mar 2013 06:53:57 +0000 http://www.cocoanetics.com/?p=7927 We’re out of office until March 31st. There will be no e-mail checking until we get back, so please be patient with your requests and wishes.

We’ll be back at your service on March 31st.

]]>
https://www.cocoanetics.com/2013/03/well-be-back-soon-3/feed/ 0 7927
Fast Hashing https://www.cocoanetics.com/2013/02/fast-hashing/ https://www.cocoanetics.com/2013/02/fast-hashing/#comments Mon, 25 Feb 2013 17:21:06 +0000 http://www.cocoanetics.com/?p=7663 In DTCoreText there is the DTCoreTextParagraphStyle class which represents an Objective-C wrapper around CTParagraphStyle. This has a method createCTParagraphStyle which creates the actual Core Text object to put in attributes of an NSAttributedString. It also knows how to create an NSParagraphStyle, but since this only exists from iOS 6 upwards and lacks a few features we’re still using the Core Text variant everywhere.

Due to the way how DTCoreText works I need to createCTParagraphStyle whenever I am constructing a sub string of the generated attributed string. This led to an unnecessarily large amount of CTParagraphStyle instances being created. So I had implemented a method long time ago to cache thusly created CoreText objects based on the ivars.

Though this was causing some problems in DTRichTextEditor and so I yanked the caching back out. Now the project has developed much further and so I felt I would want to give the caching another go. Here’s something interesting I learned.

As always please don’t hesitate to leave a comment if you have a better method to share …

The previous method of getting a unique key was using a naughty trick. Even Core Foundation objects usually have a description and this I was using as the key:

// this is naughty: CTParagraphStyle has a description
NSString *key = [(__bridge id)ctParagraphStyle description];

This had several drawbacks. First creating an NSString description for many internal values is rather slow and also for reasons that I did not investigate much further there where scenarios where this would break. Possibly because I have 2 additional items inside the paragraph style that this would ignore: lists and blocks.

NSString as Cache Key

So the first approach I came up with this morning was to create my own NSString to be used as the cache key.

- (NSString *)_cacheKey
{
   NSMutableString *key = [NSMutableString stringWithFormat:@"%d-%f-%f-%f-%f-%f-%f-%d-%f-%f-%f", 
                              _alignment, 
                              _firstLineHeadIndent, 
                              _defaultTabInterval, 
                              _paragraphSpacing, 
                              _paragraphSpacingBefore, 
                              _headIndent, 
                              _tailIndent, 
                              _baseWritingDirection, 
                              _lineHeightMultiple, 
                              _minimumLineHeight, 
                              _maximumLineHeight];
 
   for (id tab in _tabStops)
   {
      CTTextTabRef tabStop = (__bridge CTTextTabRef)tab;
 
      CTTextAlignment alignment = CTTextTabGetAlignment(tabStop);
      double location = CTTextTabGetLocation(tabStop);
 
      [key appendFormat:@"-tab:%d-%f", alignment, location];
   }
 
   for (DTTextBlock *textBlock in _textBlocks)
   {
      [key appendFormat:@"-block:%lx", (unsigned long)[textBlock hash]];
   }
 
   for (DTCSSListStyle *listStyle in _textLists)
   {
      [key appendFormat:@"-list:%lx", (unsigned long)[listStyle hash]];
   }
 
   return key;
}

To prove that this indeed works, I constructed a new unit test case.

@implementation DTCoreTextParagraphStyleTest
 
- (void)testCache
{
   // make a test style
   DTCoreTextParagraphStyle *paraStyle = [[DTCoreTextParagraphStyle alloc] init];
   paraStyle.lineHeightMultiple = 2.0f;
   paraStyle.headIndent = 30;
 
   CTParagraphStyleRef para1 = [paraStyle createCTParagraphStyle];
   CTParagraphStyleRef para2 = [paraStyle createCTParagraphStyle];
 
   STAssertEquals(para1, para2, @"Two successife Paragraph Styles should be identical");
 
   // change something
 
   paraStyle.tailIndent = -20;
 
   CTParagraphStyleRef para3 = [paraStyle createCTParagraphStyle];
 
   STAssertTrue(para2!=para3, @"Paragraph Styles should not be identical after change");
 
   // change back
 
   paraStyle.tailIndent = 0;
 
   CTParagraphStyleRef para4 = [paraStyle createCTParagraphStyle];
 
   STAssertEquals(para1, para4, @"Paragraph Styles should be identical after change back");
 
   // cleanup
 
   CFRelease(para1);
   CFRelease(para2);
   CFRelease(para3);
   CFRelease(para4);
}
 
@end

The point of this test was to first show that creating two CTParagraphStyle instances in succession should return the same result. Then by modifying the underlying DTCoreTextParagraphStyle I would get a different result. Finally by reverting the change I would again get the same result as the first two.

Unfortunately this method is not very fast. Especially if it is called many times over a lengthy HTML document you would see it become a hot spot in Instruments. In this screenshot the marked purple mountain is me parsing the War&Peace HTML in the DTCoreText demo app.

Parsing War & Peace

You can see how the createCTParagraphStyle method is calling _cacheKey and this making up the lion share of the execution time in this method (432 ms of 553 ms). If you double-click on it you see a big red sea.

NSString Construction

Apparently 97.7% of the samples taken execution was within this stringWithFormat: method. At this point it is quite clear that this is not an optimal approach. I suspect the main reason for lackluster performance on this line is that it takes quite some time to format floating point numbers as NSString.

I googled around a bit further, but couldn’t find a simple, yet easy to implement method for getting a better cache key. Then it dawned on me …

CommonCrypto

Why not simply putting all values in a struct and then creating an md5 checksum over that?

- (id )_cacheKey
{
   NSMutableString *tabsBlocksListsDescription = [NSMutableString string];
 
   for (id tab in _tabStops)
   {
      CTTextTabRef tabStop = (__bridge CTTextTabRef)tab;
 
      CTTextAlignment alignment = CTTextTabGetAlignment(tabStop);
      double location = CTTextTabGetLocation(tabStop);
 
      [tabsBlocksListsDescription appendFormat:@"-tab:%d-%f", alignment, location];
   }
 
   for (DTTextBlock *textBlock in _textBlocks)
   {
      [tabsBlocksListsDescription appendFormat:@"-block:%x", [textBlock hash]];
   }
 
   for (DTCSSListStyle *listStyle in _textLists)
   {
      [tabsBlocksListsDescription appendFormat:@"-list:%x", [listStyle hash]];
   }
 
   struct  {
      CGFloat firstLineHeadIndent;
      CGFloat defaultTabInterval;
      CGFloat paragraphSpacingBefore;
      CGFloat paragraphSpacing;
      CGFloat headIndent;
      CGFloat tailIndent;
      CGFloat listIndent;
      CGFloat lineHeightMultiple;
      NSInteger alignment; // make it full width, origin is uint8
      NSInteger baseWritingDirection; // make it full width, origin is int8
      NSUInteger tabsBlocksListsHash;
   } allvalues = {0,0,0,0,0,0,0,0,0,0,0,0, nil};
 
   // pack all values in the struct
   allvalues.firstLineHeadIndent = _firstLineHeadIndent;
   allvalues.defaultTabInterval = _defaultTabInterval;
   allvalues.paragraphSpacingBefore = _paragraphSpacingBefore;
   allvalues.paragraphSpacing = _paragraphSpacing;
   allvalues.headIndent = _headIndent;
   allvalues.tailIndent = _tailIndent;
   allvalues.listIndent = _listIndent;
   allvalues.lineHeightMultiple = _lineHeightMultiple;
   allvalues.minimumLineHeight = _minimumLineHeight;
   allvalues.maximumLineHeight = _maximumLineHeight;
   allvalues.baseWritingDirection = _baseWritingDirection;
   allvalues.alignment = _alignment;
   allvalues.tabsBlocksListsHash = [tabsBlocksListsDescription hash];
 
   // create md5
   void *cStr = &amp;allvalues;
 
   void *digest = malloc(CC_MD5_DIGEST_LENGTH);
   CC_MD5( cStr, (CC_LONG)sizeof(allvalues), digest);
 
   return [NSData dataWithBytesNoCopy:digest length:sizeof(digest) freeWhenDone:YES];
}

There are some things that – due to their dynamic nature – still have to go in a string. Then I am putting all numbers into the allvalues struct. The dynamic string is represented by its hash. CC_MD5 creates an MD5 digest/checksum of a few bytes length. Finally we transfer malloc’ed digest into an NSData which takes over the responsibility of freeing the bytes when it is being deallocated.

Note how I am changing the width of baseWritingDirection and alignment in the struct. The reason for this – as I found out later – is that the compiler apparently pads narrower values to word boundaries, probably for performance reasons. If you leave these as 1-byte-wide then the assignment zeroes out the member variable, but the padding bytes are still random values.

This is the fasted method of getting bytes into an Objective-C object because it uses the bytes as they are without needing to copying them off somewhere. Speaking of copying, NSData kindly implements the NSCopying protocol which is needed if you want to use it as a cache key. Keys for NSCache and NSDictionary need to be copyable, i.e. implement the NSCopying protocol.

Another thing I learned only by finding that some other unit tests would suddenly break with the new implementation. I didn’t zero the contents of the struct instead relying on LLVM doing that for me. But apparently there are some instances where this is not true. By setting all values to 0 first I was able to get all unit tests back to working.

How does it Measure Up?

This can easily be determined by using CFAbsoluteTime to measure the duration it takes to execute this method.

- (CTParagraphStyleRef)createCTParagraphStyle
{
   CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
   id cacheKey = [self _cacheKey];
   CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
 
   NSLog(@"time: %f", endTime - startTime);
 
   //...

Putting a few results for the same UI activity next to each other we find that the md5 method is 5 times as fast as the stringWithFormat: approach.

Comparison

I’ll take the left column please, thank you.

Inspecting this in Instruments paints the same picture. Time spent in _cacheKey dropped to around 100 ms. Much better.

NSData Direct?

There is even another way to use NSData as cache key. Apparently NSData is using up to 80 bytes from its bytes payload as good hash as we can see in Apple’s Open Source code. They use an CFHashBytes macro which uses the ELF hashing algorithm, as explained in this question on Stack Overflow. Thanks Saurabh Garg for pointing this out to me!

This basically means that if the number of your ivar bytes is guaranteed to be less than 80, then you can also directly wrap the struct into an NSData and use this as key instead.

My first try actually seemed to reduce performance again, because having to unnecessarily copy around bytes takes a bit of time. The duration of _cacheKey went up from 100 to around 120 ms.

NSData with copy

So I rolled up my sleeves and thought back to a time when I was still creating C-structures dynamically with malloc. Since we know that the struct’s data will be put into an NSData anyway there is no need to let the compiler create an allvalues variable on the stack (as all temporary variables are) but use malloc to create it on the heap right from the start.

Instead of creating a new variable we just define a variable type allvalues_t. Then we use the arrow operator to fill in the member variables.

// ...
   // a struct that takes on all sub-values
   typedef struct {
      CGFloat firstLineHeadIndent;
      CGFloat defaultTabInterval;
      CGFloat paragraphSpacingBefore;
      CGFloat paragraphSpacing;
      CGFloat headIndent;
      CGFloat tailIndent;
      CGFloat listIndent;
      CGFloat lineHeightMultiple;
      CGFloat minimumLineHeight;
      CGFloat maximumLineHeight;
      CTTextAlignment alignment;
      CTWritingDirection baseWritingDirection;
      NSUInteger tabsBlocksListsHash;
   } allvalues_t;
 
   allvalues_t *allvalues = malloc(sizeof(allvalues_t));
 
   *allvalues = (allvalues_t){0,0,0,0,0,0,0,0,0,0,0,0, nil};
   //memset(allvalues, 0, sizeof(allvalues_t));
 
   // pack all values in the struct
   allvalues->firstLineHeadIndent = _firstLineHeadIndent;
   allvalues->defaultTabInterval = _defaultTabInterval;
   allvalues->paragraphSpacingBefore = _paragraphSpacingBefore;
   allvalues->paragraphSpacing = _paragraphSpacing;
   allvalues->headIndent = _headIndent;
   allvalues->tailIndent = _tailIndent;
   allvalues->listIndent = _listIndent;
   allvalues->lineHeightMultiple = _lineHeightMultiple;
   allvalues->minimumLineHeight = _minimumLineHeight;
   allvalues->maximumLineHeight = _maximumLineHeight;
   allvalues->baseWritingDirection = _baseWritingDirection;
   allvalues->alignment = _alignment;
   allvalues->tabsBlocksListsHash = [tabsBlocksListsDescription hash];
 
   return [NSData dataWithBytesNoCopy:allvalues length:sizeof(allvalues_t) freeWhenDone:YES];
}

Note the commented out memset. My experiments have shown it to be slightly slower than setting all values via the curly-bracketed list.

The result in Instruments is another saved 20 ms which causes _cacheKey to no longer appear in the list of hot spots in the right-hand panel.

_cacheKey no longer there

It is a bit further down with 84ms. In fact you can see NSCache’s objectForKey take up even more time than our highly optimized _cacheKey method function. I would call this a good optimization success.

Conclusion

If you need a fast hashing function and you have less than 80 bytes you can wrap a temporary struct into an NSData and use the built-in hashing function which is slightly faster than even the highly optimized MD5 function from CommonCrypto.

For more than 80 bytes of key data you cannot do anything wrong with and MD5. No need to convert that into a string since NSData implements NSCopying and is fine for using as a cache key as well. Constructing long NSStrings from your key data should be the least favored option since the performance gap is as wide as a factor of 5.

Another thing though that I learned from my experiments today is that this kind of optimization needs a good set of unit tests to immediately know if your wonderful optimization doesn’t actually break something. I only noticed the need to zero the struct values from suddenly my Right-to-Left writing direction tests failing.

]]>
https://www.cocoanetics.com/2013/02/fast-hashing/feed/ 5 7663