Let’s play with swift-part 4

Generic, Access Modifiers, Custom operator, Subscript and Keypath

Rejaul Hasan
10 min readAug 23, 2021

For better understanding you can read the Swift apprentice chapter- 17,18,19.

Generic

class Cat{
var name: String
init(name: String) {
self.name = name
}
}
class Dog{
var name: String
init(name: String) {
self.name = name
}
}
class Keeper<Animal>{
var name: String
var inMorning: Animal
var inNoon: Animal
init(name: String, morningCare: Animal, afternoonCare: Animal) {
self.name = name
self.inNoon = afternoonCare
self.inMorning = morningCare
}
}
let jason = Keeper(name: "Jason",morningCare: Cat(name: "Whiskers"),afternoonCare: Cat(name: "Sleepy"))let ana = Keeper(name: "Ana",morningCare: Dog(name: "Whiskers"),afternoonCare: Dog(name: "Sleepy"))let testString = Keeper(name: "Ana",morningCare: "Cat",afternoonCare: "Dog")

Here Animal is just a type holder. So, it’s work is to take whatever type we want to give. So we can give Cat, Dog object at the same time, we can give String also.

We can restrict the accepted type by a generic through several way.

protocol Pet{
var name: String { get }
}
class Cat: Pet{
var name: String
init(name: String) {
self.name = name
}
}
class Dog: Pet{
var name: String
init(name: String) {
self.name = name
}
}
class Keeper<Animal: Pet>{
var name: String
var inMorning: Animal
var inNoon: Animal
init(name: String, morningCare: Animal, afternoonCare: Animal) {
self.name = name
self.inNoon = afternoonCare
self.inMorning = morningCare
}
}

Here, the constraint : Pet requires that the type assigned to Animal must be a subclass of Pet, if Pet is a class, or must implement the Pet protocol, if Pet is a protocol.

extension Array where Element:Cat{
func meow() {
forEach { cat in
print("\(cat.name) meow.")
}
}
}
let test = [Cat(name: "Sleepy"), Cat(name: "Whiskers")]
test.meow()

We can use an extension to specify that when the array’s Element is a Cat the array provides meow().

We can even specify that a type should conform to some protocol only if it meets certain constraints.

protocol Meowable {
func meow()
}
protocol PrintAllMewName{
func printAll()
}
extension Cat: Meowable {
func meow() {
print("\(self.name) says meow!")
}
}
extension Array: PrintAllMewName where Element: Meowable{
func printAll() {
forEach {
print($0.meow())
}
}
}

Generic used in build in Array, Dictionary and Optional

Array

let animalAges: [Int] = [2,5,7,9]

and

let animalAges: Array<Int> = [2,5,7,9]

are similar. So inside array struct there is construct methods which use generic to accept any type you want to provide. Like below

Struct Array<Element>{
init(unsafeUninitializedCapacity: Int, initializingWith initializer: (inout UnsafeMutableBufferPointer<Element>, inout Int) throws -> Void) rethrows
@inlinable public init(repeating repeatedValue: Element, count: Int)@inlinable public init<S>(_ elements: S) where S : Sequence, Element == S.Element}

Dictionary

Dictionary has two type parameters in the comma-separated generic parameter list that falls between the angle brackets, as you can see in its declaration

struct Dictionary<Key: Hashable, Value>

So dictionary took 2 generic parameter but the key parameter must confirm the protocol Hashable. So key is constraints in Hashable. We can assign value like this.

let intNames: Dictionary<Int, String> = [42: "forty-two"]
let intNames = [42: "forty-two"]

Optional

As optional uses enum but the enum took generic as associate value like below

enum Optional<Wrapped> {
case none
case some(Wrapped)
}

Generic function parameters

Functions can be generic as well. A function’s type parameter list comes after the function name. You can then use the generic parameters in the rest of the definition.

func swapped<T, U>(_ x: T, _ y: U) -> (U, T) {
(y, x)
}
swapped(33, "Jay") // returns ("Jay", 33)

Access Control,Code Organization

typealias Dollars = Double
protocol Account {
associatedtype Currency
var balance: Currency { get }
func deposit(amount: Currency)
func withdraw(amount: Currency)
}
class BasicAccount: Account{
var balance: Dollars = 0.0
func deposit(amount: Double) {
balance+=amount
}
func withdraw(amount: Double) {
if balance>amount {
balance -= amount
}else{
balance = 0
}
}
}
let obj = BasicAccount()
obj.deposit(amount: 10.0)
obj.withdraw(amount: 6.0)
obj.balance = 1000.5
print(obj.balance)

Oh no! Even though you carefully designed the Account protocol to only be able to deposit or withdraw funds, the implementation details of BasicAccount that allow it to update its own balance could be used by any code. So, we can constraints our balance access by making it’s set as private and get as public as it is now.

class BasicAccount: Account{
private(set) var balance: Dollars
//Others code
}

