NSURL extension to adopt StringLiteralConvertible - ios

According to this post from NSHipster, we have extended the NSURL class to initialize NSURL objects using literals like this:
// the following is a full fledged NSURL object
let url: NSURL = "http://nshipster.com/"
Unfortunately, the post was written when Swift was first announced and it no longer compiles.
I was able to get my own custom object to conform to StringLiteralConvertible, and it looked like this:
final class Dog {
let name: String
init(name: String) {
self.name = name
}
}
// MARK: - StringLiteralConvertible
extension Dog: StringLiteralConvertible {
typealias UnicodeScalarLiteralType = StringLiteralType
typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
convenience init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(stringLiteral: value)
}
convenience init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(stringLiteral: value)
}
convenience init(stringLiteral value: StringLiteralType) {
self.init(name: value)
}
}
This works great. For example, the following 2 lines of code will create a Dog object:
let dog = Dog(name: "Bob")
let dog: Dog = "Bob"
Unfortunately, using this strategy via extending NSURL was met with errors:
extension NSURL: StringLiteralConvertible {
public typealias UnicodeScalarLiteralType = StringLiteralType
public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
convenience public init?(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(stringLiteral: value)
}
convenience public init?(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(stringLiteral: value)
}
convenience public init?(stringLiteral value: StringLiteralType) {
self.init(string: value)
}
}
I've been trekking through the compiler errors, solving them 1 guess at a time. However, I can't get past the following error that occurs for each of the initializers:
Initializer requirement 'init(...)' can only be satisfied by a 'required' initializer in the definition of non-final class 'NSURL'
Adding the required keyword will give the error that you may not declared required initializers within extensions.
Looking for some directions :|

