Thursday, December 19, 2019

DreamLisp - A Lisp dialect in Objective-C

DreamLisp is a Lisp dialect with modules, lazy collections and is a Lisp-2 with an unhygienic macro system. DreamLisp interpreter is implemented in Objective-C and as such can run on macOS and iOS. It is tail recursive, uses ARC instead of garbage collection, has asynchronous communication using notifications taking advantage of the underlying Objective-C runtime and Foundation library. In the experimental version objc-rt-1 branch, we can define classes and create objects at runtime from the REPL. At present, the language runs in interpreted mode.

The language takes inspiration from MAL which itself is heavily influenced by Clojure, LFE and Erlang. DreamLisp has unhygienic macros, is single-threaded for the most part has a bunch of built-in modules core, network, test. Core module contains the native functions implemented in Objective-C and exposed to DreamLisp. The network module has functions to make simple REST based HTTP requests. The test module contains macros and functions to write unit tests which resembles Clojure tests.

The main goal of the project was to have a working Lisp that can use all the Objective-C runtime capabilities and thereby develop software that runs on Apple devices using Lisp. In that front, we need a Lisp language, REPL and there is nothing better than having the language itself be written in Objective-C directly. This helps to use Objective-C internal functions without having to resort to FFIs. The language part is ready, i.e, the specification and the implementation. Even though it's Lisp, it's not Common Lisp or any other Lisp. It's a dialect of its own. Next is the Objective-C interop, which is partial. These built-in functions follow ideas from CLOS. Currently, we can dynamically create classes, add methods, create objects of the classes, invoke methods for a certain set use cases. We can also create objects of the built-in classes and invoke methods on it. It's not robust yet and interop between native Objective-C types and DLisp types are not fully functional, also the Lisp case to camel, Pascal casing and vice-versa. Once we have these, then we can use the same code to run through a transpiler written in DLisp, generate Objective-C or Swift code, then use xcodebuild to generate native code with no eval and the code will be just like handwritten code.

Currently, the language is fully interpreted and as such works for simple use cases, but with full-fledged app development, having a language that runs natively either using LLVM or at least JIT would provide a better development experience. In that regards, I find malc interesting, which is MAL to LLVM or targetting MMIX bytecode. MMIX is a RISC architecture by Donald Knuth and is really cool.

Sunday, December 15, 2019

dnet - An LFE OTP REST API Server

I have created a seed project which uses LFE OTP with Cowboy for as the webserver and Mnesia as the database. There is also a gen_server which does HTTP request using Cowboy Gun library and handles the response as a stream asynchronously. This allows finer control over the HTTP response and allows to pause, cancel the stream, as well as continue from partially left of streams. The project demonstrates the use of LFE with OTP for writing web services with rebar3 for build and dependency management. Also, there are scripts for loading project env into LFE REPL, giving a full modern development workflow experience.

The supervision tree for the app is given below.
               ┌──────┐
┌───▶│db-sup│
│ └──────┘

┌────┐ │ ┌────────────┐
│dnet│────┼───▶│dnet-svc-sup│
└────┘ │ └────────────┘

│ ┌────────┐
└───▶│http-sup│
└────────┘
The server listens on port 8080 and has two handlers, one for GET and another for POST. The GET handler returns JSON and the POST handler returns HTML generated using Exemplar.

LFE comes with an emacs mode. So I have Spacemacs configuration setup with LFE mode with Lisp and general editor enhancements like rainbow-delimiters, OS X keybindings and such. The LFE mode files are currently configured to load from ~/env/emacs/lfe directory.

Saturday, December 14, 2019

NSJSONSerialization of BOOL values

To serialize BOOL values using NSJSONSerialization, we can use the NSNumber form. i.e, @YES and @NO which represents the BOOL YES and NO respectively.

Friday, November 1, 2019

Workaround for Performance Issue in Firestore get API

