iOS V.S. Android (Language Characters)

2021, Oct 20    

After a long journey in iOS native stack development, my recent incoming projects will be leaning back to Android side again (previously before I started working on iOS in recent years, I’ve been working on Android for around 3 years). After a roughly going through the contents in google’s official Android developer portal, it is a very instinctive thing for a developer with a similar background like me to come out the idea of making a comparison on the two platforms.

By having a systematically comparison of the two platform, one can understand clearly about the difference and similarities of the two and hence, become easier to memorize and handle the development techniques on both of them.

Since the knowledge map of Android and iOS developement contains a long list of topics, which might include for example: OS Characters, Language, Architecture Components, Engineering Tools & Automations, Challenges and Advantages, and etc. When I was writing this article, I was planning to have several post as a series to cover this. In this post, I would like to start with the comparison from the language aspect.

Comparison

The characters of the two languages are summarized from their official documents: https://kotlinlang.org and https://swift.org. Sections are devided following the language document section strcutures provided on the two document portal.

1. Basics

1.1 Variables

  • Swift:
    • let/var
    • static let for thread safety lazy.
    • support get/set/willSet/didSet
  • Kotlin:
    • val/var
    • lateinit var for lazy properties with thread safety.
    • const can be used to declare a compile time constant.
    • support getter/setter

1.2 Data Classes

  • Swift:
    • Struct: passed by value, copy on modify
  • Kotlin:
    • Data Class: auto generate hash/equal/toString
    • Inline Class: aka wrapper class, provides a wrapping on the single wrapped in properties.

1.3 Protocols

  • Swift:
    • Protocol: support default implementation via protocol’s extension
  • Kotlin:
    • Interface:
      • support SAM (which can be used as lambda expressions or function input params)
      • support interface delegation and hence provide default implementations. (note: members overridden in this way do not get called from the members of the delegate object, refer to this issue)
// add singleton
object GoldColor: AquariumFish {
	override val color: String
		get() = "gold"
}

// example of interface delegation for fishAction
class PrintFishAction(val food: String) : FishAction {
	override fun eat() {
		println(food)
	}
}

// 此处函数完全通过interface delegation 替代
// 类似于swift 中的extension default implementation for protocol
class Plecostomus: AquariumFish by GoldColor,
FishAction by PrintFishAction("testing food")

1.4 Optional Handlings

  • Swift:
    • if: optional unwrapping
    • guard: check
    • ? for optional chaining
    • ! force unwrapping
    • ?? short circuit valuation
  • Kotlin:
    • if: the clause and clause after if block will automatically unwrap the optional.
    • variable?.let { } conduct nil check.
    • ?: for short circuit.
    • !!: for force unwrap.

1.5 Control Flow

Generally, the control condition in Swift does not require a braket but for those in kotlin need. e.g. if (xxxx != xxx) {}

  • Swift:
    • for/while/do-while/if-else
    • switch
  • Kotlin:
    • for/while/if-else
    • use when as a conditional switch
when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> {
        print("x is neither 1 nor 2")
    }
}

1.6 Visibilities

  • Swift:
    • scopes: private \ fileprivate \ internal \ public \ open
  • Kotlin:
    • scopes: private \ protected \ internal \ public \ open

note:

  • protected in kotlin is not available on package level declaration,
  • private in kotlin is same as fileprivate in Swift.
  • internal/public/open has same visibility definition accross the two languages.

1.7 Nested Classes

  • Swift:
    • inner class: can be initialized with out the outter class instance.
  • Kotlin:
    • inner class: specified with inner keyword, needs to initialize with the outter class instance.
    • nested class: can be initialized with out the outter class instance.

1.8 Closures/Lambdas

In both language, code blocks in self-conatining form are provided to act as function/function parameter/variables in code. In Swift it is called closure and in kotlin it is called lambdas (actually lambda is a more commonly used term to describe this in consideration of other languages like javascript).

  • Swift:
    • implicit return: only applicable for clousre with a simple expression
    • implicit param name: $0, $1
    • support passing trailing closure someFunctionThatTakesAClosure() { // trailing closure's body goes here }
    • use @escaping to denote a escaping closure.
  • Kotlin:
    • implicit return: support all levels of
    • implicit param name: only available for single params
    • support passing trailing lambda val product = items.fold(1) { acc, e -> acc * e }
    • support default scope functions: let/run/with/apply/also, there are some trivial differences between those scope lambda, please refer to this article.
    • for lambdas in kotlin can be bound to a typealias typealias ClickHandler = (Button, ClickEvent) -> Unit

1.9 Coding Conventions

1.10 Operators

  • Swift:
    • support overloading existing operators.
    • support the customisation of prefix/infix/postfix operators, taking the following as the example.
struct Vector2D {
    var x = 0.0, y = 0.0
}

infix operator +-: AdditionPrecedence
extension Vector2D {
    static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y - right.y)
    }
}
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
fun main() {
   println(-point)  // prints "Point(x=-10, y=-20)"
}

1.11 Annotations (Attibutes)

  • For swift: annotations are basically compiler attibutes and used in compilation time, there is no flexibility to customize your own.
  • For kotlin: the annotations are API capabilities which can be used in both compile time and runtime. It is quite common to use annotations to generate classes/methods during compilation time and for the purpose of dependency injections.
infix fun Int.shl(x: Int): Int { 
	this + x
}

// the function call below is identical
1 shl 2
1.shl(2)

2. Advanced

2.1 Dynamic Capabilities

  • Swift: heavily focuses on compile time validation, the reflection capability provided in Swift is much limited than other languages.
    • Swift officially there are APIs provided limited use cases like Mirror in section debugging and reflection
    • Since in iOS both Swift and ObjC are under the same runtime, hence swift can make use of ObjC capabilities: method swizzling, kvo, kvc, etc.
  • Kotlin: similar to java, you can dynamically inject dependencies, invoke methods and retrieve properties via reflections.

2.2 Generics

  • Swift: using generics are quite straight forward in Swift, where it can be both applied in class and methods. generics can be added with class or protocol constraints.
    • In protocols, generics needs to be declared in form of associated types.
  • Kotlin: not about the concept of in and out, in is used in generics types for input parameters (aka consumer), out is used in generics types for output parameters (aka producer). Can refer this article to enhance your understanding.
    • for in as consumer [? super in Java]
    • for out as producer [? extend in Java]
TOC