Dan Rosenstark, author of MIDI Designer, on Tech & Music

Dan Rosenstark

Dan Rosenstark, Author & CEO of MIDI Designer, muses about all things tech. Particularly: Notes on software development in Swift, Objective-C, and many non-Apple languages. Also: lots of random technology notes on OS X and iOS.

Solution for No Optionals in Swift String Interpolation

This is probably a bad idea...

// Solution is reduced from here

/// Anything that can be unwrapped.
public protocol Unwrappable {
/// Returns the unwrapped value of the receiver.
func unwrap() -> Any?
}

extension Optional: Unwrappable {
/// Returns the unwrapped value of the `Optional`.
public func unwrap() -> Any? {
switch self {
case nil:
return nil
case let unwrappable as Unwrappable:
return unwrappable.unwrap()
case let any:
return any
}
}
}


public extension String {
/// Creates an instance containing the unwrappable's representation.
init(stringInterpolationSegment expr: Unwrappable) {
if let unwrapped = expr.unwrap() {
self.init(stringInterpolationSegment: unwrapped)
} else {
self.init("nil")!
}
}
init(stringInterpolationSegment expr: Optional<T>) {
self.init(stringInterpolationSegment: expr as Unwrappable)
}
}

/**
Force the default behavior
*/
postfix operator *

public postfix func *(optional: Optional<Any>) -> String {
if "\(optional)" == "nil" {
return "nil"
}
return "Optional(\"\(optional)\")"
}

My Time/Datestamp Applescript

Here's the .scpt file that will open in Script Editor on Mac. You can set it up as a keystroke (CMD-Shift-D for me) in FastScripts (free for up to 10 scripts).

-- yar2050 (Dan Rosenstark)'s favorite date format
-- 2017-08-03 update
on fillInZero(int)
if (int < 10) then set int to "0" & int
return int as string
end fillInZero

set theDate to (current date)
set theHour to fillInZero(hours of (theDate) as integer)
set theMinutes to fillInZero(minutes of (theDate) as integer)
tell theDate to get (its month as integer)
set theMonth to fillInZero(result)
set theDay to fillInZero(day of theDate)
tell (theDate) to get (its year as integer) & "-" & (theMonth) & "-" & theDay & " " & (theHour) & ":" & theMinutes
set date_ to (result as string)
tell application "System Events"
set frontmostApplication to name of the first process whose frontmost is true
end tell


tell application frontmostApplication
delay 0.2 -- I have no idea why this is necessary
activate
tell application "System Events"
if (frontmostApplication = "Evernote") then
keystroke "h" using {command down, shift down}
keystroke "b" using {command down}
keystroke date_
keystroke "b" using {command down}
else
keystroke date_
end if
end tell
end tell




Just Because It Compiles: More Notes from Swift 3 Upgrade

This is the method signature for the superclass:

public func sendRequest<T: Request>(request: T, handler: (Result<T.Response, Error>) -> Void) -> NSURLSessionDataTask? {

and this is in the subclass

override func sendRequest<T : RequestType>(request: T, handler: (Result<T.Response, PayLib.Error>) -> Void) -> NSURLSessionDataTask? {

and in Swift 3 this got translated to be the same, except for some minor changes:

open func sendRequest<T: Request>(_ request: T, handler: @escaping (Result<T.Response, Error>) -> Void) -> URLSessionDataTask? {

Subclass:

override func sendRequest<T : RequestType>(_ request: T, handler: @escaping (Result<T.Response, PayLib.Error>) -> Void) -> URLSessionDataTask? ```

Now that was good enough for Swift 2. In Swift 3, a call to this method resulted in a crash with no interesting information.

The reason was the mismatch between RequestType and Request (RequestType is the protocol which the Request Protocol extends). So the fix was merely to have the signatures match exactly.

override func sendRequest<T : Request>(_ request: T, handler: @escaping (Result<T.Response, PayLib.Error>) -> Void) -> URLSessionDataTask? {

I'm not sure that there's a point here, except that: Just because Xcode likes it, it compiles and it works in Swift 2 doesn't mean that it won't cause a runtime crash in Swift 3. Or something like that: check everything by hand/eye!

Show Only Apps in Search Results, iOS 11

I want my iPad (and iPhone) to show Apps first when I hit CMD-space or slide down for search. This is the setting to change.



And this is what search results look like without all the clutter.


Optional Block Parameters and Fix-Its

Consider this fix-it:



Now if this happens to you -- as it happened to me -- as part of a massive code conversion to Swift 3, you might be tempted to follow the fix it. Then your code would read like this:

var blockWithOptionalParam: ((NSString?)->())?

func assignBlockWithNonOptionalParameter() {
let tempBlock: (NSString)->() = {
print("count of string \($0.appending("appendMe"))")
}
blockWithOptionalParam = tempBlock as! ((NSString?) -> ())
}

This might be fine, but would result in a crash if you ever passed a nil to your blockWithOptionalParam. The only safe thing to do, really, is to ignore the fix it and handle the optional properly:

func assignBlockWithNonOptionalParameter() {
let tempBlock: (NSString?)->() = {
guard let text = $0 else { return }
print("count of string \(text.appending("appendMe"))")
}
blockWithOptionalParam = tempBlock
}


So What?
For some other cases -- and I'm not sure what the difference is, yet -- Xcode always suggests casting the block rather than fixing the optional in the block. I'm unable to show this example in the lab, but it does show up in the codebase I'm working on currently.



Enabling Command-Line Pipe for Atom on OSX

Pipe to Atom Command-Line Tools

This allows you to pipe from the command line to Atom. Simplest example:


ls -l | atom

Set It Up

Make sure you've run File -> Install Shell Commands first.



Edit your /usr/local/bin/atom (in Atom, for instance)

Below this code:


if [ $EXPECT_OUTPUT ]; then

  export ELECTRON_ENABLE_LOGGING=1

fi

insert this code


if [ ! -t 0 ]; then

   TEMPFILE="$(mktemp)"

   cat > "$TEMPFILE"

   set "$TEMPFILE" "$@"

fi

Save the file and now piping should work.

This comment is also on GitHub here


* osx os x macos

Breakpoint Swift Deinit in Xcode


Setting up the Breakpoint
It's actually very simple to breakpoint the deinit method of a Swift class. Given a class called Test47, the symbolic breakpoint merely looks like this:


And that's about it.


Notes
  1. If there is no body to your deinit method, the breakpoint will not fire (it gets optimized out or something similar).
  2. There is no difference in syntax, for the Xcode symbolic breakpoints, between class and instance methods.
  3. There are no deinit methods for structs, so your SOL on that one.

Swift 3.x Calls Objective-C

Maybe you or a colleague named a constant with PascalCase in an Objective-C method (class or like this:

+ (UIColor *)SKDarkBlue;

and in Swift 2, you could call it easily with

self.checkStateBand.backgroundColor = UIColor.SKDarkBlue;

But in Swift 3, the casing is automatically changed for you... isn't that nice!?

self.checkStateBand.backgroundColor = UIColor.skDarkBlue;

This example is great because you can see what happens to two initial caps.

What if it's just one?

- (void) FirstCapMethod;

looks like this in Swift 3:

ObjCObject().firstCapMethod()

Fun!


Does Your Constant End in Notification?

Objective-C Notification

extern NSString * const SKMagTekSwiperDisconnectedNotification;

Gets called like this from Swift

let someNotification = NSNotification.Name.SKMagTekSwiperDisconnected

Swift and Object-C Interop, Simplest Possible Example

Start with a Single View App (Swift) in Xcode 9 Beta 6 called SwiftObjCInterop.


Swift Calls Objective-C

Create an Objective-C Class

Create an Objective-C class called ObjCObject

This will automatically give you the Bridging Header to call the Objective-C from Swift (that was easy!):

Select Create Bridging Header which will produce the file SwiftObjCInterop-Bridging-Header

Add a Method to ObjCObject

Add a method like this in your .m file

- (void) test {
    NSLog(@"Yes we are ObjCObject and we are testing");
}

and this in your .h

- (void) test;

Add ObjCObject to your Bridging Header

#import "ObjCObject.h"

Test Swift Calls Objective-C

Put this in your viewDidLoad:

ObjCObject().test()

Run the project. Now Swift calls Objective-C.


Objective-C Calls Swift

This is slightly trickier in an existing project, but in a new project there are no issues.

Include this in your ViewController.swift file:

@objc(testSwift)
func testSwift() {
    print("yes ViewController is printing")
}

For some reason, you need the @objC tag to be able to see your method from Objective-C.

Include this import in your .m file:

#import "SwiftObjCInterop-Swift.h"

And add this to your test method in your Objective-C class:

ViewController *vc = [[ViewController alloc] init];
[vc testSwift];

That's it.