In the Firestore SDK for iOS, calling any get APIs like getDocument, getDocuments takes a long time. An empirical estimate is for a collection containing around 10,000 documents, it's taking around 20 seconds or more to retrieve the latest 50 documents. The workaround is to use addSnapshotListener once and remove after we obtain the data. This makes the retrieval fast enough.

Friday, September 27, 2019

Using Expressions in LLDB

Often times we would want to hold some temporary variables when debugging with lldb. We can make use of expression in such cases. An expression, short form e or expr evaluates the given statement. If we use an assignment with a variable starting with $, that variable will be available through out and can be used in other expressions.
(lldb) expr NSRange $range1 = NSMakeRange(0, 3);
(lldb) expr [@"Hi Olive" substringWithRange:(NSRange)$range1];
(NSTaggedPointerString *) $9 = 0x7a858d27bb2607ed @"Hi "
Since we are in single-line expression mode, there are slight differences in the way the expressions work. We need to cast NSRange. A better option in this case is to use the multi-line expression mode. For that, first enter expr↩ followed by a carriage return. The lldb is now in multi-line expression mode where we can enter normal Objective-C (or Swift) code with each line terminated with a semi-colon and an empty line after the final statement will evalute all the expressions and exit the mode.
(lldb) expr
Enter expressions, then terminate with an empty line to evaluate:
1 NSRange range = NSMakeRange(9, 4);
2 NSString *str = @"Slice of Life";
3 [str substringWithRange:range];
4
(NSTaggedPointerString *) $4 = 0x7a858d42fd26039d @"Life"
(lldb)
Multi-line expression with lldb makes debugging much better.

Paginate a UITableView with a Bottom Loader

 We can paginate a UITableView by implementing the tableView:willDisplayCell:forRowAtIndexPath method. In it, we can check if the displayed row index is nearing the last row in the model and if so, we fetch new data and append it to the model and reload the table view.

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == self.dataList.count - self.dataOffset && self.shouldFetchMore && !self.isDataLoading {
UI.showLoadingForTableView(self.tableView) // Display loader at the bottom
self.api.fetchSomeData(offset: self.dataList.count, max: Const.dataFetchMaxLimit, success: { xs in
DispatchQueue.main.async {
UI.hideLoadingForTableView(self.tableView) // Hide loader
self.isDataLoading = false
self.dataList.append(contentsOf: xs)
self.reloadTableView()
if xs.count == 0 { self.shouldFetchMore = false } // If data returned is 0 => there are no more data to fetch
}
self.lastFetchedIndex += Constants.dataFetchMaxLimit // Increment the fetch index to include current offset
}, error: { _ in
self.shouldFetchMore = true
self.isDataLoading = false
DispatchQueue.main.async {
UI.hideLoadingForTableView(self.tableView)
self.reloadTableView()
}
})
}
}
Here, dataList is the table's data model, dataOffset is a constant, say 5 which means, if there are only five more cells to be displayed, paginate. The shouldFetchMore variable indicates if there are more data available to paginate and isDataLoading indicates if a fetch is already in progress.

We can show a loader activity view at the bottom of the table view to indicate that more data is being fetched.
class UI {
/// A loading indicator snipper
private static var spinner: (UITableView) -> UIActivityIndicatorView = { tableView in
let s = UIActivityIndicatorView(style: .gray)
s.startAnimating()
s.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
return s
}

/// Show loading spinner at the bottom of the given table view
static func showLoadingForTableView(_ tableView: UITableView) {
tableView.tableFooterView = self.spinner(tableView)
tableView.tableFooterView?.isHidden = false
}

/// Hide loading spinner for the given table view if present
static func hideLoadingForTableView(_ tableView: UITableView) {
tableView.tableFooterView?.subviews.forEach({ view in
if view == self.spinner(tableView) {
view.removeFromSuperview()
}
})
tableView.tableFooterView?.isHidden = true
}
}


Tuesday, September 24, 2019

