Stacked Heaps!

Heaps of insights, stacked for the curious minds.

How do you extend a class to support a new type? In following code, how would you extend class A to take TypeB instead of TypeA?

class A {

    let type: TypeA

    var title: String {
        type.name
    }

    func ready() {
        type.doThing()
    }

}

class TypeA {

    let name: String

    func doThing() {

    }

}

One approach is to inject dependency. We can update concrete dependency with a protocol. Now any type confirming to a protocol can be used in class A.

protocol ThingType {

    var name: String { get }
    func doThing()

}

class A {

    let type: ThingType

    var title: String {
        type.name
    }

    func ready() {
        type.doThing()
    }

}

class TypeA: ThingType {

    let name: String

    func doThing() {

    }

}

What if, you can't inject your dependencies? Like you can't construct an object or constructor is dependent on the properties/state of the class A. What other options do you have? Either is very powerful pattern you can use in cases like this.

// This can be extended to as many cases you need. 
public enum Either<A, B> {
    case a(A)
    case b(B)
}

public class A {
    let type: Either<EitherTypeA, EitherTypeB>

    public init(with rawType: String, name: String) {
        // Made up scenario to show dependencies are not injected.  
        if rawType == "a" {
            self.type = Either.a(EitherTypeA(name: name))
        } else {
            self.type = Either.b(EitherTypeB(name: name))
        }
    }

    public func ready() {
        type.doThing()
    }
}

public class EitherTypeA {
    public let nameA: String

    public init(name: String) {
        self.nameA = name
    }

    public func doAThing() {
        print("Doing \(nameA) thing")
    }
}

public class EitherTypeB {
    public let nameB: String

    public init(name: String) {
        self.nameB = name
    }

    public func doBThing() {
        print("Doing \(nameB) thing")
    }
}

/// Property and function signatures doesn't need to be identical. We can map them as needed. 
extension Either where A == EitherTypeA, B == EitherTypeB {

    var name: String {
        switch self {
        case .a(let typeA): return typeA.nameA
        case .b(let typeB): return typeB.nameB
        }
    }

    func doThing() {
        switch self {
        case .a(let typeA): typeA.doAThing()
        case .b(let typeB): typeB.doBThing()
        }
    }
}

If you have to work with Class A without Either pattern. Class A will be full of switch-cases/if-else and optional properties.

Here is a playground with all the above scenarios.

Cheers, Stacked Heaps


#Pattern


Discover tools, techniques, architectures and patterns to feed your curiosity.

Stacked Heaps!

Naming – I struggled to name this article. Should this article be Naming in software engineering/development/programming?

Naming is hard – irrespective of your native language.

Documentation for code can become outdated, but well-thought-out naming conventions are a living document.

Although subjective, I have some guidelines to assist with naming conventions. While most of these guidelines are applicable to all languages, this article is specifically focused on Swift.

Follow existing standards set in the projects: IMO, this is really important and it's NOT limited to naming. If you don't have already, please use linting tools to set teams preferences. Linter can help you to set min/max lengths, casing etc. for names. This will save you ton of time in PR reviews.

Acronyms

Using acronyms that are internal, business-related, unknown, or associated with a specific feature would lose readability and make the life of a new engineer harder. Well-known acronyms like HTTP, DB, and defined patterns for projects and modules are acceptable.

Namespacing

This is particularly important for third-party frameworks. I have encountered numerous frameworks using common names like Event, CustomEvent, Location etc. This increases the chances of conflicts when integrating projects. Additionally, common names make it challenging to determine which framework object belongs to.

Files

Keep files for a single purpose. One Type per file with matching name.

Types

Avoid using common names that could be conflicted with iOS or third-party frameworks.

Naming a protocol in Swift or interface or abstract class in other languages

Protocol names should not use a concrete product/service name like Firebase/Adobe for a protocol. Concrete names should be used for concrete implementation. Follow a pattern (preferably a suffix) that would help identify a protocol by name.

Examples: – Prefer –> LogType, AnalyticsType, ... – Avoid –> FirebaseLog, AdobeAnalytics, ...

Functions

Follow verb+noun pattern for naming a function. Functions should either return a value or update state. They should not do both!

Repeating name of parent/class in the function name. func configure() in a User Type doesn't need to be named func configureUser() In some cases, when you don't need to know the internals, for example, configure or find, using simply user is acceptable as well.

Functions returning a value: – Prefer –> fetchUser() –> User, buildUser() –> User, ... – Avoid –> userFetch() –> User, userBuild() –> User, ...

Function updating a state: – Prefer –> setUser(_ user: User) –> Void, updateUser(_ user: User) –> Void, ... – Avoid –> userSet(_ user: User) –> Void, userUpdate(_ user: User) –> Void, ..

Properties & local variables

Practice of pre/suffixing variable types in names was introduced because of a lack of language, IDE, and debugging support. Modern languages, tools, and type safety make this redundant and unnecessary.

Examples: – Prefer –> let name: String – Avoid –> let nameString: String

Singleton