StringLiteralConvertible support
Unfortunately, StringLiteralConvertible support for NSURL seems to be not possible in the current Swift version (2.2). The closest I can get is the following:
extension NSURL: StringLiteralConvertible {
public convenience init(stringLiteral value: String) {
self.init(string: value)!
}
public convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(string: value)!
}
public convenience init(unicodeScalarLiteral value: String) {
self.init(string: value)!
}
}
But the compiler complains:
Playground execution failed: OS X Playground.playground:5:24: error: initializer requirement 'init(stringLiteral:)' can only be satisfied by a `required` initializer in the definition of non-final class 'NSURL'
public convenience init(stringLiteral value: String) {
^
OS X Playground.playground:3:24: error: initializer requirement 'init(extendedGraphemeClusterLiteral:)' can only be satisfied by a `required` initializer in the definition of non-final class 'NSURL'
public convenience init(extendedGraphemeClusterLiteral value: String) {
^
OS X Playground.playground:7:24: error: initializer requirement 'init(unicodeScalarLiteral:)' can only be satisfied by a `required` initializer in the definition of non-final class 'NSURL'
public convenience init(unicodeScalarLiteral value: String) {
And required initializers cannot be implemented in an extension.
Alternative solution
We can simplify string-to-URL conversion from the other side!
extension String {
var url: NSURL? {
return NSURL(string: self)
}
}
var url = "http://google.coom/".url
print(url?.scheme) // Optional("http")

An another alternative is to subclass NSURL and conform to StringLiteralConvertible there.
class URL: NSURL, StringLiteralConvertible {
<#...#>
}

Related

Generic class initialization from protocol

I want to have classes objects JSONSerialization. So my input is [String: Any] and from a documentation I know it may be either NSNull, NSString or NSNumber. So I've made a protocol:
protocol PlainValue { }
and all of those above conform to this protocol:
extension NSString: PlainValue { }
extension NSNull: PlainValue { }
extension NSNumber: PlainValue { }
Then I want to create a class storage that hold key-value like:
class KeyValue<T: PlainValue> {
let key: NSString
let value: T
init(key: NSString, value: T) {
self.key = key
self.value = value
}
}
And want to use it like this:
func parse(json: [String: Any]) {
...
if let value = json[key] as? PlainValue { // this should be `Any` but I want to check here if thats an PlainValue or embedded object
let obj = KeyValue<PlainValue>(key: key, value: value) // this currently not working
...
}
...
}
But my issue is how to declare this object creation on protocol level. If I do this like:
protocol PlainValue {
func convert(key: NSString) -> KeyValue<PlainValue>
}
I'm getting error:
Value of protocol type 'PlainValue' cannot conform to 'PlainValue'; only struct/enum/class types can conform to protocols
Make sense, since I'm already in protocol declaration. So I have a feeling that maybe this should be declared on KeyValue<T> level? But I'm not sure if I'm on a right path for this, since I'm getting compilers error on every approach I'm trying to make. Can anyone point me into right direction how to make it working?
Approach that I feel like it's closest to working is:
extension NSNumber: PlainValue {
func convert(key: NSString) -> KeyValue<NSNumber> {
return KeyValue<NSNumber>(key: key, value: self)
}
}
And similar for above on NSString and NSNull, but not sure how to declare this on protocol level to make this callable from my parsing function. Since
protocol PlainValue: JSONValue {
func convert(key: NSString) -> KeyValue<Self> // this claims that implementation does not match this declaration
func convert<T: PlainValue>(key: NSString) -> KeyValue<T> // same as above
associatedtype Object: PlainValue
func convert(key: NSString) -> KeyValue<Object> // this is working! But... then I cannot check if value is PlainValue in my parsing function, because of `Protocol 'PlainValue' can only be used as a generic constraint because it has Self or associated type requirements`
}
A protocol cannot conform to another protocol, the generic type T in KeyValue<T> must be a concrete type.
An alternative is a protocol extension with an associated type
extension NSString: PlainValue { }
extension NSNull: PlainValue { }
extension NSNumber: PlainValue { }
protocol PlainValue {
associatedtype ValueType : PlainValue = Self
func convert(key: NSString) -> KeyValue<ValueType>
}
extension PlainValue where ValueType == Self {
func convert(key: NSString) -> KeyValue<ValueType> {
return KeyValue<ValueType>(key: key, value: self)
}
}
class KeyValue<T: PlainValue> {
let key: NSString
let value: T
init(key: NSString, value: T) {
self.key = key
self.value = value
}
}
let stringResult = "Foo".convert(key:"bar")
let numberResult = NSNumber(value:12).convert(key:"baz")
let nullResult = NSNull().convert(key:"buz")
Not sure if I understand correctly what you would like to achieve, but based on what you wrote, you don't necessarily need generics here. The code below would resolve your issue, if there are no other factors that make generics necessary in your situation. Also, this way you won't need to declare the object creation on the protocol level.
class KeyValue {
let key: NSString
let value: PlainValue
init(key: NSString, value: PlainValue) {
self.key = key
self.value = value
}
}
Your original example did not work because you needed a concrete type there that conforms to PlainValue. In swift, only concrete types such as struct/enum/class can conform to protocols. For example let obj = KeyValue<NSString>(key: key, value: value) would work there, while using PlainValue would not.

Using Class as RawValue of Enum not working

I am trying to use my custom class ChallengeUIElement as rawValue of my enum ChallengeUIElementType.
Therefore, I tried everything that Xcode wanted me to do, but I still get error messages thrown right at my face. It starts to hurt...
Anyways, I followed along this tutorial and get lots of error messages that I do not understand.
After googling for a while, I found some stack overflow entries about classes as rawValue types of enums.
However, I still was unable to get my enum with class type working.
My errors:
Here is my code:
enum ChallengeUIElementType: ChallengeUIElement, CaseIterable {
typealias RawValue = ChallengeUIElement
case Default = "DefaultElementCell"
}
class ChallengeUIElement: Equatable, ExpressibleByStringLiteral {
static func == (lhs: ChallengeUIElement, rhs: ChallengeUIElement) -> Bool {
return lhs.identifier == rhs.identifier && lhs.height == rhs.height && lhs.type == rhs.type
}
var height: CGFloat
var identifier: String
var type: UICollectionViewCell.Type
public init(stringLiteral value: String) {
let components = value.components(separatedBy: ",")
if components.count == 1 {
if components[0] == "DefaultElementCell" {
self.identifier = components[0]
self.type = DefaultElementCell.self
self.height = 380
}
}
}
public init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
public init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
}
Also, I do not understand why I could use RawRepresentable as protocol with this approach, but not with this one?
Any help would be highly appreciated.
The errors you get are mostly about the way you incorrectly declared your initialisers. They don't really have much to do with creating an enum with a class raw value.
Because your initialisers are required by the protocol, they need to be marked as required. You don't need to do this if your class is final. So either mark the class as final or mark all the initialisers as required.
The two convenience initialisers need to be marked as convenience:
public convenience init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
public convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
These are "convenience" initialisers because they call another initialiser declared in the class.
Additionally, the public init(stringLiteral value: String) initialiser does not initialise all the properties. Think about what happens if the if statements are not run. You need to give your properties (height, identifier and type) some default values.

How to use custom initializer on an Realm object inside a class with generic type?

I have a class called ObjectManager which has generic type O: Object, Persistable. This way I can have one class that can operate with multiple different subclasses of realm Object. But the problem is in the last line cache = O(message: "hello world") the error is: Incorrect argument label in call (have 'message:', expected 'value:')
When typing it suggests me that I can use initializer of Persistable protocol, but complain during the compiling, that it expect an initializer from Object class. Is it possible to cast Persistable initializer?
protocol Persistable {
init(message: String)
}
class CustomObject: Object, Persistable {
dynamic var message: String = ""
required convenience init(message: String) {
self.init()
self.message = message
}
}
class ObjectManager<O>: NSObject where O: Object, O: Persistable {
var cache: O?
func didReceive(message: String) {
cache = O(message: "hello world")
}
}
If you make your CustomObject class final, and replace your initializer with a factory method, you will get code which does what you want. Unfortunately, the Swift compiler error messages are so cryptic in this case as to be completely unhelpful.
protocol Persistable {
static func factory(message: String) -> Self
}
final class CustomObject: Object, Persistable {
dynamic var message: String = ""
static func factory(message: String) -> CustomObject {
let x = CustomObject()
x.message = message
return x
}
}
class ObjectManager<O>: NSObject where O: Object, O: Persistable {
var cache: O?
func didReceive(message: String) {
cache = O.factory(message: "hello world")
}
}

why need to use NSObject?

i am confused about that should i need to used NSObject to swift3 . if so please guide me also is it best practice to used NSNumber
class App: NSObject {
var id: NSNumber?
var name: String?
var category: String?
var imageName: String?
var price: NSNumber?
var screenshots: [String]?
var desc: String?
var appInformation: AnyObject?
// override func setValue(_ value: Any?, forKey key: String) {
// if key == "description" {
// self.desc = value as? String
// } else {
// super.setValue(value, forKey: key)
// }
// }
}
Note : could you tell me please why need to use NSObject ? what is the advantage ?
NSObject class:
This class is the root class of most Objective-C class hierarchies, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects. (source).
AnyObject protocol: its a implicit confirmation of any object.
You use AnyObject when you need the flexibility of an untyped object or when you use bridged Objective-C methods and properties that return an untyped result. AnyObject can be used as the concrete type for an instance of any class, class type, or class-only protocol.
AnyObject can also be used as the concrete type for an instance of a type that bridges to an Objective-C class.
let s: AnyObject = "This is a bridged string." as NSString
print(s is NSString)
// Prints "true"
let v: AnyObject = 100 as NSNumber
print(type(of: v))
// Prints "__NSCFNumber"
The flexible behavior of the AnyObject protocol is similar to Objective-C’s id type. For this reason, imported Objective-C types frequently use AnyObject as the type for properties, method parameters, and return values. (source)
So you can use NSNumber variable as AnyObject which can be later type cast implicitly accordingly.
You're overriding a super class method:
override func setValue(_ value: Any?, forKey key: String) { }
So, if your class doesn't extends NSObject class, how could overrides its super class method?
You could not extend NSObject class, but you'll have to remove override keyword from code since setValue would become a method of your custom class. If you do it, you'll not be able to call super.setValue(value, forKey: key) obviously.

Get Swift class name in "class func" method

I have a static method in Swift
class BaseAsyncTask: WebServiceClient {
class func execute(content : [String:AnyObject], cancelled:CustomBool)
{
// Print class name (BaseAsyncTask) here
}
}
And I want to know how to get the class name inside that method. I tried
self.dynamicType
but that gives error (I suppose because of the self inside a class function)
There are different methods to do that, if your method inherits from NSObject you can expose it to objective-c and do something like that.
#objc(BaseAsyncTask)
class BaseAsyncTask: WebServiceClient {
class func execute(content : [String:AnyObject], cancelled:CustomBool)
{
println("Class \(NSStringFromClass(self))")
}
}
For pure SWIFT introspection check here about MirrorType
I've found also this extension credits to ImpactZero
public extension NSObject{
public class var nameOfClass: String{
return NSStringFromClass(self).components(separatedBy: ".").last!
}
public var nameOfClass: String{
return NSStringFromClass(type(of: self)).components(separatedBy: ".").last!
}
}
[Xcode 8]
Alex suggested me that in the Xcode 8 version this code shows a warning. To avoid that we should prefix the method like that:
#nonobjc class var className: String{
return NSStringFromClass(self).components(separatedBy: ".").last!
}
You can use string interpolation to print self:
let className = "\(self)"
Sample code:
class BaseAsyncTask: WebServiceClient {
class func execute(content : [String:AnyObject], cancelled: CustomBool)
{
let className = "\(self)"
print(className)
}
}
class AnotherAsyncTask : BaseAsyncTask {
}
BaseAsyncTask.execute([:], cancelled: true) // prints "BaseAsyncTask"
AnotherAsyncTask.execute([:], cancelled: true) // prints "AnotherAsyncTask"
Another way to do this, when you don't have an instance of the class is this.
Swift 4
print(String(describing:BaseAsyncTask.self))
Swift 2
print(String(BaseAsyncTask))
Inspired here.
Get class name of object as string in Swift

Resources