Manage Multiple Configs in Xcode with a Single Target

When developing apps that connect to a server, mostly we have a staging server and a production server. As such we need two app versions, one the dev version that connects to the staging server and the other that connects to the prod. These apps could have different configs to load, especially if it's based on Firebase. Firebase provides a GoogleService-Info.plist file for each project. Below demonstrates how we will use this as a use-case to further demonstrate working with multiple app configurations, schemas and Xcode config file, but with a single target.


1. First, download the Firebase config for both dev and prod to Config folder under the project root and name them as GoogleService-Dev-Info.plist and GoogleService-Prod-Info.plist respectively.


2. Under the Xcode configuration for the project, click on the Project > Info and under Configurations, we need to add one each for Dev Debug and Prod Release. For that, from Editor choose Add Configuration > Duplicate "Debug" Configuration and name it Dev Debug and follow the same for prod, but use Duplicate "Release" Configuration option.


3. Now under the Build Settings tab, we will add an ENV flag. For that click the plus icon next to Levels in the header menu and choose Add User-Defined Settings with the following values.
Dev Debug  DEV_DB
Dev Release DEV_RELEASE
Prod Debug PROD_DBG
Prod Release PROD_RELEASE

4. Next click on the app name under the Target section and choose Build Phases and add a new Run Script Phase with the following content.
if [ "$ENV" == "DEV_DBG" ]; then
echo "Dev debug config found"
cp -r "${PROJECT_DIR}/Config/GoogleService-Dev-Info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
echo "Firebase staging plist copied."
elif [ "$ENV" == "PROD_RELEASE" ]; then
echo "Prod release config found."
cp -r "${PROJECT_DIR}/Config/GoogleService-Prod-Info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
echo "Firebase production plist copied."
fi

This will copy the right plist and place it under the .app folder with plist renamed to GoogleService-Info.plist. This makes the call to [FIRApp configure]; to work without having to manually load the plist and specify the credential info.

5. Next, we will create multiple schemes, one for building the staging version and the other for prod. For that, click on the target next to the run-stop, button and choose New Scheme. For the target, choose the target app and let's name the schemes as Dev Debug FBApp and Prod Release FBApp.


6. We need to set the right configuration for each scheme. For that click Edit scheme, and for the debug scheme, under the Run section, for Build Configuration, choose Dev Debug and for the production scheme, choose, Prod Release. Under Manage schemes, make sure these schemes have Shared enabled.


With this, we have Firebase setup working with multiple environments. However, if we use other Firebase options like FirebaseAuth, we need to add additional settings like URL Type so that after the user sign-in, the invoking the app works. For this, we need to create a new xcconfig as described next.

From File > New File > File, choose Configuration Settings File and name it as DevConfig.xcconfig and another with ProdConfig.xcconfig. Do not add the xcconfig file to any target as it's a build config file and we don't want it to be shipped with the app bundle. The content of the dev config follows.
APP_NAME = FBAppDev
APP_BUNDLE_ID = org.example.app.FBAppDev
APP_FIREBASE_URL_SCHEME = com.googleusercontent.apps.25135051042-2utrt0j007lp7vf23p73fd306trszqn2
#include "Pods/Target Support Files/Pods-FBApp/Pods-FBApp.dev debug.xcconfig"
Note that in case of URLs in the xcconfig, we need to escape it like APP_SERVER_URL_LOCAL = http:/$()/localhost:4000/.


Here we define a variable APP_FIREBASE_URL_SCHEME with value taken from the reverse client ID of the dev plist. We will give a different bundle ID so that we can have both staging and prod versions of the app installed, without one overwriting the other.
7. We need to specify the xcconfig file for each project configuration we created before (in step 2). Under the Project > Configurations, for Dev Debug, expand and choose the DevConfig for the Based on Configuration File value. Same for the prod configuration name.


