How does one create a 'static' in a Swift class extension? - swift

In several places here, it has been suggested that using a computed property within an extension of NSDate might a good way to obtain a string version of a date via a NSDateFormatter, like so:
extension NSDate {
public var UTC : String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter.stringFromDate(self)
}
}
However, allocating a NSDateFormatter is expensive and it is suggested that they be created once and cached. The above code creates the NSDateFormatter every time a date is formatted, and I'm wondering if there is a way to create the NSDateFormatter once inside the extension for reuse?
Obviously, I could create it just once outside the extension, but that seems to defeat the encapsulation that characterizes classes.
I am reminded of: https://xkcd.com/1179/ !!

You can add static members to class extensions just the same as on classes. You need to prefix the class name to the static member name when you use it, e.g. NSDate.dateFormatterUTC, even if you’re using it in the same class.
This works:
extension NSDate {
private static let dateFormatterUTC: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter
}()
public var UTC : String {
return NSDate.dateFormatterUTC.stringFromDate(self)
}
}
It’s also not the worst thing in the world just to use a private constant:
private let dateFormatterUTC: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = NSTimeZone(abbreviation: "UTC")
return formatter
}()
extension NSDate {
public var UTC : String {
return dateFormatterUTC.stringFromDate(self)
}
}
This is not significantly worse than the static class member, because Swift’s private is file-private, not type-private. These two declarations of dateFormatterUTC have the same scope. Even in the first example, NSDate.dateFormatterUTC is accessible throughout the entire file it’s declared in.
I do agree that the static version is preferable, but for stylistic reasons only: I like the way it’s indented right next to the thing that uses it.
As Gwendal wisely notes above, this approach assumes UTC will only ever be called from one thread. Although static let and global let are both thread-safe in Swift, the NSDateFormatter class is not! Looks like it’s threadsafe starting in iOS 7. Phew.
Still, always good to keep a thread safety warning next to any mention of singletons. If you do want to use a non-threadsafe helper object from multiple threads, consider either creating a new helper on every call, or using NSThread.currentThread().threadDictionary to create a per-thread instance. Be sure to do a little profiling to make sure you’re actually solving a performance problem before opting for the more complex thread-local option.

Related

Migrating project from swift3 to swift4 not able to override init

