Curry in Swift and Javascript

Issue #185

You may encounter curry in everyday code without knowing it. Here is a bit of my reflections on curry and how to apply it in Javascript and Swift.

Taking one parameter in Haskell

In Haskell, all function officially takes only 1 parameter. Function with many parameters are just curried, which means they will be partially applied. Calling sum 1 just returns a function with 1 parameter, then 2is passed into this function. The following 2 function calls are the same.

ghci> sum 1 2
3 
ghci> (max 1) 2
3

I tend to think of curried function or partially applied function as something that carry dependencies at each application step. Each curried function can be assigned to variable or pass around, or as returned value.

Curry in Swift for predicate

When I was trying to make my own Signal library, I have

and Event

filter

Then there should be a filter for Signal. The idea of filter is that we should update signal if the Event is Next with right filtered value

public func filter(f: T -> Bool) -> Signal<T>{
    let signal = Signal<T>()
    subscribe { result in
        switch(result) {
        case let .Success(value):
            if f(value) {
                signal.update(result)
            }
        case let .Error(error): signal.update(.Error(error))
        }
    }
    return signal
}

2 parameters

But having Event as another monad, I think it should be more encapsulated if that switching logic gets moved into the Event. Here the filter takes 2 params

Event.swift

func filter(f: T -> Bool, callback: (Event<T> -> Void)) {
        switch self {
        case let .Next(value) where f(value):
            callback(self)
        case .Failed:
            callback(self)
        default:
            break
    }
}

Signal.swift

public func filter(f: T -> Bool) -> Signal<T> {
    let signal = Signal<T>()

    subscribe { event in
        event.filter(f, callback: signal.update)
    }

    return signal
}

Currying

With currying, we can make filter a more abstract function, and defer the decision to pass the callback param. It is a little carried away but I find it helpful this way

Now filter accepts 1 param, and it returns a function that takes callback as its param

Event.swift

func filter(f: T -> Bool) -> ((Event<T> -> Void) -> Void) {
        return { g in
            switch self {
            case let .Next(value) where f(value):
                g(self)
            case .Failed:
                g(self)
            default:
                break
            }
        }
    }

Signal.swift

public func filter(f: T -> Bool) -> Signal<T> {
        let signal = Signal<T>()

        subscribe { event in
            event.filter(f)(signal.update)
        }

        return signal
    }

Curry syntax in Swift 2 and above

Swift 2 supports curry syntax function

func sum(a: Int)(b: Int) -> Int {
    return a + b
}

let sumWith5 = sum(5)
let result = sumWith5(b: 10)

Unfortunately, the syntactic sugar for declaring curry has been dropped since Swift 3. You may want to find out in Bidding farewell to currying. But it’s not a big deal as we can easily create curry function. It is just a function that returns another function.

Using curry for partial application in UIKit

I used this curry technique in my Xkcd app. See MainController.swift. MainController is vanilla UITabBarController with ComicsController and FavoriteController , all embedded in UINavigationViewController .

The feature is that when a comic is selected, a comic detail screen should be pushed on top of the navigation stack. For example in ComicsController

/// Called when a comic is selected  
var selectComic: ((Comic) -> Void)?

All ComicsController needs to know is to call that selectComic closure with the chosen Comic, and someone should know how to handle that selection. Back to the handleFlow function inside MainController.

private func handleFlow() {
  typealias Function = (UINavigationController) -> (Comic) -> Void
  let selectComic: Function = { [weak self] navigationController in
    return { (comic: Comic) in
      guard let self = self else {
        return
      }

  let detailController = self.makeDetail(comic: comic)
      navigationController.pushViewController(detailController, animated: true)
    }
  }

  comicsController.selectComic = selectComic(comicNavigationController)
  favoriteController.selectComic = selectComic(favoriteNavigationController)
}

I declared Function as typealias to explicitly state the curry function that we are going to build

typealias Function = (UINavigationController) -> (Comic) -> Void

We build selectComic as curried function, that takes UINavigationViewController and returns a function that takes Comic and returns Void . This way when we partially apply selectComic with the a navigationController , we get another function that has navigationController as dependency, and ready to be assigned to selectComic property in comicsController .

Curry promised function in Javascript

I like to work with Promise and async/await in Javascript. It allows chainable style and easy to reason about. So when working with callbacks in Javascript, for example callback from native modules in React Native, I tend to convert them into Promise.

For example when working with HealthKit, we need to expose a native modules around it

// [@flow](http://twitter.com/flow)

import { NativeModules } from 'react-native'

type HealthManagerType = {
  checkAuthorisation: ((string) => void)) => void,
  authorise: ((boolean) => void)) => void,
  readWorkout: (Date, Date, () => void)) => void,
  readDailySummary: (Date, Date, () => void)) => void,
  readMeasurement: (Date, Date, () => void)) => void
}

const HealthManager: HealthManagerType = NativeModules.HealthManager
export default HealthManager

We can build a toPromise function that can convert a function with callback into Promise

// [@flow](http://twitter.com/flow)

const toPromise = (f: (any) => void) => {
  return new Promise<any>((resolve, reject) => {
    try {
      f((result) => {
        resolve(result)
      })
    } catch (e) {
      reject(e)
    }
  })
}