The access modifier above is placed before the property declaration, and includes an optional get/set modifier in parentheses. In this example, the setter of balance is made private. Types of access modifiers are.

  • private: Accessible only to the defining type, all nested types and extensions on that type within the same source file.
  • fileprivate: Accessible from anywhere within the source file in which it’s defined.
  • internal: Accessible from anywhere within the module in which it’s defined. This is the default access level.
  • public: Accessible from anywhere within the module in which it is defined, as well as another software module that imports this module.
  • open: The same as public, with the additional ability of being able to be overridden by code in another module.

Private

The private access modifier restricts access to the entity it is defined in, as well as any nested type within it — also known as the “lexical scope”. Extensions on the type within the same source file can also access the entity.

typealias Dollars = Double
protocol Account {
associatedtype Currency
var balance: Currency { get }
func deposit(amount: Currency)
func withdraw(amount: Currency)
}
class BasicAccount: Account{
private(set) var balance: Dollars = 0.0
func deposit(amount: Double) {
balance+=amount
}
func withdraw(amount: Double) {
if balance>amount {
balance -= amount
}else{
balance = 0
}
}
}
class CheckingAccount: BasicAccount{
private let accountNumber = UUID().uuidString
class Check {
let account: String
var amount: Dollars
private(set) var cashed:Bool = false
func cash() {
self.cashed = true
}
init(account: CheckingAccount, amount: Dollars) {
self.account = account.accountNumber
self.amount = amount
}
}
func writeCheck(amount: Dollars) -> Check? {
guard amount<=balance else {
return nil
}
let check = Check(account: self, amount: amount)
withdraw(amount: check.amount)
return check
}
func deposit(_ check: Check) {
guard !check.cashed else {
return
}
deposit(amount: check.amount)
check.cash()
}
}
// Create a checking account for John. Deposit $300.00
let johnChecking = CheckingAccount()
johnChecking.deposit(amount: 300.00)
// Write a check for $200.00
let check = johnChecking.writeCheck(amount: 200.0)!
// Create a checking account for Jane, and deposit the check.
let janeChecking = CheckingAccount()
janeChecking.deposit(check)
janeChecking.balance // 200.00
// Try to cash the check again. Of course, it had no effect on
// Jane’s balance this time :]
janeChecking.deposit(check)
janeChecking.balance // 200.00

Here accountNumber is accessible by Check Class and you can not set cashed property.

File Private

The above example anyone can create Check object but it will be great if only CheckingAccount object can make Check object. So here we can transfer our CheckingAccount class into a file and make Check initial method as file private. Let’s see.

fileprivate init(amount: Dollars, from account: CheckingAccount)
{ //...
}

Internal

The internal access level means that an entity can be accessed from anywhere within the software module in which it’s defined.

Public

To make CheckingAccount visible to your playground, you’ll need to change the access level from internal to public. An entity that is public can be seen and used by code outside the module in which it’s defined.

Add the public modifier to class CheckingAccount:

public class CheckingAccount: BasicAccount {

You’ll also need to add public to BasicAccount since CheckingAccount subclasses it:

public class BasicAccount: Account

The playground will now recognize CheckingAccount, yet you’re still not able to instantiate it.

While the type itself is now public, its members are still internal and thus unavailable outside of the module. You’ll need to add public modifiers to all the entities you want to be part of your module’s interface.

Start by adding a public initializer to BasicAccount and CheckingAccount:

// In BasicAccount:
public init() { }
// In CheckingAccount:
public override init() { }

Next, in BasicAccount, add public to balance, deposit(amount:) and withdraw(amount:). You’ll also need to make the Dollars typealias public, as this typealias is now used in public methods.

Finally, in CheckingAccount, add public to writeCheck(amount:), deposit(_:) and class Check. Save all files. You’ll find that everything builds and runs!

Note: Even though BasicAccount adopts Account, you may notice that the playground can’t see Account, nor does it know that BasicAccount conforms to it. Protocol conformance will be invisible to consuming modules if the protocol itself is not accessible.

N.B: I don’t attached code here. It will be long. Please visit Swift apprentice book page 345–346 for more details.

Open

The object are visible between different module when we use public but we can not inherit them, override them. To override or inherit we have to make those classes open with their related methods.

available()

It is common for an iOS developer to see that Xcode warn you to not use a method or properties as it is deprecated and they may introduce alternative method or properties for that. So how they able to notify you? Let’s see how they can do it.

Let’s assume we provide an api to our developers and they use our api to build their apps. In our codebase we have a class something like that.

class Test{private var version: String
private var second: String = ""
init(first: String) {
self.version = first
}
func printTest() {
print("I am version \(version)")
}
}

It works fine. User can create Test object and use them like below.

let obj = Test(first: "1")
obj.printTest()

So some developer use those methods and build their apps and publish it.

As, we have a great team and we love to improve ourself every time. So we start working on our second version codebase and we notice that it will be great if we get both version and second property initialize when user construct Test class object. So we remove our previous init with new init something like that.

init(first: String, second: String) {
self.version = first
self.second = second
}

And we publish our version . Now, the new developer who just use our api from version 2 they may get it easy but what about the previous developers who already used our api in their project and now they update their code base with our new published version but they saw their project broke badly. That’s where available comes in.

Instead of replace the previous init we should add new init besides old init and say our user that our old init deprecated so use the new one instead. Something like below.

class Test{private var version: String
private var second: String = ""
@available(*, deprecated, message:" Use init(firast: String, second: String)")
init(first: String) {
self.version = first
}
init(first: String, second: String) {
self.version = first
self.second = second
}
func printTest() {
print("I am version \(version)")
}
}

The asterisk in the parameters denotes which platforms are affected by this deprecation. It accepts the values *, iOS, iOSMac, tvOS or watchOS. The second parameter details whether this method is deprecated, renamed or unavailable. The 3rd one is the message.

now our user will get the warning like the upper attached image.

Custom Operator, subscripts and keypaths

Custom Operator

3 types of operator

