Trying (and failing) to implement the best conventions on OOP Swift I'm making a MacOS app that will do some analysis on the user's calendar data. For now it only supports Apple's native calendar (EventKit), but I will later add support to Google, outlook, etc.

Models/Events/Base.swift:

import Foundation

struct EventData {
    var title: String;
    var startDate: Date;
    var endDate: Date;
    var organizerName: String;
    var notes: String;
    var location: String;        
}

struct CalendarData {
    var title: String;
    var isSubscribed: Bool;
}

class Events {
    init() {
        checkForAuthorization();
    }

    func checkForAuthorization() {}
    func requestAccess() {}
    func getCalendars() -&gt; [CalendarData] {
        return [];
    }
    func getEvents(calendarName: String, from: Date, to: Date) -&gt; [EventData] {
        return [];
    }
}


Models/Events/iCal.swift:

import Foundation
import EventKit

class iCal: Events {

    let eventStore = EKEventStore();
    var calendars: [EKCalendar]?;

    override func checkForAuthorization() {
        let status = EKEventStore.authorizationStatus(for: EKEntityType.event);

        switch (status) {
            case EKAuthorizationStatus.notDetermined:
                self.requestAccess();
                break;
            case EKAuthorizationStatus.authorized:
                break;
            case EKAuthorizationStatus.restricted, EKAuthorizationStatus.denied:
                break;
         }
    }

    override func requestAccess() {
        eventStore.requestAccess(to: EKEntityType.event, completion:{ (accessGranted: Bool, error: Error?) in
            if accessGranted == true {
                print("Granted")
            } else {
                print("Denied")
            }
        });
    }

    override func getCalendars() -&gt; [CalendarData] {
        let _cals = self.eventStore.calendars(for: .event);
        var cals: [CalendarData] = [];

        for cal: EKCalendar in _cals {
            cals.append(CalendarData(title: cal.title, isSubscribed: cal.isSubscribed));
        }
        return cals;
    }
    override func getEvents(calendarName: String, from: Date, to: Date) -&gt; [EventData] {
        let cals = self.eventStore.calendars(for: .event);
        var events: [EventData] = [];

        if let calIndex: Int = cals.firstIndex(where: { $0.title == calendarName }) {
            let selectedCalendar: EKCalendar = cals[calIndex];

            let predicate = eventStore.predicateForEvents(withStart: from, end: to, calendars: [selectedCalendar])
            let _events = eventStore.events(matching: predicate) as [EKEvent];

            for ev: EKEvent in _events {
                events.append(
                    EventData(
                        title: ev.title,
                        startDate: ev.startDate,
                        endDate: ev.endDate,
                        organizerName: ev.organizer?.name ?? "",
                        notes: ev.notes ?? "",
                        location: ev.location ?? ""
                    )
                );
            }

        }

        return events;
    }
}


And I use this class like this:

let calendar = iCal();
for cal in calendar.getCalendars() {
   print("****** \(cal.title) *******\n");
   print(calendar.getEvents(calendarName: cal.title, from: Calendar.current.date(byAdding: .day, value: -2, to: Date())!, to: Calendar.current.date(byAdding: .day, value: 1, to: Date())!))
}


This works, but it feels very wrong... 
I know the semi-colons are not needed, but I always used it in other languages and the linting will delete them anyway after.

Questions:


How can I improve this?
Would it be better to instantiate the class
Events, by passing the calendar type as an argument (eg. iCal,
outlook, etc)
Are those structs ok? As in should they go in another
file?


Thank you
Please consider follow recommendations:

Make habit of extracting each struct/class/enum in separate files which brings more readability and clarity. Code looks less cluttered.

Please try getting used to of not using semicolons :)

You have defined Events as a class with empty implementations of functions. Are you sure you want to do that, I think it could be converted as a protocol instead
To be able to inject different type of calendar APIs, you should define your own protocol and make concrete implementations for each calendar (Adapter Pattern).

You must use dependency injection. For example even things like EKEventStore() should be injected.
final class iCal: Events {

    private let eventStore: EKEventStore

    init(with eventStore: EKEventStore = EKEventStore()) {
        self.eventStore = eventStore
    }
}


Using force unwrap is a bad practice, you should use guard let or if let to unwrap the optional value and only then use that unwrapped value for whatever purpose you want. By using forced unwrapping your app can crashes unexpectedly where it will find a nil value inside any optional variable.

Please make use of Swiftlint to make sure that your code is following standard convention. It will force you to think swift way :)

You should make class final when don't want to allow subclassing it (even if when you are not sure for now). If you do need subclassing later, you can remove final keyword.


All the best!