export default toPromise

However, as you can see in the signature, it only works with a callback of type (any) => void In other words, this callback must have exactly 1 parameter, because a Promise can either returns a value or throws an error.

To remedy this, we can build a curry function that can turns function with either 1, 2, 3 parameters into curried function. Thanks to the dynamic nature of Javascript, we have

// [@flow](http://twitter.com/flow)

function curry0(f: () => void) {
  return f()
}

function curry1(f: (any) => void) {
  return (p1: any) => {
    return f(p1)
  }
}

function curry2(f: (any, any) => void) {
  return (p1: any) => {
    return (p2: any) => {
      return f(p1, p2)
    }
  }
}

function curry3(f: (any, any, any) => void) {
  return (p1: any) => {
    return (p2: any) => {
      return (p3: any) => {
        return f(p1, p2, p3)
      }
    }
  }
}

export default {
  curry0,
  curry1,
  curry2,
  curry3
}

So with a function that have 3 parameters, we can use curry3 to partially apply the first 2 parameters. Then we have a function that accepts just a callback, and this is turned into Promise via toPromise

const readWorkout = curry.curry3(HealthManager.readWorkout)(DateUtils.startDate))(DateUtils.endDate))

const workouts = await toPromise(readWorkout)

Where to go from here

Here are some of my favorite posts to read more about curry

How to fix SSLPeerUnverifiedException in Android

Issue #184

Get error javax.net.ssl.SSLPeerUnverifiedException: No peer certificate in Android API 16 to API 19

Getting started

Read about HTTPS and SSL https://developer.android.com/training/articles/security-ssl
Check backend TLS https://www.ssllabs.com/index.html
TLS by default in Android P https://android-developers.googleblog.com/2018/04/protecting-users-with-tls-by-default-in.html

TLS version

Read https://developer.android.com/reference/javax/net/ssl/SSLSocket.html

This class extends Sockets and provides secure socket using protocols such as the “Secure Sockets Layer” (SSL) or IETF “Transport Layer Security” (TLS) protocols.

ssl

TLS 1.1 and 1.2 are supported from API 16, but not enabled by default until API 20.

Install TLS 1.2 when needed

Read https://medium.com/tech-quizlet/working-with-tls-1-2-on-android-4-4-and-lower-f4f5205629a

The first thing we realized was that despite documentation suggesting otherwise, not all devices on Android 4.1+ actually support TLS 1.2. Even though it is likely due to device manufacturers not fully following the official Android specs, we had to do what we could to ensure this would work for our users.

Luckily, Google Play Services provides a way to do this. The solution is to use ProviderInstaller from Google Play Services to try to update the device to support the latest and greatest security protocols.

1
2
3
4
5
6
7
8
9
10
11
fun Context.installTls12() {
try {
ProviderInstaller.installIfNeeded(this)
} catch (e: GooglePlayServicesRepairableException) {
// Prompt the user to install/update/enable Google Play services.
GoogleApiAvailability.getInstance()
.showErrorNotification(this, e.connectionStatusCode)
} catch (e: GooglePlayServicesNotAvailableException) {
// Indicates a non-recoverable error: let the user know.
}
}

Does not seem to work, as the root problem was that TLS was not enabled

Try normal HttpsUrlConnection

If we use any networking library and suspect it is the cause, then try using normal HttpsUrlConnection to check.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class MyHttpRequestTask extends AsyncTask<String,Integer,String> {

@Override
protected String doInBackground(String... params) {
String my_url = params[0];
try {
URL url = new URL(my_url);
HttpsURLConnection httpURLConnection = (HttpsURLConnection) url.openConnection();
httpURLConnection.setSSLSocketFactory(new MyFactory());
// setting the Request Method Type
httpURLConnection.setRequestMethod("GET");
// adding the headers for request
httpURLConnection.setRequestProperty("Content-Type", "application/json");


String result = readStream(httpURLConnection.getInputStream());
Log.e("HttpsURLConnection", "data" + result.toString());


}catch (Exception e){
e.printStackTrace();
Log.e("HttpsURLConnection ", "error" + e.toString());
}

return null;
}

private static String readStream(InputStream is) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(is, Charset.forName("US-ASCII")));
StringBuilder total = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
total.append(line);
}
if (reader != null) {
reader.close();
}
return total.toString();
}
}

class MyFactory extends SSLSocketFactory {

private javax.net.ssl.SSLSocketFactory internalSSLSocketFactory;

public MyFactory() throws KeyManagementException, NoSuchAlgorithmException {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
internalSSLSocketFactory = context.getSocketFactory();
}

@Override
public String[] getDefaultCipherSuites() {
return internalSSLSocketFactory.getDefaultCipherSuites();
}

@Override
public String[] getSupportedCipherSuites() {
return internalSSLSocketFactory.getSupportedCipherSuites();
}

@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}

@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
}

@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
}

@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
}

@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
}

private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
}
return socket;
}
}

The key is setEnabledProtocols. Then use

1
2
String url = "https://www.myserver.com/data"
new MyHttpRequestTask().execute(url);