  1. Unary
  2. Binary
  3. Ternary

let’s create our own define operator, which calculate exponent for all types of integers.

infix operator **
infix operator **=
func **<T: BinaryInteger>(base: T, power: Int)-> T{
precondition(power>=2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
func **=<T: BinaryInteger>(lhs: inout T, rhs: Int){
lhs = lhs**rhs
}
var base:UInt8 = 2
let power = 3
print(base ** power) //8

Precedence and associativity

2 * 2 ** 3 ** 2 // Does not compile!

Not compile because compiler can not understand which operator have the high priority and as there is 2 exponential operator so which way compiler should execute. Left to right or right to left.

We can implement precedent and associativity with our custom operator. So, let’s do that.

precedencegroup ExponentiationPrecedence{
associativity: right
higherThan
: MultiplicationPrecedence
}
infix operator **: ExponentiationPrecedence
infix operator **=
func **<T: BinaryInteger>(base: T, power: Int)-> T{
precondition(power>=2)
var result = base
for _ in 2...power {
result *= base
}
return result
}
func **=<T: BinaryInteger>(lhs: inout T, rhs: Int){
lhs = lhs**rhs
}
print(2 * 2 ** 3 ** 2)

Subscript

subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set(newValue) {
// set someValue of ReturnType to newValue
}
}

As you can see, subscripts behave like functions and computed properties

  • The subscript’s prototype looks like a function’s signature: It has a parameter list and a return type, but instead of the func keyword and the function’s name, you use the subscript keyword. Subscripts may have variadic parameters but can’t use inout or default parameters nor can they throw errors. You’ll learn more about errors in Chapter 21, “Error Handling”.
  • The subscript’s body looks like a computed property: it has both a getter and a setter. The setter is optional, so the subscript can be either read-write or read-only. You can omit the setter’s newValue default parameter; its type is the same as the subscript’s return type. Only declare it if you want to change its name to something else.
class Person{
let name: String
let age: Int
init(_ name: String, _ age: Int) {
self.name = name
self.age = age
}
subscript(key: String)-> String?{
switch key{
case "name":
return name
case "age":
return "\(age)"
default:
return nil
}
}
}
let rx = Person("rx", 28)print(rx["name"]!)

Static subscripts

class File {
let name: String
init(name: String) {
self.name = name
}
static subscript(key: String) -> String {
switch key {
case "path":
return "custom path"
default:
return "default path"
}
}
}
File["path"]
File["PATHS"]

Dynamic member lookup

You use dynamic member lookup to provide arbitrary dot syntax to your type.

@dynamicMemberLookup
class
Person{
let name: String
let age: Int
init(_ name: String, _ age: Int) {
self.name = name
self.age = age
}
subscript(dynamicMember key: String)-> String?{
switch key{
case "name":
return name
case "age":
return "\(age)"
default:
return nil
}
}
}
let rx = Person("rx", 28)
print(rx.age)

Keypaths

Keypaths enable you to store references to properties. For example, this is how you model the tutorials on our website:

class Tutorial {
let title: String
let authorName: String
let details: (type: String, category: String)
init(title: String, author: String, details: (type: String, category: String)) {
self.title = title
self.authorName = author
self.details = details
}
}
let tutorial = Tutorial(title: "Object Oriented Programming in Swift", author: "me", details: (type: "Swift", category: "iOS"))let title = \Tutorial.title
let tutorialTitle = tutorial[keyPath: title]

Setting properties

Keypaths can change property values. Suppose you set up your very own jukebox to play your favorite song:

class Jukebox {
var song: String
init(song: String) {
self.song = song
}
}
let jukebox = Jukebox(song: "Nothing Else Matters")

You declare the song property as a variable because your best friend comes to visit and wants to listen to her favourite song instead:

let song = \Jukebox.song
jukebox[keyPath: song] = "Stairway to Heaven"

--

--

Rejaul Hasan

I work as a Sr. software engineer for iOS platform. Available to discuss about any good opportunities or projects.