Reputation: 1475
I'm trying to figure out something with the new Apple Swift language. Let's say I used to do something like the following in Objective-C. I have readonly
properties, and they cannot be individually changed. However, using a specific method, the properties are changed in a logical way.
I take the following example, a very simple clock. I would write this in Objective-C.
@interface Clock : NSObject
@property (readonly) NSUInteger hours;
@property (readonly) NSUInteger minutes;
@property (readonly) NSUInteger seconds;
- (void)incrementSeconds;
@end
@implementation Clock
- (void)incrementSeconds {
_seconds++;
if (_seconds == 60) {
_seconds = 0;
_minutes++;
if (_minutes == 60) {
_minutes = 0;
_hours++;
}
}
}
@end
For a specific purpose, we cannot touch the seconds, minutes and hours directly, and it's only allowed to increment second by second using a method. Only this method could change the values by using the trick of the instance variables.
Since there are no such things in Swift, I'm trying to find an equivalent. If I do this:
class Clock : NSObject {
var hours: UInt = 0
var minutes: UInt = 0
var seconds: UInt = 0
func incrementSeconds() {
self.seconds++
if self.seconds == 60 {
self.seconds = 0
self.minutes++
if self.minutes == 60 {
self.minutes = 0
self.hours++
}
}
}
}
That would work, but anybody could change directly the properties.
Maybe I already had a bad design in Objective-C and that's why the potential new Swift equivalent is not making sense. If not and if someone have an answer, I would be very grateful ;)
Maybe the future Access Control Mechanisms promised by Apple is the answer?
Thanks!
Upvotes: 132
Views: 64716
Reputation: 18864
Simply prefix the property declaration with private(set)
, like so:
public private(set) var hours: UInt = 0
public private(set) var minutes: UInt = 0
public private(set) var seconds: UInt = 0
private
keeps it local to a source file, while internal
keeps it local to the module/project.
private(set)
creates a read-only
property, while private
sets both, set
and get
to private.
Upvotes: 369
Reputation: 9437
There are two basic ways of doing what you want. First way is by having a private property and and a public computed property which returns that property:
public class Clock {
private var _hours = 0
public var hours: UInt {
return _hours
}
}
But this can be achieved in a different, shorter way, as stated in the section "Access Control" of the "The Swift Programming Language" book:
public class Clock {
public private(set) var hours = 0
}
As a side note, you MUST provide a public initialiser when declaring a public type. Even if you provide default values to all properties, init()
must be defined explicitly public:
public class Clock {
public private(set) var hours = 0
public init() {
hours = 0
}
// or simply `public init() {}`
}
This is also explained in the same section of the book, when talking about default initialisers.
Upvotes: 20
Reputation: 534885
Since there are no access controls (meaning that you can't make an access contract that differs depending on who the caller is), here's what I would do for now:
class Clock {
struct Counter {
var hours = 0;
var minutes = 0;
var seconds = 0;
mutating func inc () {
if ++seconds >= 60 {
seconds = 0
if ++minutes >= 60 {
minutes = 0
++hours
}
}
}
}
var counter = Counter()
var hours : Int { return counter.hours }
var minutes : Int { return counter.minutes }
var seconds : Int { return counter.seconds }
func incrementTime () { self.counter.inc() }
}
This merely adds a level of indirection, as it were, to direct access to the counter; another class can make a Clock and then access its counter directly. But the idea — i.e., the contract we're trying to make — is that another class should use the Clock's top-level properties and methods only. We can't enforce that contract, but actually it was pretty much impossible to enforce in Objective-C too.
Upvotes: 5
Reputation: 5316
Actually access control (which does not exist yet in Swift) is not as enforced as you may think in Objective C. People can modify your readonly variables directly, if they really want to. They just do not do it with the public interface of the class.
You can do something similar in Swift (cut&paste of your code, plus some modifications, I did not test it):
class Clock : NSObject {
var _hours: UInt = 0
var _minutes: UInt = 0
var _seconds: UInt = 0
var hours: UInt {
get {
return _hours
}
}
var minutes: UInt {
get {
return _minutes
}
}
var seconds: UInt {
get {
return _seconds
}
}
func incrementSeconds() {
self._seconds++
if self._seconds == 60 {
self._seconds = 0
self._minutes++
if self._minutes == 60 {
self._minutes = 0
self._hours++
}
}
}
}
which is the same as what you have in Objective C except that the actual stored properties are visible in the public interface.
In swift you can also do something more interesting, which you can also do in Objective C, but it's probably prettier in swift (edited in the browser, I did not test it):
class Clock : NSObject {
var hours: UInt = 0
var minutes: UInt {
didSet {
hours += minutes / 60
minutes %= 60
}
}
var seconds: UInt {
didSet {
minutes += seconds / 60
seconds %= 60
}
}
// you do not really need this any more
func incrementSeconds() {
seconds++
}
}
Upvotes: 2