Why are extensions used so heavily?

Extensions can extend classes, structs, enums, as well as protocols.

Interesting question. I will bring an example to understand which is the better approach to follow. Do you guys think the following Code is more readable (something more readable to me means it's either more organized, written without too much complexity etc..)

import Foundation

class CustomerListViewModel {
    
    // MARK: - Public Vars
    var customerHasBeenSelected: ((Customer) -> Void)?
    var loadingDataHasStarted: (() -> Void)?
    var loadingDataHasEnded: (() -> Void)?
    var loadingDataHasFailed: ((Error) -> Void)?
    var reload: (() -> Void)?
    
    // MARK: - Private Vars
    private var customers: [Customer] = []
    private let customerService: CustomerService
    
    // MARK: - Init
    init(customerService: CustomerService) {
        self.customerService = customerService
    }
    
}

// MARK: - Input Methods
extension CustomerListViewModel {
    
    func customerSelected(at index: Int) {
        let customerSelected = customers[index]
        customerHasBeenSelected?(customerSelected)
    }
    
}

// MARK: - Support Methods
extension CustomerListViewModel {
    
    func numberOfCustomers() -> Int {
        return customers.count
    }
    
    func makeCustomerTableViewCellViewModel(at index: Int) -> CustomerTableViewCellViewModel {
        let customer = customers[index]
        return CustomerTableViewCellViewModel(customer: customer)
    }
    
}

// MARK: - Services
extension CustomerListViewModel {
    
    func loadCustomers() {
        loadingDataHasStarted?()
        customerService.customers { [weak self] response in
            self?.loadingDataHasEnded?()
            switch response {
            case .failure(let error):
                self?.loadingDataHasFailed?(error)
            case .success(let customers):
                self?.customers = customers
            }
            self?.reload?()
        }
    }
    
}

I think the // MARK: delineations already serve the purpose of splitting the file into categories, the extensions themselves do not add much.

Two recurring patterns I see in use of extensions in the same file as the extended object are:
a) Conformances to protocols. This means they can be easily extracted into files or types of their own.
b) A main declaration that only contains stored properties and possibly initialisers, with methods being defined in an additional extension. This ensures that the methods do not add extra stored properties and thereby remain reasonably "pure".

2 Likes

not everyone uses xcode :slight_smile:

and moreover extensions can do a lot of things MARKs can't. you can add @available to an extension. you can use them to gate APIs en masse by generic constraint. today you can even index them with SymbolGraphGen, which means in theory you could attach documentation to them.

4 Likes

Good point, though I hope other editors support MARK too.

I wonder why you say so. A trivial example showing both extension (category) and retroactive conformance in Objective-C:

#import <UIKit/UIKit.h>

@interface MyView: UIView
@end

@implementation MyView
@end

// MARK: example category
@implementation MyView (SomeCategory)
- (void)foo { printf("Hello\n"); }
@end

// MARK: example category conforming to a protocol
@interface MyView (Datasource) <UITableViewDataSource>
@end

@implementation MyView (Datasource)
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 10;
}
...
@end

Just a few more characters to type compared to Swift, but that's the case for pretty much everything in Obj-C compared to Swift.

That's not a retroactive conformance, just a conformance of your type to a protocol in a category. That wasn't super common, but was used to conform large types to protocols. I don't recall if Obj-C supported fully retroactive conformance (a type you don't own conforming to a protocol you don't own) but I largely meant it wasn't popular. Whether that was due to the language itself or just the community I'm not sure.

That's possible as well:

// MARK: example category conforming to an existing protocol
@interface UIView (Datasource) <UITableViewDataSource>
@end

@implementation UIView (Datasource)
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 10;
}
@end

@protocol MyProto
- (void)bar;
@end

// MARK: example category conforming to a custom protocol
@interface UIView (MyProto) <MyProto>
@end

@implementation UIView (MyProto)
- (void)bar {}
@end

IIRC protocols and categories themselves were relatively new addition to Objective-C 10 years ago (I remember I had to lobby using categories back then in the project I was working at the time).

I remember loving protocols and categories when I learned Objective C more than 20 years ago — and they were not recent additions even back then.

My guess is that if you had to lobby for them 10 years ago, it may have been because of the flood of new Objective C programmers after the introduction of iOS, who were not used to categories and most likely tended to use them a lot less than MacOS app developers did before that (and NeXTStep devs before them).

2 Likes

You are right. IIRC the main objection was along the lines of usage of the -ObjC flag in the library, for some reason the guys were afraid of using it (the fear that client apps would be reluctant adding that). Indeed that was for iOS.