8. If the project uses Cocoapods, first close the workspace, delete the xcworkspace file, the Pods directory and the Podfile.lock and run pod install which will install the dependencies as usual and generate the Pod xcconfig files for each configuration we have, but does not set it as we have already set a config. So we include the corresponding Pod config in the xcconfig using the #include.

9. For the final step, we need to parameterise the variables in the Info.plist file as follows.
Bundle identifier  $(APP_BUNDLE_ID)
Bundle name $(APP_NAME)
URL Types > URL Schemes $(APP_FIREBASE_URL_SCHEME)
Voila, we now have a project setup with different configurations with a single app target.

Saturday, September 21, 2019

AppleScript to Toggle Dark Mode

We can use AppleScript to toggle between dark and light mode under macOS.
-- Toggle dark mode
tell application "System Events"
tell appearance preferences
get properties
if dark mode is false then
set dark mode to true
else
set dark mode to false
end if
end tell
end tell
Save this in a file say, toggle-dark-mode.applescript. Now we can use osascript to run this.
λ osascript ~/bin/toggle-dark-mode.applescript

Enable JavaScript From Apple Events for Safari

In macOS High Sierra, after enabling the Developer menu in Safari, clicking on "Allow JavaScript from Apple Events" does not work. We can use the below script to enable the option, once the Develop menu is enabled.
-- Enabled Apple Events in Safari if not enabled using Menu
set isEventsEnabled to false
to enableAppleEventsUsingMenu()
tell application "Safari"
tell application "System Events"
tell process "Safari"
tell menu "Develop" of menu bar item "Develop" of menu bar 1
click it
get properties of menu item "Allow JavaScript from Apple Events"
tell menu item "Allow JavaScript from Apple Events"
if value of attribute "AXMenuItemMarkChar" is not "✓" then
set isEventsEnabled to false
click
else
set isEventsEnabled to true
keystroke return
end if
delay 1
end tell
end tell
if isEventsEnabled is false then
tell front window
click button "Allow"
end tell
end if
end tell
end tell
end tell
end enableAppleEventsUsingMenu

-- Invoke the method
my enableAppleEventsUsingMenu()
Run this in the Script Editor, after launching Safari, which will then ask for confirmation. Once done, we will have a tick on that menu. This script can also be used in other automation workflows where interaction with AppleScript and JavaScript within Safari needs to be enabled beforehand.

URLSession Custom Server Trust Evaluation

In certain situations where the server certificate is not configured properly, or when using self signed SSL certificates, requests validation fails with URLSession. URLSession provides delegate methods which can be used in situations like this and can be used as a workaround until the certificate gets fixed on the server end. We can also use manual certification validation if we want to pin to a particular certificate. To do this, we need to assign a delegate for the URLSession object and handle the validation there as shown in the snippet below.
class NetworkService: NSObject {
private lazy var opsQueue: OperationQueue = {
let q = OperationQueue.init()
q.name = "api queue"
q.qualityOfService = QualityOfService.background
return q
}()
private lazy var session: URLSession = {
return URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: opsQueue)
}()
}

extension NetworkService: URLSessionDelegate {
/// Customise server trust evaluation in case where server certificate validation fails.
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
let protectionSpace: URLProtectionSpace = challenge.protectionSpace
if protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
if let trust: SecTrust = protectionSpace.serverTrust {
let cred = URLCredential(trust: trust)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, cred) // Say, custom SSL, so allow
} else {
completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
}
} else {
completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
}
}
}
Here the URLCredential object containing the SecTrust is passed to the completionHandler which makes the request go through. We can also do additional validation on the URLProtectionSpace and so on.

TeaPotServer

I wrote a TeaPotServer which is an HTTP2 server that uses Swift NIO under the hood. Swift NIO does the request, response handling with it event loop mechanism and the request processing is offloaded to GCD queue, making the event loop run as quick as possible without having to wait for a request to be processed. It's modelled as a REST API server with JSON as the main data exchange format. The server also has a logging component which conforms to SwiftLog API. The logging service uses the multiplexed logger to write to both file and console IO and does log rotation without missing any logs in the meantime. The main goal of the project is to demonstrate the development of a production server just by using Swift NIO and related libraries.

