In Swift we have five types of access-level modifier
: open
, public
, internal
, fileprivate
and private
. An internal declared property means that it can only be used within the module for which it is declared. In Swift, by default, all properties are internal, but things will get interesting when we split the code into multiple modules.
In the framework of this article we will see how to access data structures inside the framework classes, while still keeping the internal properties hidden.
Problem
Most current applications store data locally, we have a lot of options such as: Core Data, Realm, SQLite, … And no matter which way you choose it, when the project grows bigger. above 10k, 50k, 100k lines of code then surely we will have to start thinking of dividing them into modules.
Suppose we have a module divided from the main target of the project that contains the code related to manipulating the database. Let’s call it PersistenceKit – following Apple’s naming convention. We can implement this module as a dynamic framework
or static framework
our choice.
Let’s assume that PersistenceKit will contain many repositories
, such as: ArticleRepository, UserRepository, etc. that we will use to fetch and store data. A repository can be implemented as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public struct Article { public let id: ArticleID public let title: String public let content: String } public class ArticleRepository { public func article(for id: ArticleID) -> Article? { // finds a row in the database and maps it to a struct // // missing implementation } // other methods... } |
In the implementation of Repository above, to be able to manipulate the database, we need to use some objects related to the database used in the project, such as:
- Core Data: NSManagedObjectContext
- Realm: Realm object
- SQLite in C: sqlite3 pointer
- GRDB.swift: DatabasePool
In order to try to become good and thoughtful programmers, we should:
- Avoid using singleton or global variables to reference the above objects.
- Do not allow the users of the PersistenceKit module to know how it implements, ie no one knows that we are using
NSManagedObjectContext
orDatabasePool
or whatever.
Solution
I recently thought about these two things and tried to find a solution that would satisfy me. It is based on a combination of public
and internal
modifiers. Suppose we declare a struct:
1 2 3 4 | public struct Connection { let pool: DatabasePool } |
This way the Connection property is accessible from outside PersistenceKit
, and the pool
property is not. It is not even possible to initialize this struct outside PersistenceKit
because the memberwise initializer
in this case is internal
.
And because the PersistenceKit
module users won’t be able to initiate the Connection
, we need to give them an instance, like this:
1 2 3 4 5 6 | public struct AppDatabase { public func setup(with path: URL) throws -> Connection { // performs the setup and returns a connection instance } } |
The next thing is to pass the instance of the above Connection
to ArticleRepository
, as follows:
1 2 3 4 5 6 7 8 9 | public class ArticleRepository { public func article(for id: ArticleID, connection: Connection) -> Article? { // we can access `pool` property here because it’s accessible in this module return connection.pool.read { (db) -> Article? in return Article.fetchOne(db, key: id) } } } |
Thus, from now on we can set up PersistenceKit as follows:
1 2 3 4 5 6 7 8 9 10 | class AppCoordinator { let connection: PersistenceKit.Connection ... init() throws { let database = AppDatabase() connection = try database.setup(with: path) } } |
And whenever we want to fetch data, we just need to pass the connection
into the ArticleRepository
method:
1 2 3 | let repository = ArticleRepository() let article = repository.article(for: id, connection: connection) |
By the way, the framework user will not be able to call Connection.pool directly, because it is not accessible outside PersistenceKit
. This way we can ensure that others cannot access the components within PersistenceKit
, thereby ensuring cleaner overall architecture .
Summary
The public types that come with internal properties are a very powerful tool, while our framework can still allow users to get what they want, but make sure they don’t know what we’re doing. inside the framework.
Are you using any other approach with public types and internal properties, please let me know by commenting below the article!