Use custom SSLSocketFactory in some networking libraries

If our custom MyFactory works for HttpsUrlConnection, then the problem lies in some 3rd party networking libraries.

Read https://blog.dev-area.net/2015/08/13/android-4-1-enable-tls-1-1-and-tls-1-2/

The Android documentation for SSLSocket says that TLS 1.1 and TLS 1.2 is supported within android starting API level 16+ (Android 4.1, Jelly Bean). But it is by default disabled but starting with API level 20+ (Android 4.4 for watch, Kitkat Watch and Android 5.0 for phone, Lollipop) they are enabled. But it is very hard to find any documentation about how to enable it for phones running 4.1 for example.

The first thing you need to do is to make sure that your minimum required API level is 16 to have the following code working in your project.

To enable TLS 1.1 and 1.2 you need to create a custom SSLSocketFactory that is going to proxy all calls to a default SSLSocketFactory implementation. In addition to that do we have to override all createSocket methods and callsetEnabledProtocols on the returned SSLSocket to enable TLS 1.1 and TLS 1.2. For an example implementation just follow the link below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import javax.net.ssl.SSLSocketFactory;

class MyFactory extends org.apache.http.conn.ssl.SSLSocketFactory {

public static KeyStore getKeyStore() {
KeyStore trustStore = null;
try {
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
} catch (Throwable t) {
t.printStackTrace();
}
return trustStore;
}


private SSLSocketFactory internalSSLSocketFactory;

public MyFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);
internalSSLSocketFactory = context.getSocketFactory();
}


@Override
public Socket createSocket() throws IOException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
}

@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return enableTLSOnSocket(internalSSLSocketFactory.createSocket(socket, host, port, autoClose));
}

private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
}
return socket;
}
}

Then maybe use it in a library, for example the ancient AsyncHttpClient

1
2
3
asyncHttpClient = new AsyncHttpClient();
asyncHttpClient.setTimeout(HTTP_GET_TIMEOUT);
asyncHttpClient.setSSLSocketFactory(new MyFactory(MyFactory.getKeyStore()));

Updated at 2020-08-11 16:27:22

How to zoom in double in MapKit

Issue #183

1
2
3
4
5
6
7
8
9
10
11
12
func zoomInDouble(coordinate: CLLocationCoordinate2D) {
let region = mapView.region
let zoomInRegion = MKCoordinateRegion(
center: coordinate,
span: MKCoordinateSpan(
latitudeDelta: region.span.latitudeDelta * 0.5,
longitudeDelta: region.span.longitudeDelta * 0.5
)
)

mapView.setRegion(zoomInRegion, animated: true)
}

How to select cluster annotation in MapKit

Issue #182

1
2
3
4
5
6
7
8
9
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let coordinate = view.annotation?.coordinate else {
return
}

if (view.annotation is MKClusterAnnotation) {
zoomInDouble(coordinate: coordinate)
}
}

How to cluster annotations in MapKit in iOS 11

Issue #181

https://developer.apple.com/documentation/mapkit/mkannotationview/decluttering_a_map_with_mapkit_annotation_clustering

1
2
3
4
5
6
7
8
9
10
11
final class AnnotationView: MKMarkerAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)

clusteringIdentifier = String(describing: ClusterView.self)
}

required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
final class ClusterView: MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
displayPriority = .defaultHigh
}

required init?(coder aDecoder: NSCoder) {
fatalError()
}

override func prepareForDisplay() {
super.prepareForDisplay()

guard let annotation = annotation as? MKClusterAnnotation else {
return
}

let count = annotation.memberAnnotations.count
image = self.image(annotation: annotation, count: count)
}

func image(annotation: MKClusterAnnotation, count: Int) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 40.0, height: 40.0))
image = renderer.image { _ in
UIColor.purple.setFill()
UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0, width: 40.0, height: 40.0)).fill()
let attributes: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 20.0)
]

let text = "\(count)"
let size = text.size(withAttributes: attributes)
let rect = CGRect(x: 20 - size.width / 2, y: 20 - size.height / 2, width: size.width, height: size.height)
text.draw(in: rect, withAttributes: attributes)
}

return image
}
}
1
mapView.register(ClusterView.self, forAnnotationViewWithReuseIdentifier: String(describing: ClusterView.self))

Understanding CanvasRenderingContext2D and UIBezierPath

Issue #180

CanvasRenderingContext2D

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/bezierCurveTo

The CanvasRenderingContext2D.bezierCurveTo() method of the Canvas 2D API adds a cubic Bézier curve to the current sub-path. It requires three points: the first two are control points and the third one is the end point. The starting point is the latest point in the current path, which can be changed using moveTo() before creating the Bézier curve.

1
void ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);

Where order is control point 1, control point 2 and end point

UIBezierPath

https://developer.apple.com/documentation/uikit/uibezierpath/1624357-addcurve

Appends a cubic Bézier curve to the receiver’s path.

1
addCurve(to:controlPoint1:controlPoint2:)

The same for void ctx.quadraticCurveTo(cpx, cpy, x, y); and addQuadCurve(to:controlPoint:)