Wednesday, September 18, 2019

Using a Macro Based Function Within an XCTest Assert Macro

Using NSString initWithFormat: option within an XCTAssertEqualObjects directly will given an error.
XCTAssertEqualObjects([[NSString alloc] initWithFormat:@"Hello %@", @"olive"], @"Hello olive");
This will show error in Xcode like:
@try statement without a @catch and @finally clause
Expected ']'
Expected identifier or '('
Expected identifier or '('
Extraneous closing brace ('}')
Unexpected '@' in program
This is because the XCTAssertEqualObjects is a macro and it accepts va_args and initWithFormat: also accepts va_args. So there is a conflict and we need to wrap it within a parentheses.
XCTAssertEqualObjects(([[NSString alloc] initWithFormat:@"Hello %@", @"olive"]), @"Hello olive");

Wednesday, September 11, 2019

Fix Rename Failed in Xcode 10

Running a refactor in Xcode can sometimes show "Rename failed" message. This can be fixed by quitting Xcode, cleaning derived data folder and doing a rebuild followed by the refactoring. Below is a script to clean the derived data folder.



#!/bin/bash
# Clean Xcode cache

rm -frd ~/Library/Developer/Xcode/DerivedData/*
rm -frd ~/Library/Caches/com.apple.dt.Xcode/*
After cleaning the caches, launch Xcode, build the project. Then wait for any indexing tasks to complete. Now try Refactor > Rename and this should work.

Sunday, September 1, 2019

Implement NSFastEnumeration

Implementing NSFastEnumeration for a custom class requires us to implement one method which will be called when we use for (var in coll) // { .. } form. Let's say we have a class DLList which is backed by an array as its main data source. For iterating elements in the DLList object, we can do as follows.
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id  _Nullable __unsafe_unretained [])buffer count:(NSUInteger)len {
if (state->state == [self count]) return 0;
__unsafe_unretained id const *arr = &_array; // (1)
state->itemsPtr = (__typeof__(state->itemsPtr))arr; // (2)
state->mutationsPtr = &state->extra[0]; // (3)
state->state = [self count];
return state->state;
}
Here state->itemsPtr requires a pointer to any object of type id. We use _array and pointer to the _array can be obtained by getting the address, &_array. But holding a reference to the variable will change its retain count, which we do not want. So in statement marked (1) we use __unsafe_unretained id const * as the type. We don't check for mutation here (3) as the collection is not being mutated during enumeration.

Section 4.3.3 of the Automatic Reference counting documentation discusses the semantics of casts under ARC:
A program is ill-formed if an expression of type T* is converted, explicitly or implicitly, to the type U*, where T and U have different ownership qualification, unless:
* T is qualified with __strong, __autoreleasing, or __unsafe_unretained, and U is qualified with both const and __unsafe_unretained; or
...

In statement marked (2), we then typecast it to the type of state->itemsPtr which is same as __unsafe_unretained id * removing the const, which works because the ownership is the same.

The DLList class snippet is given below.
@implementation DLList {
NSMutableArray *_array;
}

- (NSUInteger)count {
return [_array count];
}

// ..
Now we can use fast enumeration like
DLList *list = [[DLList alloc] initWithArray:@[@(1), @(2), @(3)]];
NSNumber *num = nil;
for (num in list) { // fast enumeration which will call the protocol method
NSLog(@"Elems: %@", num);
}

Friday, August 30, 2019

Add a Separator in macOS Dock

We can use a space separator to ground related apps together in macOS dock.


To add a separator to the dock, run the following command in the Terminal.
λ defaults write com.apple.dock persistent-apps -array-add '{"tile-type"="spacer-tile";}'; killall Dock
This will add a spacer tile to the dock which can then be positioned within the dock like any normal app icon.

Remove Code Coverage and Warnings for Cocoapods

When build and running tests with code coverage enabled for iOS projects with CocoaPods, the code in the included pods will also be taken into account by default. In order to disable code coverage and warnings from included pods, add the below block to the Podfile.
# Disable code coverage for pod files
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['CLANG_ENABLE_CODE_COVERAGE'] = 'NO'
config.build_settings['SWIFT_EXEC'] = '$(SRCROOT)/../SWIFT_EXEC-no-coverage'
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = ''
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO'
end
end
installer.pods_project.build_configurations.each do |config|
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = ''
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO'
end
end
Create a file SWIFT_EXEC-no-coverage in the root folder at the same level as Podfile with the below snippet and make the file executable by running chmod +x SWIFT_EXEC-no-coverage.
#! /usr/bin/perl -w
use strict;
use Getopt::Long qw(:config pass_through);

my $profile_coverage_mapping;
GetOptions("profile-coverage-mapping" => \$profile_coverage_mapping);

exec("/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc", @ARGV);

Thursday, August 29, 2019

Hello, Dreamboat. Shall we chat?

The story goes like, I got a gig to work on a bot that should work with Artstation.com website. The requirement was that there must be some automated way to send messages to users of ArtStation. So I used AppleScript to interface with Safari and JavaScript to bridge between AppleScript and the web page. The bot or rather the script will get a list of user's profile link from a local file and then load them in Safari. For the initial launch, if the sender is not signed-in, the bot will sign in with the sender credentials loaded from a creds file. Then the bot will invoke a couple of JavaScript bridging methods to simulate a click on the buttons, add text in the message field, trigger change event so that AngularJS recomputes the constraints and enables the submit button. Then it invokes submit button tap which will send the message to the user, and finally closes the tab and if all tasks were done, closes the browser.

Everything is cool, right? Not quite so, because now the requirement changes and we need to have some intelligent behaviour for the bot, plus there won't be any list provided. And the bot has to figure out which message should be sent to which category of user and has to get all the users that the website has. So basically the bot is a crawler plus an intelligent messenger. Okay, so let's add some intelligent behaviour and spawn a couple of crawlers. When it comes to AI, true randomness marks the height of intelligence ;) So with that in mind, I went with a full-fledged re-architecture.

The app is a Cocoa app which now uses a web view to do the messaging part. This gives more fine-grained control over the web page loading events and such. The JavaScript communicates to the native code using WebKit message handler. There isn't much for the UI for the app as the main focus was on the functionality. I added some screens to view the details of the crawler, messenger and to configure messages and sender credential, which gets persisted to the database.

Unfortunately, there is no developer API the website provides. Life would have been much easier otherwise. I did some debugging of the JavaScript the site loads and figured how their API service works. Just for the kicks, I wrote the crawler and frontier services in Objective-C. The crawler first gets the Anti-CSRF token so that the request can get through the CloudFront security validation. Without the CSRF token, we will get a captcha which is hard to solve for my bot ;) Then the bot calls the user v2 API which uses the same params the website uses to get the list of users which returns data in JSON format. The JSON data reflects their model layer, presumably that of a NoSQL DB.

Now that we have the users list by category, we need to persist the data. And there are probably close to a million users, so I need a DB that scales well. So I went with FoundationDB with the Document Layer. As a matter of fact, FoundationDB powers iCloud, and the DB is distributed, fault-tolerant, scalable architecture is very promising. All of the DB setups went really well. Now I needed a MongoDB driver to talk the document layer. So I used the official MongoSwift library, but now Swift Package Manager refuses to work because it sensed the presence of Objective-C code. After wrestling with SPM and Xcode and MongoSwift, I just wrote the FoundationDBSevice, which is the persistence layer in Objective-C again so that I can call directly into the Mongo C driver to work with the DB. Less pain. If I had used only Swift, working the SPM would have been a breeze and could have used MongoSwift readily. Nevertheless, the bot now crawls the website saving users based on the category to the local FDB. To not DDoS their website, I used the GKGaussianDistribution that comes with GameplayKit to generate a random number within a specified set of mean and standard deviation values, and uses this value along with current time to schedule the crawl. The same logic is used for the messenger as well, but with a different set of mean and SD values. The bot saves the state of each crawl so that the next time it starts, it can crawl (fetch) users from where it left off. Users are messaged only once for a category. If a user belongs to multiple categories she will get different set of messages relevant to that category. The sender details can be added from the settings which are persisted in the DB except for the password, which is stored in the macOS keychain.

We can set a message template for each category and the bot will interpolate the string before sending the message to each user. Now the bot says Hi to ArtStation users.


Check out the source code at Github and let me know what you think.

Monday, July 29, 2019

Linting Swift Code with SwiftLint

When working within a team, it is generally a good practice to follow a common coding convention and style guide. SwiftLint is a nice tool that helps to enforce style rules and best practices on code structure which makes all code in a project to have a familiar style. This helps to easily grasp the app logic rather than trying to work out the code differences. It's really easy to get started with adding SwiftLint to an existing project. Install the library using brew.
λ brew install swiftlint
Add the following script in the run phase under Xcode Build Phases section
if which swiftlint >/dev/null; then
swiftlint
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
This will enable the default SwiftLint configuration to be used to lint the code base. We can have custom rules defined under .swiftlint.yml file placed inside the project root. A sample config file is given below.
excluded:
- Carthage
- Pods
- SwiftLint/Common/3rdPartyLib

disabled_rules:
- trailing_whitespace
- identifier_name
- type_body_length
- large_tuple

opt_in_rules:
- unneeded_parentheses_in_closure_argument

force_cast: warning

line_length:
warning: 160

file_length:
warning: 1000

function_body_length:
warning: 100
error: 200

allowed_symbols: "_"

reporter: "xcode"
The project is very active and contributions are welcomed. I did fix a bug and raised a pull request which got merged in release v0.32.0.

Saturday, July 27, 2019

Display Xcode Build Time

To show Xcode build time, from the Terminal, run the below command.
λ defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES
After which, running Xcode builds will display the time in the info bar. To get the full build time, clean the project and build again.


To get the dynamic linked framework startup time, add DYLD_PRINT_STATISTICS environment variable with value as 1. Environment variables can be added from the Run section by editing the target's schema from Xcode.



Initialise iOS App Programmatically Along With Unit Testing

The default Xcode iOS template create a main storyboard for the app. Here we will look into initialising an app programmatically in Swift without using storyboards. For that first we need to create a main.swift file with code as shown in the snippet. We will also add an option for running unit tests. Since unit tests do not require UI, we will bootstrap the app without the UI code for faster test runs using an AppDelegateMock class.
// main.swift
import Foundation
import UIKit

let isRunningTests = NSClassFromString("XCTestCase") != nil
let appDelegateClass = isRunningTests ? NSStringFromClass(AppDelegateMock.self) : NSStringFromClass(AppDelegate.self)
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, appDelegateClass)
Here we instantiate the right app delegate class and pass it to the UIApplicationMain as an argument. Remove the storyboard given in Main Interface option under app target's General section. The AppDelegate.swift snippet is as follows.
import UIKit

class AppDelegate: UIResponder, UIApplicationDelegate {
private var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
initUI()
}

func initUI() {
window = UI.mainWindow()
UI.initMainWindow(window!, navVCRoot: MainViewController.self)
}
}
class MainViewController: UIViewController {
}
class UI {
/// Returns a main window for the screen.
static func mainWindow() -> UIWindow {
return UIWindow(frame: UIScreen.main.bounds)
}

/// Initialise main window with a navigation view controller.
static func initMainWindow(_ window: UIWindow, navVCRoot: UIViewController.Type) {
let vc = navVCRoot.init()
window.rootViewController = UI.navigationController(with: vc)
window.makeKeyAndVisible()
}

/// Initialise a navigation controller with a root view controller.
static func navigationController(with rootVC: UIViewController) -> UINavigationController {
return UINavigationController(rootViewController: rootVC)
}
}
In AppDelegate class we remove the @UIApplicationMain annotation and in the didFinishLaunchingWithOptions delegate method, we initialises the UI. Here, the app uses NavigationViewController as the root view controller. So we will create a UIWindow and then sets the rootViewController to a navigation view controller. We then create a navigation view controller with a view controller MainViewController as its root. UI related code can be organized into a UI class.

For the mock app delegate class, create a AppDelegateMock.swift under the Tests folder with target membership to both the main app target and the unit test target.
import Foundation
@testable import AppTargetName

class AppDelegateMock: NSObject {
private let log = Logger()
override init() {
super.init()
}
}
With this, the app now launches without a storyboard and the unit tests runs quicker as we do not initialise any UI view controllers, all done programatically.

A Programmatic UI Pattern

Creating UI programmatically gives better control over various UI components and very much helps in writing modular, reusable UI components. Also this makes it easier when working as a team were multiple developers can work on same UI components, resolve code conflicts quickly as opposed to fixing storyboard XML file conflicts, which are harder to understand. There are some repeating patterns one will encounter when working with view and view controllers. We will make this modular as shown in the sample code.
class ViewController: UIViewController {
private lazy var submitBtn: UIButton = {
let b = UIButton(type: .system)
b.translatesAutoresizingMaskIntoConstraints = false
b.setTitle("Submit", for: .normal)
return b
}()

deinit {
NotificationCenter.default.removeObserver(self)
}

override func loadView() {
super.loadView()
initUI()
initConstraints()
}

override func viewDidLayoutSubviews() {
initStyle()
}

override func viewDidLoad() {
super.viewDidLoad()
initDelegates()
initEvents()
initNotifications()
initData()
}

func initUI() {
self.view = UI.view() // set the view for the view controller
self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.backgroundColor = UIColor.white // set the default view color to white
// add sub views...
}
}
class UI {
static func view() -> UIView {
let screenSize = UIScreen.main.bounds
let v = UIView(frame: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
v.backgroundColor = UIColor.white
return v
}
}
Here in loadView() method, we initialise the UI using the initUI() method, where we write any UI related code to display the view controller. We can use a static class UI.swift for common UI related methods, which can be used from any view controller. We can then build the UI by adding subviews, with independent components as lazy variables, which can be accessed from other parts of the code for getting values, adding events to it and such. We can further customise this by moving all UI related code to a separate view class if the view controller has a lot of functionality.

Once the UI is set up, next we need to add constraints. Constraints for each UI component will be added separately using the NSLayoutConstraint.activate([]) method. Make sure that the UI components have translatesAutoresizingMaskIntoConstraints set to false.
NSLayoutConstraint.activate([
submitBtn.topAnchor.constraint(equalTo: self.formView.bottomAnchor, constant: 8),
submitBtn.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16),
submitBtn.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -16),
submitBtn.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -8)
])
Here, the difference between programmatic constraint and storyboard is that the bottom and trailing units need to be in negative where as the same constraint when added in Interface Builder, we use positive values. In the viewDidLayoutSubviews() method, we call initStyles() to add any style to UI elements, at which point we will have the UI initialised properly with constraints and adding properties like border, color etc will work because the UI component's frame is not of CGRect.zero. Once the constraints are setup, we will set the delegates of any UI components and objects. Then we add target-action for UI elements in initEvents() method. If the view controller needs to listen to certain notifications, we add that in the initNotifications() method. Next we call initData() where we populate any model variables which is required for displaying, like in case of table view as well as making calls to service layer to fetch or update data.