I am trying to override init in my DatabaseHelper but somehow. I am getting an error Property 'self.dateFormatter' not initialized at super.init call I am not getting what i have done wrong here.
public override init() {
super.init() //I am getting error here
dateFormatter.dateFormat = "yyyyMMdd HHmmss";
do {
db = try Connection(dbPath);
try db!.run(FormTbl.create(temporary: false, ifNotExists: true) { t in
t.column(self.ColumnId, primaryKey:true);
t.column(self.FileName);
t.column(self.ReceivedDate);
t.column(self.SentDate);
t.column(self.Status);
t.column(self.AwaitingStatus);
});
try db!.run(SettingTbl.create(temporary: false, ifNotExists:true) {t in
t.column(self.ColumnId, primaryKey:true);
t.column(self.SettingName, unique: true);
t.column(self.SettingValue);
});
try db!.run(PhotoTbl.create(temporary: false, ifNotExists:true) {t in
t.column(self.ColumnId, primaryKey:true);
t.column(self.PhotoFormId);
t.column(self.FileName);
t.column(self.PhotoSentFileName);
t.column(self.ReceivedDate);
t.column(self.SentDate);
t.column(self.Status);
});
try db!.run(GpsTbl.create(temporary: false, ifNotExists:true) {t in
t.column(self.ColumnId, primaryKey: true);
t.column(self.GpsFormId);
t.column(self.Longitude);
t.column(self.Latitude);
t.column(self.Altitude);
t.column(self.DateTime);
});
} catch {
}
}
NOTE: I was trying to migrate my project from swift3 to swift4.2
my full source code is here : full class url
I am really stuck here from a day! Can any body please help me what I am doing wrong here !! Any suggestion will be a great help! Thanks in advance
The code doesn't even compile in Swift 3.
As dateFormatter is a declared but not initialized property in the class you have to initialize it before calling super for example
public override init() {
dateFormatter = DateFormatter()
super.init()
dateFormatter.dateFormat = "yyyyMMdd HHmmss"
and remove the exclamation mark, dateFormatter is supposed to be non-optional
private let dateFormatter : DateFormatter
Apart from that there are three bad practices in your code.
This is Swift. No trailing semicolons
Please conform to the naming convention, this line is highly confusing
public var DateFormatter : DateFormatter {
All structs, classes and enums start with an uppercase letter
All variables, functions and enum cases start with a lowercase letter.
The creation of the shared instance is objective-c-ish and outdated since Swift 2
You're getting that error because your dateFormatter variable isn't initialized in your class. You have to initialize all your variables before you finish your init initializer, or otherwise the compiler will complain for security reasons.
In your code, you're declaring the following:
private let dateFormatter : DateFormatter!
But that is just a constant that holds no value. Isn't pointing to a DateFormatter class. You haven't initialized it. In order to initialize it, you have to assign a DateFormatter() to it, for example, in that exact same line:
private let dateFormatter = DateFormatter()
You should be able to initialize it in your init method too, by adding dateFormatter = DateFormatter() on it, but you'll need to replace your let with a var.

Swift Override Default Init No Subclass

Is it possible to override a default init without subclassing?
I want every instance of DateFormatter created in my app to have the same locale attached to it. Right now, upon each instance creation, I have to:
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX"
Is it possible to have this happen without the need to write out the locale every single time? I'd prefer to not create a DateFormatter subclass as I feel that's a bit of overkill
Thanks!
There is no way to override an existing method of a class without subclassing it or modifying the source code (which you obviously can't do in case of built-in types).
However, for your specific case, you could simply add a method/static variable that returns a DateFormatter specific to your needs.
extension DateFormatter {
static func usPosix() -> DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter
}
}
And then instead of calling let dateFormatter = DateFormatter(), you do let dateFormatter = DateFormatter.usPosix()
While I personally HIGHLY advise against swizzling, that is certainly an option here.
First, start by creating your own locale that you want to be used throughout your app:
extension DateFormatter {
// Note the "#objc" here, this is necessary for altering the runtime logic
#objc var swizzledLocale: Locale {
return Locale(identifier: "en_US_POSIX")
}
}
Next, you'll want to tell Swift that whenever a DateFormatter's locale is accessed, you want your new swizzledLocale to be used instead:
// get reference to the two getter methods for the properties you want to "switch"
let orig = #selector(getter: DateFormatter.locale)
let new = #selector(getter: DateFormatter.swizzledLocale)
let origMethod = class_getInstanceMethod(DateFormatter.self, orig)!
let newMethod = class_getInstanceMethod(DateFormatter.self, new)!
// performs the actual switch so that whenever "locale" is accessed in code, the returned value will be the value of "swizzledLocale"
method_exchangeImplementations(origMethod, newMethod)
And that's all there is to it! Now whenever you access any DateFormatter's locale, a Locale("en_US_POSIX") will be returned.
Some things to note: first, this will alter ALL instances of locale, not just the locales that you personally use in your own code. That is, if there's any internal code that relies on a formatter's locale, en_US_POSIX will always be used. Second, the second block of code (the one that performs the actual swizzling) should be called exactly one time; look into wrapping it in a dispatch_once block to ensure it only runs once.

NSCalendar.startOfDayForDate(date:) equivalent for iOS 7 with non-optional return type

Is it possible to change an NSDate object so that the result is equivalent to NSCalendar.startOfDayForDate(date:)? That method is only available to iOS 8 and newer, but I am looking for something that works on iOS 7.
I have looked at two methods:
NSCalendar.dateFromComponents(comps:) as described here: NSDate beginning of day and end of day. For instance, like this:
class func startOfDay(date: NSDate, calendar: NSCalendar) -> NSDate {
if #available(iOS 8, *) {
return calendar.startOfDayForDate(date)
} else {
let dateComponents = calendar.components([.Year, .Month, .Day], fromDate: date)
return calendar.dateFromComponents(dateComponents)!
}
}
NSDateFormatter.dateFromString(string:) by way of
stringFromDate(date:), i.e. converting the NSDate object into a string without the time, then converting it back into an NSDate object.
The problem with both methods is that they return an optional NSDate. I am reluctant to unwrap this implicitly and I’d rather avoid changing the return type of the method within which these methods are called.
I think the calendar.components() method returns an optional, because you can theoretically enter components that do not create valid date, like 2000-02-30. If, as in your case, the components already come from a valid date, I would not be reluctant to implicitly unwrap the optional.

Getting the current time as a decimal point number

I want to be able to get the current time as a decimal point number so it can be used. so for example if the time is 13:46 I want to get it as 13.46
It seems simple but I am struggling getting to it.
We can use an NSDateFormatter to do exactly this:
extension NSDate {
func currentTime() -> String {
let formatter = NSDateFormatter()
formatter.dateFormat = "HH.mm"
return formatter.stringFromDate(self)
}
}
And now we just use it by calling it on any instance of NSDate:
let now = NSDate()
print(now.currentTime())
you can stringify the time and serch lastindexOf(":") and substitue it with a "."
UPDATE
I don't really catch what program language are you using, but it's plenty of library for stringify object so if you have a 13:46 you can convert it to String and, in the same string library you could find the method lastIndexOf(char). But if you don't find it you can always write following this concepts:
String are an array of Char so you can cycle it and convert the char in that position in the char you need.

Using customized setter/getter and initializing the property for a swift date

I have a class called Trip which has an NSDate property called date. My desire is to make sure that the date property always has the time value set to the beginning of the day. I do this by:
Creating an NSCalendar constant called calendar so that I can use the startOfDayForDate method to reset the date value.
Creating a custom setter method for the date property that calls the startOfDayForDate method.
However, I want to initialize the date property to the start of today's date.
The code that I have so far is shown below.
class Trip {
private let calendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var date: NSDate {
get {
return self.date
}
set {
self.date = calendar.startOfDayForDate(newValue)
}
}
}
This code a) doesn't initialize the date property and b) (I discovered) results in an infinite loop when the setter is called. Upon further research, I believe that customized getters/setters are strictly computed, right?
How do I accomplish what I mentioned earlier:
a.) making sure that sets to the date property reset to the start of the day and
b.) initializing the date property to the start of today's date?
I think you want to have a date that will always be the start of the date. Replace your current date variable with this:
private var privateDate = NSDate()
var date: NSDate {
get {
return privateDate
}
set {
privateDate = calendar.startOfDayForDate(newValue)
}
}
There may be a slightly better way to do this, but I'm guessing your application won't have tens of thousands of your Trip class, so you should be fine.