How to run ffmpeg in macOS app

Issue #178

Install ffmpeg, which installs ffprobe

1
brew install ffmpeg

Find location of installed ffmpeg

1
which ffmpeg

Add all executables to project

Get error

unable to obtain file audio codec with ffprobe

Run in verbose mode

1
ffmpeg -v

Get

[debug] Encodings: locale US-ASCII, fs utf-8, out None, pref US-ASCII
[debug] Python version 2.7.10 (CPython) - Darwin-18.2.0-x86_64-i386-64bit
[debug] exe versions: ffmpeg present, ffprobe present

Run version of ffprobe

1
ffprobe --version

Get dylb not found

dyld: Library not loaded: /usr/local/Cellar/ffmpeg/4.1.1/lib/libavdevice.58.dylib

Similar issue https://techglimpse.com/ffprobe-command-error-linux-solution/
Read dylib path https://stackoverflow.com/questions/23777191/dyld-library-not-loaded-when-trying-to-run-fortran-executable-from-objective-c

How to run executable in macOS

Issue #176

Enable executable

1
chmod +x executable

Add executable file to target
Use Process with correct launchPad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import Foundation

protocol TaskDelegate: class {
func task(task: Task, didOutput string: String)
func taskDidComplete(task: Task)
}

class Task {

weak var delegate: TaskDelegate?
let process = Process()

func run(arguments: [String]) {
DispatchQueue.background.async {
let launchPath = Bundle.main.path(forResource: "executable", ofType: "")!
self.run(launchPath: launchPath, arguments: arguments)
}
}

func stop() {
DispatchQueue.background.async {
if self.process.isRunning {
self.process.terminate()
}
}
}

// MARK: - Helper

private func run(launchPath: String, arguments: [String]) {
let process = Process()
process.launchPath = launchPath
process.arguments = arguments

let stdOut = Pipe()
process.standardOutput = stdOut
let stdErr = Pipe()
process.standardError = stdErr

let handler = { [weak self] (file: FileHandle!) -> Void in
let data = file.availableData
guard let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else {
return
}

guard let strongSelf = self,
let string = output.components(separatedBy: "\n").first else {
return
}

DispatchQueue.main.async {
strongSelf.delegate?.task(task: strongSelf, didOutput: string)
}
}

stdErr.fileHandleForReading.readabilityHandler = handler
stdOut.fileHandleForReading.readabilityHandler = handler

process.terminationHandler = { [weak self] (task: Process?) -> () in
stdErr.fileHandleForReading.readabilityHandler = nil
stdOut.fileHandleForReading.readabilityHandler = nil

guard let strongSelf = self else {
return
}

DispatchQueue.main.async {
strongSelf.delegate?.taskDidComplete(task: strongSelf)
}
}

process.launch()
process.waitUntilExit()
}
}

How to make scrollable vertical NSStackView

Issue #173

You might need to flip NSClipView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import AppKit
import Anchors
import Omnia

final class ScrollableStackView: NSView {
final class FlippedClipView: NSClipView {
override var isFlipped: Bool {
return true
}
}

override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setup()
}

required init?(coder decoder: NSCoder) {
fatalError()
}

let stackView: NSStackView = withObject(NSStackView()) {
$0.orientation = .vertical
}

private let scrollView: NSScrollView = NSScrollView()

private func setup() {
addSubview(scrollView)
scrollView.hasVerticalScroller = true
scrollView.drawsBackground = false

activate(
scrollView.anchor.edges
)

let clipView = FlippedClipView()
clipView.translatesAutoresizingMaskIntoConstraints = false

clipView.drawsBackground = false
scrollView.contentView = clipView

activate(
clipView.anchor.edges.equal.to(scrollView.anchor)
)

scrollView.documentView = stackView
stackView.translatesAutoresizingMaskIntoConstraints = false
activate(
clipView.anchor.left.top.right.equal.to(stackView.anchor)
)
}
}

Read more

How to make view take up full width in vertical NSStackView

Issue #172

https://stackoverflow.com/questions/51644692/nsstackview-subviews-not-resizing-sub-stack-views/55220837#55220837

If you want child view inside vertical NSStackView to fill its parent width, then reduce contentCompressionResistancePriority

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
myChildView.translatesAutoresizingMaskIntoConstraints = false
myChildView.setContentCompressionResistancePriority(
NSLayoutConstraint.Priority(rawValue: 1),
for: .horizontal
)

NSLayoutConstraint.activate([
myChildView.heightAnchor.constraint(equalToConstant: 50)
])

NSAnimationContext.runAnimationGroup({context in
context.duration = 0.25
context.allowsImplicitAnimation = true
stackView.insertArrangedSubview(myChildView, at: 1)

view.layoutSubtreeIfNeeded()
}, completionHandler: nil)

Updated at 2020-11-20 16:11:11

How to load top level view from xib in macOS

Issue #171

1
2
3
var views: NSArray?
NSNib(nibNamed: NSNib.Name("ProfileView"), bundle: nil)?.instantiate(withOwner: nil, topLevelObjects: &views)
let profileView = views!.compactMap({ $0 as? ProfileView }).first!