I understand it's considered an anti-pattern. However, at times if I need to use it, I find it useful to have a naming pattern to identify Singleton from its name. For instance, I have used Shared* prefix to know the class I am interacting with is a Singleton.

Unnamed tuples

This feature is called by many different names. In Swift, it is referred to as Unnamed tuples – it is a tuple without variable names. Access to variables is done with index. Access via index reduces readability and could lead to unintended errors from referencing incorrect fields.

Examples: – Avoid –> func split(name: String) –> (String, String) { } – Prefer –> func split(name: String) –> (first: String, last: String) { }

Cheers, Stacked Heaps


#Guidelines #Naming #Practices


Discover tools, techniques, architectures and patterns to feed your curiosity.

Stacked Heaps!

Xcode Run Scripts can take input/output file(s) list. It is one of the amazing feature to optimise build process. In my opinion, it is well known but frequently unused.

You are providing list of files to run a script. In this case, you don't need to run SwiftLint on a non-swift file(s). This will be particularly useful if you have a large number of non-Swift data files.

To get started, provide input file/files and output file/files in the Run Script. Check Based on dependency analysis option in the Run Script. This will run the script only if the input has changed.

Create list by file-types:

$PROJECT_DIR/$SCRIPT_LOCATION/input_output_list_for_file_types.sh $FILE_NAME "$TYPE_1 $TYPE_2" $OPTIONAL_OUTPUT_DIR

Gist – file type

Cheers, Stacked Heaps


#Xcode #Build #BuildPhase #RunScript #Performance


Discover tools, techniques, architectures and patterns to feed your curiosity.

Stacked Heaps!

We use Xcode Build Settings to update project to our preferences.

Build Settings are available for every Target and Project in your workspace/project.

I often find myself falling into one common pitfall: Project – Build Settings are overridden by Target. So you could be looking at a Project setting and not realizing it is overridden in Target.

Fortunately, Xcode provides a way to see customized settings. However they could be easily lost in the pool of settings.

All your settings are stored in *.xcodeproj/*.pbxproj. It also contains changes like add/move/remove a file or group, build phases, package dependencies, test target settings and more.

One way to solve this is to keep build settigns changes in *.xcconfig files. All your settings are out of *.xcodeproj/*.pbxproj and code reviews are lot more simpler.

Great! So we decided to use xcconfig. How do we stop everyone from making changes to build settings? Complexities of *.xcodeproj/*.pbxproj XML makes it unreliable to review build settings changes in a PR. xcodebuild command line option doesn't have anything that helps our cause.

Let's explore other options.

Parse pbxproj XML and review build setting changes.

One of the popular tool is Xcodeproj. We can use Xcodeproj and other Ruby tools to write a script that will find build setting changes. – Adds dependency on third-party tool and overhead of managing Ruby environment. If you already using Ruby then it could be a promising option.

Generate *.xcodeproj/*.pbxproj.

Tools like XcodeGen help to create xcodeproj with a script. It will force use to move all our settings in Xcconfig. Xcodegen` comes with features like using directory structure for project. This is a great tool and would recommended as a best practice. – Adds dependency on third-party tool. If you are not interested in the other features provided by the tool, it could be overkill.

Write a shell script to find changes in build settings.

Simplest option, no dependencies! You own and maintain the script.

After listing down above options, I started working on a shell script. I looked at the XML structure and git changes on updating a setting.

Empty buildSettings:

buildSettings = {
}

Modified buildSettings:

buildSettings = {
    $SETTINGS_NAME1=$VALUE1,
    $SETTINGS_NAME2=$VALUE2
}

Since project file contains buildSettings for project & every target. You do have multiple occurrence of buildSettings. All of them follow same pattern. After looking at diff it was clear what should my script do.

We can find occurrence of buildSettings = { count number of line till }

If buildSettings is empty total number of lines will be twice as many of start lines (start + end). If any of the settings are updated, number of lines will be more (start + content + end).

Count occurrence of buildSettings = {

SETTINGS_START_OCCURRENCES=$(sed -n '/buildSettings = {/p' $PBXPROJ_FILE | wc -l)

Count number of lines from buildSettings = { to }

SETTINGS_ACTUAL_START_TO_END_LINE_COUNT=$(sed -n '/buildSettings = {/,/}/p' $PBXPROJ_FILE | wc -l)

If the build settings are empty, the following conditions will return true; otherwise, they will return false.

SETTINGS_ACTUAL_START_TO_END_LINE_COUNT == 2 * SETTINGS_START_OCCURRENCES

I am not a shell script expert. If I could find non-empty occurrence of buildSettings = { } that would be even better. Anyhoo, above works just the great!

Here is the entire script.

Cheers, Stacked Heaps


#Xcode #BuildSettings #Tools #RunScript


Discover tools, techniques, architectures and patterns to feed your curiosity.

Stacked Heaps!

Let's write our first Swift program.

print("Hello, World!")

Voila!

Hello, World!

You are all done :)

Cheers, Stacked Heaps


#InitialCommit #Swift


Discover tools, techniques, architectures and patterns to feed your curiosity.

Stacked Heaps!

Enter your email to subscribe to updates.