I stuck into an issue where I want to declare a property in NSError extension.
This is my error structure
struct CustomError {
var errorTitle: String?
var errorDescription: String?
var isClear: Bool?
}
This is my extension
extension NSError {
var customeError:CustomError {
get {
return self.customeError
}
set {
self.customeError = newValue
}
}
}
I am getting bad access error while setting custom error into the property.
You cannot add properties to extensions.
There only way to make it work is to use objc_getAssociatedObject to have stored properties.
After searching this issue over the internet I found a cool solution. We can declare but there is the trick here is the updated extension.
extension NSError {
struct Holder {
static var customErr: CustomError = CustomError()
}
var customeError:CustomError {
get {
return Holder.customErr
}
set {
Holder.customErr = newValue
}
}
}
Actually previously in the question which I posted there was retain cycle but when I added holder then there is no cycle and code working perfectly.
Thanks, guys
Related
I am trying to initialize the WidgetKit struct WidgetInfo for unit testing a function that takes WidgetInfo as an argument:
let widgetInfo = WidgetInfo()
This gives me the error:
'WidgetInfo' cannot be constructed because it has no accessible initializers
I tried adding:
extension WidgetInfo {
public init() {}
}
and I can initialize - yay! But then I try to set one of its properties
widgetInfo.family = .systemSmall
and get the error: Cannot assign to property: 'family' is a 'let' constant
I tried another initializer with arguments:
extension WidgetInfo {
public init(family: WidgetFamily, kind: String) {
self.family = family
self.kind = kind
}
}
and I get the error: 'let' property 'family' may not be initialized directly; use "self.init(...)" or "self = ..." instead
I'm stuck - is there a way for me to initialize WidgetInfo? Or another way to test a function that takes WidgetInfo as an argument?
Figured it out. I created a WidgetInfo protocol with the information I needed, changed the function to take the protocol as the argument and extended WidgetInfo:
protocol WidgetInfoProtocol {
var widgetFamily: WidgetFamily { get }
var widgetKind: String { get }
}
extension WidgetInfo: WidgetInfoProtocol {
var widgetFamily: WidgetFamily {
return family
}
var widgetKind: String {
return kind
}
}
This allowed me to create a mock for use in unit testing:
struct MockWidgetInfo: WidgetInfoProtocol {
var widgetFamily: WidgetFamily
var widgetKind: String
}
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 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.
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 {
Here's my setup:
protocol Client {
var identifier: NSUUID { get }
}
class PeripheralClient: NSObject, Client {
var identifier: NSUUID {
get {
return peripheral.identifier
}
}
}
protocol NetworkManager {
var clients: [Client] { get set }
}
class CentralNetworkManager: NSObject, NetworkManager {
var clients = [Client]()
var peripheralClients: [PeripheralClient] {
get {
return clients as [PeripheralClient]
}
}
}
I get this runtime error when the peripheralClients Array is accessed for the first time: array element cannot be bridged to Objective-C.
From this answer to a question with a similar error, it looks like swift requires the items in an Array to be AnyObject compatible when converting to NSArray. So that means Swift's Array typecasting is using NSArray, thus making it impossible for me to downcast from an Array whose type is a protocol.
Anyone have a good suggestion for getting around this?
Did you try declaring Client as an Objective-C protocol?
#objc protocol Client {
var identifier: NSUUID { get }
}
Yeah, it seems, Swift itself does not have Array<T> to Array<U> casting functionality, it uses Obj-C facility.
Instead, to avoid that, you can cast each elements, using map for example:
var clients = [Client]()
var peripheralClients: [PeripheralClient] {
get {
return clients.map { $0 as PeripheralClient }
}
}