I have a question regarding object oriented design principles and Swift. I am pretty familiar with Java and I am currently taking a udacity course to get a first hands on in Swift.
In the Java community (basically in every community that follows OOP) it is very common to use information hiding techniques such as hiding or encapsulating data within classes to make sure it cannot be manipulated from outside. A common principle is to declare all attributes of a class as being private and use getters for retrieving an attribute's value and setters for manipulation.
I tried to follow this approach when writing a class that was part of the course and it looks like this:
//
// RecordedAudio.swift
// Pitch Perfect
//
import Foundation
class RecordedAudio: NSObject {
private let filePathUrl: NSURL!
private let title: String?
init(filePathUrl: NSURL, title: String?)
{
self.filePathUrl = filePathUrl
self.title = title
}
func getFilePathUrl() -> NSURL
{
return filePathUrl
}
func getTitle() -> String
{
if title != nil
{
return title!
}
else
{
return "none"
}
}
}
The code works and my private attributes cannot be accessed from outside my class, which is exactly the behavior I wanted to achieve. However, the following questions came to my mind:
1.) The course instructor decided to leave the attributes' access control level at the default "internal" and not use getters/setters but rather access the attributes directly from outside. Any thoughts on why developers might do that in swift? Is my approach not "swift" enough???
2.) In conclusion: Is there a "swifter" way to implement encapsulation when writing your own class? What are swift's native techniques to achieve the information hiding I am aiming for?
You can restrict external property manipulation, by marking the property public for reading and private for writing, as described in the documentation:
class RecordedAudio: NSObject {
public private(set) let filePathUrl: NSURL!
public private(set) let title: String?
init(filePathUrl: NSURL, title: String?) {
self.filePathUrl = filePathUrl
self.title = title
}
}
// in another file
let audio = RecordedAudio(filePathUrl: myUrl, title: myTitle)
let url = audio.filePathUrl // works, returns the url
audio.filePathUrl = newUrl // doesn't compile
I do it a bit like in Obj-C:
class MyClass
private var _something:Int
var something:Int {
get {return _something}
// optional: set { _something = newValue }
}
init() { _something = 99 }
}
...
let c = MyClass()
let v = c.something
Above is a primitive example, but handled stringent it works as a good pattern.
Related
So, I have an enum called Level. That enum is actually just a wrapper for some other Level. Now I need to access that wrapped value (currently a protected property) in a different class called Log which sits in the same package. Obviously I do not want to completely expose that property by making it internal or public, but I need to access that wrapped value in my Log class.
How to I do that?
As Kotlin doesn't provide anything similar to package-private visibility, everything I tried failed. I'm already aware of the possibility to put both classes in one file, but that only allows me to gain exclusive access to the classes themselves, not their properties. And because I need to have both classes public that won't help either. So if anyone knows a workaround, I would be more than happy to hear it, because even though I really like Kotlin, this might be the reason for me to drop the language.
Both classes I mentioned look as follows:
Level.kt
enum class Level(protected val level: java.util.logging.Level) {
/** Useful for stuff */
OFF(CustomLevel("OFF", Int.MAX_VALUE)),
ASSERT(CustomLevel("ASSERT", 1200)),
FATAL(CustomLevel("FATAL", 1100)),
ERROR(CustomLevel("ERROR", 1000)),
WARN(CustomLevel("WARN", 900)),
INFO(CustomLevel("INFO", 800)),
DEBUG(CustomLevel("DEBUG", 700)),
ALL(CustomLevel("ALL", Int.MIN_VALUE));
private class CustomLevel(name: String, value: Int) : java.util.logging.Level(name, value)
}
Log.kt
object Log {
private val DEFAULT_CONSOLE_VERBOSITY = Level.ERROR
private val DEFAULT_FILE_VERBOSITY = Level.ALL
#JvmStatic
var consoleVerbosity: Level
get() = Level.findLevel(consoleHandler.level)
set(value) {
consoleHandler.level = value.level // The property I need to access
}
#JvmStatic
var fileVerbosity: Level
get() = Level.findLevel(fileHandler.level)
set(value) {
fileHandler.level = value.level // The property I need to access
}
private val consoleHandler = ConsoleHandler()
init {
consoleHandler.formatter = SimpleFormatter()
consoleHandler.level = DEFAULT_CONSOLE_VERBOSITY.level
}
private val fileHandler = FileHandler()
init {
fileHandler.formatter = SimpleFormatter()
fileHandler.level = DEFAULT_FILE_VERBOSITY.level
}
}
I am running the latest stable version of Kotlin (1.4.31)
As a workaround you may define an extension function/property for Log class in the scope of Level class:
enum class Level(private val level: java.util.logging.Level) {
//...
//Option 1
companion object {
fun Log.getLevelOf(level: Level) = level.level
}
//Option 2
val Log._level get() = level
}
Also you may define extension property for Level class in the scope of Log class for more natural usage:
object Log {
//...
private val Level.level : java.util.logging.Level
get() = consoleHandler.level = Level.run { getLevelOf(this#level) } // For Option 1
get() = with(this) { _level } // For Option 2
}
Downside of this approach is a tough coupling between these classes.
You just have to use extension functions like this:
fun Level.toLevel() = this.level
That allows you to access protected properties of other classes.
You cannot access a private class from another class but you can access a class from a class that is packed inside a file. So the workaround is to make fun in public class to access the private class which is in the same file.
But the highlight is that you cannot write a class inside an enum class in Kotlin.
I still don't know how you managed to write this code down in an IDE, because it will show an error.
I use kotlinx.serialization for my models.
I'd like the idea of them to not depend on JavaFX, so they do not expose properties.
Given a model, I want a tableview for a quick representation of a list of instances, and additionally a more detailed Fragment as an editor.
consider the following model:
#Serializable
data class Person(
var name: String,
var firstname: String,
var complex: Stuff)
the view containing the tableview contains
private val personlist = mutableListOf<Person>().observable()
with a tableview that opens an instance of PersonEditor for the selected row when Enter is pressed:
tableview(personlist) {
column("name", Person::name)
column("first name", Person::firstname)
setOnKeyPressed { ev ->
selectedItem?.apply {
when (ev.code) {
KeyCode.ENTER -> PersonEditor(this).openModal()
}
}
}
}
I followed this gitbook section (but do not want the modelview to be rebound on selection of another row within the tableview)
The editor looks about like this:
class PersonEditor(person: Person) : ItemFragment<Person>() {
val model: Model = Model()
override val root = form {
fieldset("Personal information") {
field("Name") {
textfield(model.name)
}
field("Vorname") {
textfield(model.firstname)
}
}
fieldset("complex stuff") {
//... more complex stuff here
}
fieldset {
button("Save") {
enableWhen(model.dirty)
action { model.commit() }
}
button("Reset") { action { model.rollback() } }
}
}
class Model : ItemViewModel<Person>() {
val name = bind(Person::name)
val firstname = bind(Person::firstname)
//... complex stuff
}
init {
itemProperty.value = mieter
model.bindTo(this)
}
}
When I save the edited values in the detail view, the tableview is not updated.
Whats the best practize to solve this?
Also I'm unsure, if what I'm doing can be considered good practize, so i'd be happy for some advice on that too.
The best practice in a JavaFX application is to use observable properties. Not doing so is an uphill battle. You can keep your lean domain objects, but add a JavaFX/TornadoFX specific version with observable properties. This object can know how to copy data to/from your "lean" domain objects.
With this approach, especially in combination with ItemViewModel wrappers will make sure that your data is always updated.
The setOnKeyPressed code you posted can be changed to:
setOnUserSelect {
PersonEditor(it).openModal()
}
Notice though, that you are not supposed to instantiate Views and Fragments directly, as doing so skips certain steps in the TornadoFX life cycle. Instead you should pass the person as a parameter, or create a new scope and inject a PersonModel into that scope before opening the editor in that scope:
setOnUserSelect {
find<PersonEditor>(Scope(PersonEditor(it)))
}
I have some experience in ObjC and I just started learning Swift. In Objc everything is a class through #interface in .h and #implementation of .m, or in other Swift classes that I have seen everything is usually in some form of
class MyCustomClassInhertingfrom: SomeFoundationClass { //methods & properties}
Yet here in some class named pancakeHouse.Swift there is no mention of the keyword class WHY? Isn't this a Model Class? Doesn't this break the MVC design pattern? Is this happening because of new powerful features of enums& structs vs class in Swift?_____I am confused obviously!
import UIKit
import CoreLocation
enum PriceGuide : Int {
case Unknown = 0
case Low = 1
case Medium = 2
case High = 3
}
extension PriceGuide : CustomStringConvertible {
var description : String {
switch self {
case .Unknown:
return "?"
case .Low:
return "$"
case .Medium:
return "$$"
case .High:
return "$$$"
}
}
}
enum PancakeRating {
case Unknown
case Rating(Int)
}
extension PancakeRating {
init?(value: Int) {
if value > 0 && value <= 5 {
self = .Rating(value)
} else {
self = .Unknown
}
}
}
extension PancakeRating {
var ratingImage : UIImage? {
guard let baseName = ratingImageName else {
return nil
}
return UIImage(named: baseName)
}
var smallRatingImage : UIImage? {
guard let baseName = ratingImageName else {
return nil
}
return UIImage(named: "\(baseName)_small")
}
private var ratingImageName : String? {
switch self {
case .Unknown:
return nil
case .Rating(let value):
return "pancake_rate_\(value)"
}
}
}
struct PancakeHouse {
let name: String
let photo: UIImage?
let thumbnail: UIImage?
let priceGuide: PriceGuide
let location: CLLocationCoordinate2D?
let details: String
let rating: PancakeRating
}
extension PancakeHouse {
init?(dict: [String : AnyObject]) {
guard let name = dict["name"] as? String,
let priceGuideRaw = dict["priceGuide"] as? Int,
let priceGuide = PriceGuide(rawValue: priceGuideRaw),
let details = dict["details"] as? String,
let ratingRaw = dict["rating"] as? Int,
let rating = PancakeRating(value: ratingRaw) else {
return nil
}
self.name = name
self.priceGuide = priceGuide
self.details = details
self.rating = rating
if let imageName = dict["imageName"] as? String where !imageName.isEmpty {
photo = UIImage(named: imageName)
} else {
photo = nil
}
if let thumbnailName = dict["thumbnailName"] as? String where !thumbnailName.isEmpty {
thumbnail = UIImage(named: thumbnailName)
} else {
thumbnail = nil
}
if let latitude = dict["latitude"] as? Double,
let longitude = dict["longitude"] as? Double {
location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
} else {
location = nil
}
}
}
extension PancakeHouse {
static func loadDefaultPancakeHouses() -> [PancakeHouse]? {
return self.loadPancakeHousesFromPlistNamed("pancake_houses")
}
static func loadPancakeHousesFromPlistNamed(plistName: String) -> [PancakeHouse]? {
guard let path = NSBundle.mainBundle().pathForResource(plistName, ofType: "plist"),
let array = NSArray(contentsOfFile: path) as? [[String : AnyObject]] else {
return nil
}
return array.map { PancakeHouse(dict: $0) }
.filter { $0 != nil }
.map { $0! }
}
}
extension PancakeHouse : CustomStringConvertible {
var description : String {
return "\(name) :: \(details)"
}
}
extension PancakeHouse: Equatable {
}
func ==(lhs: PancakeHouse, rhs: PancakeHouse) -> Bool {
return lhs.name == rhs.name
}
Note: I would appreciate an answer that also includes comparison of the .swift vs .h + .m ie don't just consider this as a specific question, consider it as a general question and explain or link the prerequisites's details needed to understand this question)
pancakeHouse.swift defines PancakeHouse and all the various things that go with it. That is perfectly good Swift style. There is no class here because PancakeHouse happens to be a struct, which is also perfectly good Swift style (and mildly preferred). Structs are much like classes in Swift, in that they can have data and methods (and extensions).
ObjC does not require that each class be defined in its own .h/.m pair, but it is fairly typical ObjC style to do so. That said, even in ObjC there are exceptions. It is common to have mutable subclasses defined in the same file as their base class (NSArray and NSMutableArray are both defined in NSArray.h). Swift style has evolved towards lumping related things together more closely. One style makes it easier to find something if you know its name. The other makes it easier to find related concepts together. Both have their advantages, and once people are used to one they tend to believe that that one is obviously correct. But they're just different ways of organizing.
Note that this file also makes use of Swift extensions to break up related methods. That is also common and good Swift. Old ObjC did that with categories, but it's much less common to organize ObjC code that way today (categories are used for other things now). Again, neither is deeply correct. It's just the styles that have evolved.
Swift files just need valid swift code. That's all. In this case, your swift file is just defining two enums. Generally it's better practice to put each enum (and its extension) in it's own .swift file, but that's really just personal opinion when you get right down to it.
A .m is the implementation of the .h interface. It's a pain in the !##$ to keep those two things always up to date. Swift makes this much easier by just merging them into one thing/file, and your entire project automatically sees it based on the access you've set. If you don't set any access, like the example above, then you have 'internal' which means the whole project.
There are two philosophies the compiler writers could take on this question:
Each public class needs a file with the matching name - this is Java's way of doing it. Although it is minimally restrictive, the logic behind it is to let the compiler find class references without looking through all the files. It also helps programmers organize their code.
Do it your own way - this is the road taken by Swift, along with C, C#, Objective-C. Essentially, compiler writers tell you that their compiler will find your classes no matter where you put them, letting you organize your code in a way that you find the most intuitive for you and your team.
MMCondition is a protocol defined in Swift, but interoperates with Objective-C (annotated with #objc).
#objc public protocol MMCondition {
static var name: String { get }
static var isMutuallyExclusive: Bool { get }
}
I have the following code:
// addCondition cannot be generic as I want it to be accessible from Objective-C as well.
public func addCondition(condition: MMCondition) {
// How do I initialize OperationConditionImplementer here?
let operationCondition = OperationConditionImplementer(condition: condition) // doesn't compile
// Error: Cannot invoke initializer for type 'OperationConditionImplementer<T>' with an argument list of type '(condition: MMCondition)'
// Can I use condition.dynamicType to init OperationConditionImplementer somehow?
}
struct OperationConditionImplementer<T: MMCondition> {
let condition: T
static var name: String {
return "Silent<\(T.name)>"
}
static var isMutuallyExclusive: Bool {
return T.isMutuallyExclusive
}
init(condition: T) {
self.condition = condition
}
}
From Objective-C, you can't use generics as stated in the documentation.
You’ll have access to anything within a class or protocol that’s
marked with the #objc attribute as long as it’s compatible with
Objective-C. This excludes Swift-only features such as those listed
here:
Generics
...
So you need to remove completely the generics code. One possible solution might be:
#objc protocol MMCondition {
static var name: String { get }
static var isMutuallyExclusive: Bool { get }
}
struct OperationConditionImplementer {
let condition: MMCondition
var name: String {
return "Silent<\(condition.dynamicType.name)>"
}
var isMutuallyExclusive: Bool {
return condition.dynamicType.isMutuallyExclusive
}
init(condition: MMCondition) {
self.condition = condition
// Here decide comparing types
if condition.dynamicType === ExampleCondition.self {
print(condition.dynamicType.name)
}
}
}
So for instance, if you try it out in a playground:
class ExampleCondition: NSObject, MMCondition {
static var name: String = "ExampleCondition"
static var isMutuallyExclusive: Bool = false
}
let example = OperationConditionImplementer(condition: ExampleCondition())
You'll see "ExampleCondition" printed.
If you eventually switch to pure Swift, you need to specify T when initializing a OperationConditionImplementer.
You can achieve that defining the addCondition method as:
func addCondition<T: MMCondition>(condition: T) {
let a = OperationConditionImplementer<T>(condition: condition)
}
Since Swift 2.0 instances of generic classes can implement Objective-C protocols. What won't be possible I believe is having a struct implement the protocol. In fact I expect that your protocol may need to inherit from NSObjectProtocol to be usable in Objective-C which would then prevent you from implementing the protocol with structs or enums.
You also rightly mention that you can't access generic functions from Objective-C.
For a concrete example of using a generic to fulfil an Objective-C protocol have a look at this blog post.
I am working on developing an application in Swift. I wanted to design a system for the application that allowed for loose coupling between objects, and one strategy (which I have used successfully in other languages) was to create something I call an instance factory. It is pretty simple and here is the basic implementation I came up with in Swift:
import Foundation
private var typeGenerators = Dictionary<String, InstanceFactory.GeneratorCallback>()
public class InstanceFactory: NSObject {
public typealias GeneratorCallback = () -> AnyObject!
public class func registerGeneratorFor(typeName: String, callback: GeneratorCallback) {
typeGenerators[typeName] = callback
}
public class func instanceOf(typeName: String) -> AnyObject! {
return typeGenerators[typeName]?()
}
}
The idea is that when an object instance needs access to another object instance, rather than creating that instance outright which would more tightly couple the two objects, the first object would defer to the factory to provide the needed instance by calling the instanceOf method. The factory would know how to provide various instance types because those types would register with the factory and provide a closure that could generate the instance.
The trick is how to get the classes to register with the factory. I had previously made a similar factory in Objective-C and the way I got registration to work was to override the +load method for each class that needed to register with the factory. This worked great for Objective-C, and I figured it could work for Swift as well since I would be restricting the factory to only provide objects that are derived from NSObject. It appeared I got this to work and I spent a significant about of effort designing classes to make use of the factory.
However, after upgrading to Xcode 6.3, I discovered Apple has disallowed the usage of the load class method in Swift. Without this, I am unaware of a mechanism to allow classes to automatically register themselves with the factory.
I am wondering if there some other way to get the registration to work.
What alternatives are available that could allow classes to register with the factory, or what other techniques could be use to accomplish the same kind of loose coupling the factory provides?
I've found a possible solution to your problem after I wanted to register all ViewControllers that would be implementing a certain Protocol in my application and I ran into both this question and a possible answer.
The original was posted here: How to list all classes conforming to protocol in Swift?
I adapted it to Swift 3 and made it a bit more Swift-y and generic:
import UIKit
class ContextRoute: NSObject {
}
#objc protocol ContextRoutable {
static var route: ContextRoute { get }
}
class ContextRouter: NSObject {
private static var storedRoutes: [ContextRoute]?
static var routes: [ContextRoute] {
get {
if let storedRoutes = storedRoutes {
return storedRoutes
}
let routables: [ContextRoutable.Type] = classes(implementing: ContextRoutable.self)
let newRoutes = routables.map { routable in routable.route }
storedRoutes = newRoutes
return newRoutes
}
}
private class func classes<T>(implementing objcProtocol: Protocol) -> [T] {
let classes = classList().flatMap { objcClass in objcClass as? T }
return classes
}
private class func classList() -> [AnyObject] {
let expectedClassCount = objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)
var classes = [AnyObject]()
for i in 0 ..< actualClassCount {
if let currentClass: AnyClass = allClasses[Int(i)],
class_conformsToProtocol(currentClass, ContextRoutable.self) {
classes.append(currentClass)
}
}
allClasses.deallocate(capacity: Int(expectedClassCount))
return classes
}
}
I tried it in my application and it works. I clocked it in the simulator and it takes 0.05s for an application that has about 12000 classes.
Consider taking the Swift approach using a protocol instead. I think the solution is actually simpler than the Objective-C approach. There are variations of this with Self constraints which are even better if you have more control over the classes.
// define a protocol to create an instance of a class
protocol FactoryInstantiable {
static func makeFactoryInstance() -> AnyObject
}
// Factory for generating new instances
public class InstanceFactory: NSObject {
public class func instanceOf(typeName: String) -> AnyObject! {
if let ProductType = NSClassFromString(typeName) as? FactoryInstantiable.Type {
return ProductType.makeFactoryInstance()
} else {
return nil
}
}
}
// your class which generally could be defined somewhere else
class MyClass {
var counter : Int
init(counter: Int) {
self.counter = 0
}
}
// extension of your class to conform to the FactoryInstantiable protocol
extension MyClass : FactoryInstantiable {
static func makeFactoryInstance() -> AnyObject {
return MyClass(counter: 0)
}
}