In the book Swift Programming Language 3.0, it says that we can't use extension to add stored property.
I tried it out with instance stored variable and Xcode displayed an error as expected.
But when I tried with static stored variable, everything compiled just fine.
Is there something that I'm missing or doing wrong?
class MyClass {}
extension MyClass {
static var one: Int {
return 1
}
static var two = 2 //compiled just fine
}
let myVariable = MyClass()
MyClass.two
You can't put stored properties in instances of an extension, you can cheat a little though and get the same effect with Objective-C associated objects. Give the following code a try:
private var associationKey: UInt8 = 0
var validationTypes: ValidationTypes {
get {
return objc_getAssociatedObject(self, &associationKey) as? ValidationTypes ?? []
}
set(newValue) {
objc_setAssociatedObject(self, &associationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
Obviously replacing ValidationTypes as appropriate.
Related
I know that in swift we can use Extensions to add new methods to existing classes.
But what about if i want to add a variable?
extension UIViewController {
var myVar = "xyz"
}
It gives like :
Extensions must not contain stored properties
We can't add the stored properties to extensions directly but we can have the computed variables.
extension UIViewController {
var myVar: String {
return "xyz"
}
}
Extensions in Swift can:
Add computed instance properties and computed type properties
...
For more please visit
https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
You can only add computed properties to extensions as follows...
extension UIViewController {
var someProperty = "xyz" : String {
return "xyz"
}
}
If you wish to use it the way you are defining it, you might need to subclass your UIViewController
class YourCustomViewController: UIViewController {
var someProperty: String = "xyz"
}
you can only use computed variables:
for example we have the type Int in swift, and we want it extend in a way that swift generates a random number from 0 to our number :
extension Int
{
var arc4random : Int{
if self > 0
{return Int(arc4random_uniform(UInt32(UInt(self))))}
else if self < 0
{return -Int(arc4random_uniform(UInt32(UInt(abs(self)))))}
else
{return 0}
}
}
and usage :
myArray.count.arc4random
here my array.count is an Int , and arc4random is the computed variable we have defined in our extension, u cant store a value in it
You can try ( This is a readOnly computed property )
extension UIViewController {
var someProperty : String {
return "xyz"
}
}
I think part of my problem is because Swift 4 has changed the way things like #objc work.
There are a lot of tutorials floating around, with a lot of different values, and I can't pick my way between what used to work in what version enough to figure out how to make it work in this version.
let delegate = UIApplication.shared.delegate as! AppDelegate
delegate.addObserver(self, forKeyPath: #keyPath(AppDelegate.session), options: [], context: nil)
// Warning: Argument of #keyPath refers to non-'#objc' property 'session'
Adding #objc to the var declaration just informs me that APISession can't be referenced in Objective-C. That seems to lead down the path towards requiring me to expose every class / variable I want to use this tool with to Obj-C, and that just seems backwards -- this is a newer feature, as I understand it, and it's just odd that Apple wouldn't make it work natively in Swift. Which, to me, suggests I'm misunderstanding or misapplying something, somewhere, somehow.
According to the docs:
In Objective-C, a key is a string that identifies a specific property of an object. A key path is a string of dot-separated keys that specifies a sequence of object properties to traverse.
Significantly, the discussion of #keyPath is found in a section titled "Interacting with Objective-C APIs". KVO and KVC are Objective-C features.
All the examples in the docs show Swift classes which inherit from NSObject.
Finally, when you type #keyPath in Xcode, the autocomplete tells you it is expecting an #objc property sequence.
Expressions entered using #keyPath will be checked by the compiler (good!), but this doesn't remove the dependency on Objective-C.
This is how I've applied #keyPath() in real project of mine. I used it to save & retrieve data to and from UserDefaults and I called that feature as AppSettings. Here's how things are going on...
1). I have a protocol called AppSettingsConfigurable It contains a couple of stuffs which are the setting features of my app...
//: AppSetting Protocol
#objc protocol AppSettingsConfigurable {
static var rememberMeEnabled : Bool { get set }
static var notificationEnabled : Bool { get set }
static var biometricEnabled : Bool { get set }
static var uiColor: UIColor? { get set }
}
2). I have class and I named it AppSettings. This is where saving and retrieving operation take place with UserDefaults
//: AppSettings
class AppSettings: NSObject {
fileprivate static func updateDefaults(for key: String, value: Any) {
// Save value into UserDefaults
UserDefaults.standard.set(value, forKey: key)
}
fileprivate static func value<T>(for key:String) -> T? {
// Get value from UserDefaults
return UserDefaults.standard.value(forKey: key) as? T
}
}
3). Here's where BIG things are happened. Conform AppSettings class to our protocol and lets implement the stuffs using #keyPath().
//: Conform to protocol
extension AppSettings:AppSettingsConfigurable{
/** get & return remember me state */
static var rememberMeEnabled: Bool {
get { return AppSettings.value(for: #keyPath(rememberMeEnabled)) ?? false }
set { AppSettings.updateDefaults(for: #keyPath(rememberMeEnabled), value: newValue) }
}
/** get & return notification state */
static var notificationEnabled: Bool {
get { return AppSettings.value(for: #keyPath(notificationEnabled)) ?? true }
set { AppSettings.updateDefaults(for: #keyPath(notificationEnabled), value: newValue) }
}
/** get & return biometric state */
static var biometricEnabled: Bool {
get { return AppSettings.value(for: #keyPath(biometricEnabled)) ?? false}
set { AppSettings.updateDefaults(for: #keyPath(biometricEnabled), value: newValue) }
}
/** get & return biometric state */
static var uiColor: UIColor? {
get { return AppSettings.value(for: #keyPath(uiColor)) }
set { AppSettings.updateDefaults(for: #keyPath(uiColor), value: newValue!) }
}
}
PS: Noticed something different with uiColor from the rest? Nothing wrong with it as it's optional and it's allowed to accept the nil
Usage:
//: Saving...
AppSettings.biometricEnabled = true
//: Retrieving...
let biometricState = AppSettings.biometricEnabled // true
In my project I am having an issue with Swift, that it doesn't recognize variables containing protocol types. This means I can't use a variable that stores a type, to check if the type of an instance matches it.
I attached problematic part of the code with some brief context.
Is this some kind of bug or am I overseeing something really badly?
Using XCode 7.3, Swift 2.2
//Context of Issue BEGIN
class TaskValueObject {
//ManyData, VeryComplexity, SoBig, wow..
}
typealias TaskListSorterBlock = (TaskValueObject, TaskValueObject) -> Bool
protocol TaskListSorter : class {
init()
var localizedTitle : String { get }
var sorterBlock : TaskListSorterBlock { get }
}
class PriorityTaskListSorter : TaskListSorter {
// ... Implementation ...
}
// Many other TaskListSorter implementation classes ...
//Context of Issue END
class TaskListContainer {
weak var currentSorter : TaskListSorter?
var sorters : [TaskListSorter]
init() {
self.sorters = [ PriorityTaskListSorter(), /* ... Instances created for <TaskListSorter> implementing class ... */ ]
loadDefaultSorter()
}
static var defaultSorterType : TaskListSorter.Type = PriorityTaskListSorter.self
private func loadDefaultSorter() {
let defaultSorterType = TaskListContainer.defaultSorterType
for sorter in self.sorters {
if sorter is defaultSorterType {
// ^ ERROR HERE : defaultSorterType is not a 'type'
self.currentSorter = sorter
}
}
}
}
Update 1: I get the same error if I replace the problematic line with the following:
if let defaultSorter = sorter as? defaultSorterType {
Update 2: Replacing the problematic line with the one below, makes the code work. However I am using here the 'dynamicType' which is not offered by code completion (must be a reason for that...). Also the question remains, why the first 2 attempts didn't work?
if sorter.dynamicType == defaultSorterType {
I have written a helper struct for saving and loading stuff to NSUserDefaults.
import UIKit
struct Database {
static let defaults = NSUserDefaults.standardUserDefaults()
static var myVariable: AnyObject?
static func save() {
defaults.setObject(myVariable, forKey: "myVariable")
}
static func load() {
if let myVariable = defaults.objectForKey("myVariable") {
self.myVariable = myVariable
}
}
static func clear() {
defaults.removeObjectForKey("myVariable")
}
}
Now I can simply use Database.load() to load myVariable from NSUSerDefaults.
However, the same is achievable with this code:
struct Database2 {
static var sharedInstance = Database()
let defaults = NSUserDefaults.standardUserDefaults()
var myVariable: AnyObject?
func save() {
defaults.setObject(myVariable, forKey: "myVariable")
}
func load() {
if let myVariable = defaults.objectForKey("myVariable") {
self.myVariable = myVariable
}
}
func clear() {
defaults.removeObjectForKey("myVariable")
}
}
Now I would use Database2.sharedInstance.load().
Which one is seen as a better practice and why? What's the use of a sharedInstance, if I can do everything I want with the static declaration?
A shared instance is recommendable, at least for the following reasons:
class methods make unit testing harder
you need class instances for dependency injection
if later on you decide that a non-singleton is more suitable - e.g. you decide to have two persistence storages for "myVariable", then you're stuck
and not lastly, class members live in the global space, and we should avoid using globals
The real question you should ask, is if you really need a singleton (with or without a shared instance) for your problem. If the only reason to have a singleton is ease-of-access, then you don't really need a singleton.
P.S. There is a very good article on objc.io about singletons, and although it was written for Objective-C, many concepts from there apply in Swift too.
// with singleton pattern, there exist only one copy of the object
// sigleton pattern can be applied for reference type only
// let st1 = Singleton(); let st2 = Sigleton(); st1 === st2
// in your example, S is value type. All instances of S share only type properties, here only i
struct S {
static var i: Int = 100
var j: Int
func foo() {
//print(i) // error: static member 'i' cannot be used on instance of type 'S'
print(S.i)
}
init(_ j: Int) {
self.j = j
}
}
var s1 = S(1)
var s2 = S(2)
//s1.i // error: static member 'i' cannot be used on instance of type 'S'
S.i // 100
s1.foo() // 100
s1.j // 1
s2.foo() // 100
s2.j // 2
S.i = 200
s1.foo() // 200
s2.foo() // 200
by the way, this (your) approach can be very useful and could be preferred in some situations.
I'm trying to convert the following Objective-C code to Swift. In my Objective-C code, there's a static variable and its accessed from a class method.
#implementation SomeClass
static NSMutableArray *_items;
+ (void)someMethod {
[_items removeAll];
}
#end
Since you can't access types declared like this private var items = [AnyObject]() from class functions in Swift, I created a stored property for it like this.
class var items: [AnyObject] {
return [AnyObject]()
}
And I'm trying to call a method on it from a class function like so.
class func someFunction() {
items.removeAll(keepCapacity: false)
}
But I get this error Immutable value of type '[AnyObject]' only has mutating members named 'removeAll'.
Can anyone please tell me what's the cause of this error and how to correct it?
Thank you.
With this code:
class var items: [AnyObject] {
return [AnyObject]()
}
you are not creating a stored property - instead it's a computed property, and the worst part is that every time you access to it, a new instance of [AnyObject] is created, so whatever you add to it, it's lost as soon as its reference goes out of scope.
As for the error, the static computed property returns an immutable copy of the array that you create in its body, so you cannot use any of the array method declared as mutating - and removeAll is one of them. The reason why it is immutable is because you have defined a getter, but not a setter.
Currently Swift classes don't support static properties, but structs do - the workaround I often use is to define an inner struct:
class SomeClass {
struct Static {
static var items = [AnyObject]()
}
}
SomeClass.Static.items.append("test")
If you want to get rid of the Static struct every time you refer to the items property, just define a wrapper computed property:
class var items: [AnyObject] {
get { return Static.items }
set { Static.items = newValue }
}
so that the property can be accessed more simply as:
SomeClass.items.append("test")
Updated to Swift1.2
In Swift1.2[Xcode6.3], you can declare static properties using keyword static, also you can declare static methods using keyword class or static.
class SomeClass {
// use static modifier to declare static properties.
static var items: [AnyObject]!
// use class modifier to declare static methods.
class func classMethod() {
items.removeAll(keepCapacity: false)
}
// use static modifier to declare static methods.
static func staticMethod() {
items.removeAll(keepCapacity: false)
}
}
EDIT:
The difference between static and class modifier is that static is just an alias for "class final",so methods modified with static can not be overridden in subclasses.
Thanks #Maiaux's
Yet the manual for Swift 2 still claims just enumeration ond structures may use static store properities.