{"id":34078,"date":"2021-11-02T16:19:46","date_gmt":"2021-11-02T20:19:46","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=34078"},"modified":"2021-11-02T16:20:32","modified_gmt":"2021-11-02T20:20:32","slug":"dangerous-nslog-calls-in-swift","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2021\/11\/02\/dangerous-nslog-calls-in-swift\/","title":{"rendered":"Dangerous NSLog() Calls in Swift"},"content":{"rendered":"<p><a href=\"https:\/\/indiestack.com\/2021\/10\/dangerous-logging-in-swift\/\">Daniel Jalkut<\/a> (<a href=\"https:\/\/twitter.com\/danielpunkass\/status\/1454478036892540934\">tweet<\/a>):<\/p>\n<blockquote cite=\"https:\/\/indiestack.com\/2021\/10\/dangerous-logging-in-swift\/\">\n<pre>NSLog(\"Failed to get modification date at URL: \\(url) - \\(error)\")<\/pre>\n<p>[&#8230;]<\/p>\n<p>In the scenario of my crash, the interpolation of &ldquo;url&rdquo; and &ldquo;error&rdquo; results in <em>surprise template placeholders<\/em>, because of the presence of spaces in the path that the URL represents. In my tests, a file called &ldquo;Open Program Ideas File&rdquo; exists and is used to test some file manipulations. When the path to this file is encoded as a URL, the name of the file becomes &ldquo;Open%20Program%20Ideas%20File&rdquo;. Since the error&rsquo;s failure message reiterates the name of the file it couldn&rsquo;t find, we end up with another copy of the percent-escaped string in the parameter to NSLog. Each instance of &ldquo;%20&rdquo; followed by a letter is liable to be interpreted by NSLog as a <a href=\"https:\/\/developer.apple.com\/library\/archive\/documentation\/Cocoa\/Conceptual\/Strings\/Articles\/formatSpecifiers.html\">printf-style format specifier<\/a>. So as far as our filename&rsquo;s string is concerned, we have &ldquo;%20P&rdquo;, &ldquo;%20I&rdquo;, and &ldquo;%20F&rdquo; threatening to cause trouble.<\/p>\n<p>[&#8230;]<\/p>\n<p>If this were Objective-C, we wouldn&rsquo;t run into the problem because the parameters would need to be passed as variadic arguments[&#8230;] But if we try that in Swift, we run into trouble.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/twitter.com\/scelis\/status\/1454504683259801607\">Sebastian Celis<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/scelis\/status\/1454504683259801607\">\n<p>That Swift compiler error is very unhelpful, but you should still be able to use variadic arguments &mdash; you just need to do it a bit differently. I tend to pass them all in as strings. For example:<\/p>\n<pre>NSLog(\"Oops! %@ - %@\", \"\\(url)\", \"\\(error)\")<\/pre>\n<\/blockquote>\n\n<p><a href=\"https:\/\/twitter.com\/noheger\/status\/1454711798473506819\">Norbert Heger<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/noheger\/status\/1454711798473506819\">\n<p>As a safety measure you could add this to your project to let the compiler catch the dangerous cases[&#8230;]<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/twitter.com\/bjhomer\/status\/1454550833173987329\">BJ Homer<\/a>:<\/p>\n<blockquote cite=\"https:\/\/twitter.com\/bjhomer\/status\/1454550833173987329\">\n<p>I prefer this format, because I can still keep all the string interpolation &ldquo;in-line&rdquo;:<\/p>\n<pre>NSLog(\"%@\", \"Oops! \\(url) - \\(error)\")<\/pre>\n<\/blockquote>\n\n<p>It&rsquo;s cool&mdash;and sometimes very useful&mdash;to be able to directly interpolate Swift strings using the <code>\\()<\/code> syntax. However, for logging I prefer the Python-style approach that Swift&rsquo;s <code>print()<\/code> function uses:<\/p>\n<pre>print(\"Oops!\", url, \"-\", error)<\/pre>\n<p>I find it more readable, and you can easily add line breaks after commas to wrap long lines or to line up pairs of arguments that go together.<\/p>\n<p>Of course, <code>print()<\/code> doesn&rsquo;t output to the same places as <code>NSLog()<\/code>, but you can write a wrapper function. I think that&rsquo;s a good idea, anyway, as it lets you switch whether logging is enabled, log to a file if necessary, include the source location, etc. At various times, <code>NSLog()<\/code>&rsquo;s output hasn&rsquo;t show up at all for me or it has been duplicated. Having this more under my control has been helpful in working around such hiccups.<\/p>\n\n<p>Here&rsquo;s some code that I&rsquo;ve been using to make logging more ergonomic:<\/p>\n\n<pre>\nfunc log(_ i0: @autoclosure() -&gt; Any?,\n         _ i1: @autoclosure() -&gt; Any?,\n         _ i2: @autoclosure() -&gt; Any?,\n         file: String = #file,\n         line: UInt = #line,\n         function: String = #function) {\n    log([i0(), i1(), i2()], file: file, line: line, function: function)\n}\n<\/pre>\n\n<p>The parameters are type <code>Any?<\/code> to avoid the compiler error about implicitly converting an <code>Optional<\/code> to <code>Any<\/code>. Unfortunately, we need a separate version for each number of parameters because <code>@autoclosure<\/code> doesn&rsquo;t work with variadic parameters. These all funnel into:<\/p>\n\n<pre>func log(_ items: @autoclosure() -&gt; [Any?],\n         file: String = #file,\n         line: UInt = #line,\n         function: String = #function) {\n    log(String(mjtPrintingOptionals: items()), file: file, line: line, function: function)\n}\n<\/pre>\n\n<p>And to convert the array to a string:<\/p>\n\n<pre>public extension String {\n    init(mjtPrinting items: [Any], separator: String = \" \") {\n        \/\/ @SwiftIssue: No print() that takes an Array, and no way to splat, so\n        \/\/ we have to print individually.\n        \/\/ https:\/\/github.com\/apple\/swift\/blob\/main\/stdlib\/public\/core\/Print.swift\n        var result = \"\"\n        var prefix = \"\"\n        for item in items {\n            result.write(prefix)\n            print(item, terminator: \"\", to: &amp;result)\n            prefix = separator\n        }\n        self = result\n    }\n\n    init(mjtPrintingOptionals items: [Any?], separator: String = \" \") {\n        self = String(mjtPrinting: items.map { $0 ?? \"nil\" }, separator: separator)\n    }\n}\n<\/pre>\n\n<p>This last function unwraps the optionals before printing so that we don&rsquo;t have <code>Optional(...)<\/code> cluttering the log.<\/p>","protected":false},"excerpt":{"rendered":"<p>Daniel Jalkut (tweet): NSLog(\"Failed to get modification date at URL: \\(url) - \\(error)\") [&#8230;] In the scenario of my crash, the interpolation of &ldquo;url&rdquo; and &ldquo;error&rdquo; results in surprise template placeholders, because of the presence of spaces in the path that the URL represents. In my tests, a file called &ldquo;Open Program Ideas File&rdquo; exists [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2021-11-02T20:19:50Z","apple_news_api_id":"bb21cc4b-e6d0-412a-a2d5-1419ce2f608a","apple_news_api_modified_at":"2021-11-02T20:20:35Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAA==","apple_news_api_share_url":"https:\/\/apple.news\/AuyHMS-bQQSqi1RQZzi9gig","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[69,31,2078,30,2077,71,901],"class_list":["post-34078","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-cocoa","tag-ios","tag-ios-15","tag-mac","tag-macos-12","tag-programming","tag-swift-programming-language"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/34078","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=34078"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/34078\/revisions"}],"predecessor-version":[{"id":34080,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/34078\/revisions\/34080"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=34078"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=34078"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=34078"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}