How to fix MethodError - undefined method `real_path` with CocoaPods?

Issue #170

abc

I’m using cocoapods 1.6.0.beta.2 in a React Native apps and it has been working fine. The pods that I need is Firebase and FacebookSDK. Today after pod install, I got error

1
NoMethodError - undefined method `real_path' for nil:NilClass

I then tried running pod deintegrate to start from scratch, but that command fails as well.

My next try is with cocoapods 1.6.0 and cocoapods 1.6.1 but the problem still persists.

undefined method in Ruby means that we are calling a method on an object that is nil. I like to track down problems via reading code, but this error is very vague.

Looking thorough at the log, I see CocoaPods has done fetching dependencies, but fails at the integration step, so it must be something wrong with my project file.

Then I trace back commits to project.pbxproj to see if there’s anything wrong. It turns out that there was a commit that accidentally removes Pods-MyApp Staging.release.xcconfig from project. That also comes with removal of

1
baseConfigurationReference = B7FC69316CC27F11022F8A82 /* Pods-MyApp Staging.release.xcconfig */;

CocoaPods uses xcconfig

As you know, CocoaPods uses xcconfig files to declare pod related information like FRAMEWORK_SEARCH_PATHS, OTHER_LDFLAGS and other variables like

1
2
3
4
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods

And for a normal pod install, CocoaPods adds some xcconfig files to your project, and the path is Pods/Target Support Files/Pods-DeepDiffDemo. There will always be debug.xcconfig and release.xcconfig for each of your project target.

xcconfig

If your project MyApp has a production target called MyApp Production and MyApp Staging, then you should have these files

1
2
3
4
Pods-MyApp Staging.debug.xcconfig
Pods-MyApp Staging.release.xcconfig
Pods-MyApp Production.debug.xcconfig
Pods-MyApp Production.release.xcconfig

These are added to projects but not checked to any targets. Just like plist, you don’t need to add xcconfig files to target.

If you go to project Info page, you will see that these xcconfig files are selected

project

Missing xcconfig

In my case, Pods-MyApp Staging.release.xcconfig was somehow missing from project, hence all pod commands fail.

The fix is to re-add that file and select that xcconfig in project Info page

Understanding weak and strong in Objective C

Issue #164

From my own blog post https://github.com/Fantageek/fantageek.github.io/blob/source/source/_posts/2014-06-27-understanding-weak-self-and-strong-self.markdown


Blocks are wonderful. To avoid retain cycle you often see the weakSelf - strongSelf dance like this

1
2
3
4
5
6
__weak __typeof__(self) weakSelf = self;
self.block = ^{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
};

When block is created

Block is object when it is copied

Blocks are created on the stack and will go away when their stack frame returns. While on the stack, a block has no effect on the storage or lifetime of anything it accesses.

If blocks need to exist after the stack frame returns, they can be copied to the heap and this action is an explicit operation. This way, a block will gain reference-counting as all objects in Cocoa. When they are copied, they take their captured scope with them, retaining any objects they refer

Blocks can capture values from the enclosing scope

As well as containing executable code, a block also has the ability to capture state from its enclosing scope. Note that block captures the variable along with its decorators (i.e. weak qualifier),

=> This explains why you need to declare self as __weak

When block is executed

When block is executed, it is possible for weakSelf to be non-nil for the first method (doSomething), but not for the second (doSomethingElse)

You may think, at first, this is a trick to use self inside the block avoiding the retain cycle warning. This is not the case. The strong reference to self is created at block execution time while using self in the block is evaluated at block declaration time, thus retaining the object.

For best practice, however, you should create a strong reference of your object using the weak one. This won’t create a retain cycle either as the strong pointer within the block will only exist until the block completes (it’s only scope is the block itself).

=> This explains why you need to declare another __strong self

More explanation

This is my answer to reader Nikita

  1. As many people point out, “Blocks are created on the stack and will go away when their stack frame returns. While on the stack, a block has no effect on the storage or lifetime of anything it accesses.”
    Even if block (declared on the stack) increase reference count to all the object it accesses, this would be useless, because this block will be discard when function returns

  2. When block are copied (You see that people usually declare property (copy) for block), it will increase reference count to all the objects it accesses.

Why? because block are mean to be executed at a later time, so it need to keep strong reference to all the object it access. Block can be executed MANY TIMES, so IT WON’T RELEASE self AFTER this ran.

When you nil out the block, it will be dealloc, hence it will decrease the reference count to all the objects it access.

AFNetworking nil out the block after it is called, so you don’t have to use weakself inside block http://www.fantageek.com/1376/afnetworking-gotcha-2/

  1. So there are cases when you don’t have to use weakself inside block
    a. Make sure the block is not copied, you simply declare and run it
    b. Make sure the block is nil out after it is called

Reference

Using CircleCI 2.0

Issue #158

We ‘ve been using CircleCI for many of our open source projects. Since the end of last year 2017, version 2.0 began to come out, and we think it’s good time to try it now together with Swift 4.1 and Xcode 9.3

The problem with version 2.0 is it’s so powerful and has lots of cool new features like jobs and workflows, but that requires going to documentation for how to migrate configuration file, especially Search and Replace Deprecated 2.0 Keys

Creating config.yml

The first thing is to create a new config.yml inside folder .circleci

Copy your existing circle.yml file into a new directory called .circleci at the root of your project repository.

Next is to declare version and jobs

Add version: 2 to the top of the .circleci/config.yml file.

Checking xcodebuild

For simple cases, we just use xcodebuild to build and test the project, so it’s good to try it locally to avoid lots of trial commits to trigger CircleCI. You can take a look at this PR https://github.com/hyperoslo/Cheers/pull/20

Before our configuration file for version 1.0 looks like this

1
- set -o pipefail && xcodebuild -project Cheers.xcodeproj -scheme "Cheers-iOS" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 8,OS=11.0' -enableCodeCoverage YES test

Now we should put pipefail inside shell, follow https://github.com/CircleCI-Public/circleci-demo-ios/blob/master/.circleci/config.yml

shell: /bin/bash –login -o pipefail

Now is the actual trying xcodebuild, after many failures due to destination param

1
2
3
4
5
6
xcodebuild: error: Unable to find a destination matching the provided destination specifier:
{ platform:iOS Simulator, OS:11.3 }

Missing required device specifier option.
The device type “iOS Simulator” requires that either “name” or “id” be specified.
Please supply either “name” or “id”.
1
xcodebuild: error: option 'Destination' requires at least one parameter of the form 'key=value'

I found this to work, run this in the same folder as your xcodeproj

1
xcodebuild -project Cheers.xcodeproj -scheme "Cheers-iOS" -sdk iphonesimulator -destination "platform=iOS Simulator,OS=11.3,name=iPhone X" -enableCodeCoverage YES test

Adding workflow

Version 2.0 introduces workflow which helps organising jobs

A workflow is a set of rules for defining a collection of jobs and their run order. Workflows support complex job orchestration using a simple set of configuration keys to help you resolve failures sooner.

For our simple use cases, we add this workflow

1
2
3
4
5
workflows:
version: 2
build-and-test:
jobs:
- build-and-test

Collecting Test Metadata

CircleCI collects test metadata from XML files and uses it to provide insights into your job

Final

Use below as template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
version: 2.1
jobs:
build_test:
macos:
xcode: "11.0"
shell: /bin/bash --login -o pipefail
steps:
- checkout
- run:
command: |
curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf
pod install
- run:
command: xcodebuild -workspace MyApp.xcworkspace -scheme "MyApp" -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone X,OS=12.2" -enableCodeCoverage YES test
- store_test_results:
path: test-results

workflows:
version: 2.1
primary:
jobs:
- build_test

Read more

Jitter buffer in VoIP

Issue #157

This post was from long time ago when I did pjsip


A jitter buffer temporarily stores arriving packets in order to minimize delay variations. If packets arrive too late then they are discarded. A jitter buffer may be mis-configured and be either too large or too small.

Impact

If a jitter buffer is too small then an excessive number of packets may be discarded, which can lead to call quality degradation.

Lower settings cause less delay in the meeting, but meetings with lower settings are more susceptible to jitter effects caused by network congestion. Less data is buffered, increasing the likelihood that delayed or lost packets will produce a jitter effect in the media stream.

If a jitter buffer is too large then the additional delay can lead to conversational difficulty.

Higher settings are more effective at reducing jitter effects. With higher settings, more data is buffered, which allows more time for delayed packets to arrive at the client. However, higher settings also result in more delay (or latency) in the meeting. A user who is speaking will not be heard immediately by the other meeting participants. The delay in the meeting increases with the amount of time that data is held in the buffer.

Resolution

A typical jitter buffer configuration is 30mS to 50mS in size. In the case of an adaptive jitter buffer then the maximum size may be set to 100-200mS. Note that if the jitter buffer size exceeds 100mS then the additional delay introduced can lead to conversational difficulty.

Reference

  1. lib.boulder.ibm.com/infocenter/sametime

  2. http://www.voiptroubleshooter.com

Some Windows Phone 7 development tips

Issue #156

This post was from long time ago when I did Windows Phone 7


Welcome back, today I will start writing all stuff, all the secret I ‘ve revealed when developing for Windows Phone 7.

  1. When setting UIElement‘s Visibility to Collapsed, the OS set its Height to 0. So if you need to have a Height of 0, simply set Height="0", which is faster in term of show time.

  2. When clearing data source, ListBox which uses VirtualizingStackPanel does not rememeber its last view, whereas StackPanel does

  3. ListBox which is inside ScrollViewer will lose its UI virtualization, even if you use VirtualizingStackPanel

  4. Using GestureListener incorrectly will cause other UIElement to not work, i.e Slider, …

  5. AdControl, ContextMenu may be a source of memory leak.

  6. Panorama control still enable swiping when there is only 1 PanoramaItem, whereas Pivot does not.

  7. Pivot control will load the current PivotItem, as well as its direct left and right PivotItem.

  8. Sometimes SIP keyboard does not show up, check your phone for physical keyboard problem.

  9. There is no way to rate a Song for 3rd app

  10. When emulator rendering has overlap issue, the workaround is to set LayoutRoot ‘s Background to a solid color

  11. Supposed you’re using ObservableCollection bindSource as binding source for ListBox. When adding items to bindSource quickly, ListBox seems to suppress creating ListBoxItem until all items are added. It is advised to add items in batch

  12. WebBrowser control does not have scroll bar

  13. Using StoryBoard when ever possible, because all of its animation is done in Compositor Thread, which leverages GPU

  14. Use ProgressIndicator for best performance and UX familiarity

  15. Collapse unimportant UIElement to decrease load time, and show them when Page is completely loaded.

  16. Always unsubscribe to event source that can continue to run when the Page is navigated from, because that may cause memory leak. Examples are timer, BackgroundAudioPlayer.PlayeStateChanged, …

  17. Using file is much faster than Local Database

  18. Remember the limit 260 characters of query string

How to calculate packet size in VoIP

Issue #155

As you have probably observed in your studies, there is a determined method for calculating VoIP packet sizes. The packet size depends on many different variables, so there is no great answer for an “average” packet size – average depends on the environment. Just as an example, if you currently have VoIP running within a LAN and want to provision a new WAN so you can use VoIP to another site, knowing how big your VoIP packets are on the LAN won’t help. See below for a VoIP packet size calculation for a typical LAN, which will get you started.

Packet size

The general formula for VoIP packet size is this

1
Frame overhead + Encapsulation overhead + IP overhead + Voice payload.

Let’s say the packet is going across our LAN, so right now the frame overhead is 18 Bytes, for Ethernet II. (This size would change later if the packet crosses a trunk with 802.1Q tagging or ISL encapsulation, or is destined for the WAN, where a different link layer framing will probably be in use.)

Encapsulation

Encapsulation overhead would include things like IPSec tunnels for security. Suppose we are not encapsulating this voice packet, so there is no overhead here.

“IP overhead” has overhead occurring at layer 3 and above, so for SIP phones this means IP (20 Bytes), UDP (8 Bytes), and RTP (12 Bytes). This is a total of 40 Bytes of IP overhead.

Lastly, you must calculate the size of the actual voice payload. Suppose we use the G.711 codec, which gives us a codec bandwidth of 64kbps. Also suppose our phones have a packetisation period of 20ms (meaning 20ms worth of voice goes into every packet). With these two numbers, we can figure out the size of the voice payload. Since one second of voice contains 64 kilobits of data (“64 kbps”), it is easy to calculate how many bits

Find the amount of Bytes per payload:

1
2
64000 bits * .02 seconds = 1280 bits of voice per payload  
1280 bits / 8 bits per byte = 160 Bytes of voice per payload

The total overhead is 58 Bytes (18 + 40)
The total VoIP packet size is 218 Bytes (160 + 58 )

In the interest of full disclosure, it is easy to get a bit rate per second from here; just convert 218 Bytes into bits and multiply by the packetization rate (which is the inverse of your packetization period, in this case 50 packets per second). The bit rate for ONE stream of this voice is 87.2kbps… we hope the user isn’t just talking to himself, so double that for an actual phone conversation.

There are lots of other little things, like VAD and various header compressions, that you may need to factor into these calculations as well. As you can see, any one of these many things being off will give you a different answer, so knowing how to go about the entire process is important.

Reference

  1. www.techexams.net

UITableViewCell and Model

Issue #154

The most common UI element in iOS is UITableView, and the most common task is to display the UITableViewCell using the model.

Although the title specifies UITableViewCell, but the problem involves other views (UICollectionView, custom view, …) as well

There are many debates about this, so here I want to make a summary, all in my opinion

It started with

UITableViewCell Is Not a Controller
This article follows strict MVC and states we should not pass model object to cell and let cell manipulate directly on the model. He shows several examples that point out this job is not suitable for the cell. Instead this job should be done on the Controller (UIViewController, UITableViewDataSource, …)

Category for simple case

Skinnier Controllers Using View Categories
This article states that we should keep ViewController skinnier by transfering the job (mapping model object to cell) to the cell category

Using subclassing

UITableViewCell Is Not a Controller, But…
This articles explains the beauty of subclassing to take advantage of Polymorphism. In the theming example he gives, we see that Controller ‘s job now is to select the correct cell subclass, and the subclass ‘s job is to know how to use the model
“When a UITableViewCell subclass accepts a model object parameter and updates its constituent subviews as I have described, it is behaving as a data transformer, not a controller”

Model Presenter

Model View Controller Presenter
This article shows that using subclassing and category will have duplication implementation when you have more cells and models. After several optimizations, he finally gets to the Model Presenter, which is the center object who knows how to represent the model in different view.
“This is an object that knows how to represent every aspect of a certain model”

MVVM

MVC, MVVM, FRP, And Building Bridges
This article explains MVVM but it touches our problem directly. The problem with cell and model actually is

  1. How to map the model to the cell
  2. Who will do this job? The cell, Controller or another Model Mapping object ?

The ViewModel is actually who does this work, which is to transform the model to something the view can easily use

“A table view data source is none of these things. It’s purely a layer between the table view and the model. The model defines lists of things, but the table view data source transform those lists into sections and rows. It also returns the actual table view cells, but that’s not what I’m focusing on here. The key is its role as a middle-tier data transformer.”

Do we access the cell ‘s subviews

Paul on UITableViewCell
Brent follows with another post explaining how cell ‘s subviews should not be accessed outside of the cell

Data Source

Clean table view code
This article deals with Bridging the Gap Between Model Objects and Cells.
“At some point we have to hand over the data we want to display into the view layer. Since we still want to maintain a clear separation between the model and the view, we often offload this task to the table view’s data source. This kind of code clutters the data source with specific knowledge about the design of the cell. We are better off factoring this out into a category of the cell class”

This together with Lighter View Controllers shows a practical example that deals with most cases

Reference

  1. Should UITableViewCells Know About Model Objects?

Netcut and ARP

Issue #153

Bad people can use Netcut to limit other ‘s internet access in same network

How does Netcut work

Netcut uses attacking technique called ARP Spoofing.

ARP (Address Resolution Protocol) is a link layer procotol, it is used for resolving network layer address (IP) into link layer address (MAC).

When we want to send IP packet to another host (a computer in the same LAN, or the Gateway), we must know the destination IP address. In order for the packet to reach the destination, the destination IP address must be converted to the corresponding MAC address, so the transmission can be processed in the data link layer. To setup this mapping IP-MAC in the ARP table, we must first send ARP request as broadcast one to the LAN. In the happy case, the one with the IP in the ARP request will reply us with his MAC address.

Unfortunately, ARP is a stateless protocol. This means we continue to accept ARP replies and overwrite the old ones, even they have not expired yet. Worse, ARP does not define any authentication method to check whether the replies come from the trusted one (the one we want to receive the replies). These offer chances for the attacker to perform ARP Spoofing.

The theory behind ARP Spoofing

From the machine in the LAN, the attacker associate his MAC address and IP address of the target host (usually the Gateway), so that any traffic sent to that target host will come to the attacker. Here the attacker can decide whether to modify the packet, send or not.

There are 3 types of ARP Spoofing

  1. Spoof the host computer
  2. Spoof the Gateway
  3. Spoof both the host computer and the Gateway

To see the ARP table

Open Command line (Windows) or Terminal (Mac OSX), and type

1
arp -a

This will list the MAC address associated with a specific IP. Note that some mappings maybe wrong due to ARP Spoofing

How to prevent against this ?

To prevent spoofing our computer, we can use softwares (search for Anti Netcut, Anti ARP spoofing, …) or set the static ARP ourselves. In fact, those softwares are based on setting static ARP. This way we set the static mapping IP-MAC for a specific host (computer or Gateway), and the OS definitely ignores all ARP replies for that IP.

Example of static ARP mapping associated with the Gateway, performed on the computer host

1
192.168.1.1 B4-B3-62-7C-CE-55

Here 192.168.1.1 and B4-B3-62-7C-CE-55 are the Gateway’s IP and MAC address

To prevent spoofing the Gateway, we must set static ARP on that Gateway. Go to the Gateway/Router interface, in its ARP setting, fill in the mapping IP-MAC for a specific host

Look for that Gateway manual or the related guide on how to perform this. This is devices dependent-method, but the theory remains the same.

Example of static ARP mapping associated with the computer host, performed on the Gateway

1
192.168.1.2 64-70-02-B2-9B-E1

Here 192.168.1.2 and 64-70-02-B2-9B-E1 are the machine host ‘s IP and MAC address. The machine host can be any computer in the LAN

How to set static ARP

Proposed that

192.168.1.1 The destination host ‘s IP

B4-B3-62-7C-CE-55 The destination host ‘s MAC

Local Area Connection Our network interface name

Open Terminal (Mac OSX)

To delete specific mapping

1
sudo arp -d 192.168.1.1

To set specific mapping

1
sudo arp -s 192.168.1.1 B4-B3-62-7C-CE-55

Reference

  1. http://en.wikipedia.org/wiki/ARP_spoofing

Make your own sliding menu on Android tutorial – Part 2

Issue #152

This is the part 2 of the tutorial. If you forget, here is the link to part 1.

Link to Github

In the first part, we learn about the idea, the structure of the project and how MainActivity uses the MainLayout. Now we learn how to actually implement the MainLayout

DISPLAY MENU AND CONTENT VIEW

First we have MainLayout as a subclass of LinearLayout

1
public class MainLayout extends LinearLayout

We then need declare the constructors

Read More

Make your own sliding menu on Android tutorial - Part 1

Issue #151

This post was from long time ago when I did Android


I can’t deny that Facebook is so amazing, they made trends and people want to follow. That is the case of the sliding menu.

Searching many threads on SO, like these create android Sliding Menu like Facebook,gmail, Android sliding menu similar to the one on facebook, Android - Sliding menu with sub menu … they will mostly introduce you to some good sliding menu libraries, like this SlidingMenu and some tutorials on how to use it. And of course, not forget to mention the Navigation Drawer that shipped with the Android SDK

If you are a do-it-yourself guy like me, then here is the tutorial for you. It is mostly based on the following Youtube videos and guides

Read More