how can I create a KeyPath from a nested class? I don't know how to access the nested class using Backslash.
Example Code:
class Config {
var clients: Array<Client> = [Client()]
// Nested Class
class Client {
public var name: String = "SomeName"
}
}
var conf = Config()
func accessValue<T, U>(
_ keyPath: ReferenceWritableKeyPath<Config, Array<T>>,
valuePath: ReferenceWritableKeyPath<T ,U>) -> Any {
let member: Array<T> = conf[keyPath: keyPath]
let firstMember: T = member.first!
let value = firstMember[keyPath: valuePath]
return value
}
// How should I call this function?
print(accessValue(\Config.clients, valuePath: wanna_know))
// Should print "SomeName"
Thanks in advance!
A nested class is referred to by name using standard dot notation, so in your case the nested class is Config.Client.
As you know a key-path is of the form \<type name>.<path> so the path you are seeking is \Config.Client.name.
HTH
Related
Is it possible to create a keypath referencing a method? all examples are paths to variables.
I'm trying this:
class MyClass {
init() {
let myKeypath = \MyClass.handleMainAction
...
}
func handleMainAction() {...}
}
but it does not compile saying Key path cannot refer to instance method 'handleMainAction()
KeyPaths are for properties. However, you can do effectively the same thing. Because functions are first class types in swift, you can create a reference to handleMainAction and pass it around:
//: Playground - noun: a place where people can play
import UIKit
import XCTest
import PlaygroundSupport
class MyClass {
var bar = 0
private func handleMainAction() -> Int {
bar = bar + 1
return bar
}
func getMyMainAction() -> ()->Int {
return self.handleMainAction
}
}
class AnotherClass {
func runSomeoneElsesBarFunc(passedFunction:() -> Int) {
let result = passedFunction()
print("What I got was \(result)")
}
}
let myInst = MyClass()
let anotherInst = AnotherClass()
let barFunc = myInst.getMyMainAction()
anotherInst.runSomeoneElsesBarFunc(passedFunction: barFunc)
anotherInst.runSomeoneElsesBarFunc(passedFunction: barFunc)
anotherInst.runSomeoneElsesBarFunc(passedFunction: barFunc)
This will work fine, and you can pass "barFunc" to any other class or method and it can be used.
You can use MyClass.handleMainAction as an indirect reference. It gives you a block that take the class instance as the input parameter, and returns corresponding instance method.
let ref = MyClass.handleMainAction //a block that returns the instance method
let myInstance = MyClass()
let instanceMethod = ref(myInstance)
instanceMethod() //invoke the instance method
The point is you can pass around / store the method reference just like what you did with a key path. You just need to supply the actual instance when you need to invoke the method.
Is there anyway to use conversion using a variable? I am using object stacking using type of "AnyObject" and I've been able to take the class type and populate a variable. Now I need to populate an array using conversion.
var myString = "Hello World"
var objectStack = [AnyObject]()
objectStack.append(myString)
let currentObject = String(describing: objectStack.last!)
var objectType = String()
let range: Range<String.Index> = currentObject.range(of: ":")!
objectType = currentObject.substring(to: range.lowerBound)
let range2: Range<String.Index> = objectType.range(of: ".")!
objectType = objectType.substring(from: range2.upperBound)
The code above will evaluate the class and set the value of "objectType" to "String". Now I'm trying to go the other way. Something like this:
for obj in objectStack{
obj = newObject as! objectType //this doesn't work
}
Is something like this possible?
There is a simpler, safer way to get the type:
let type = type(of: objectStack.last!) // String.Type
let typeString = String(describing: type) // "String"
The other way around is not possible because the type of the object is not known at compile time. Do you have a number of known types you want to try to cast to? In that case, use optional binding to check if the cast is successful:
let object = objectStack.last!
if let string = object as? String {
// do String stuff
}
else if let i = object as? Int {
// do Int stuff
}
// and so on
If you have a large number of possible types that share some common functionality: Use Protocols. See Swift Documentation for a nice introduction.
You define a protocol for some common functionality that different types can implement:
protocol Stackable {
func doStuff()
// (more methods or properties if necessary)
}
The protocol provides a contract that all types conforming to this protocol have to fulfill by providing implementations for all declared methods and properties. Let's create a struct that conforms to Stackable:
struct Foo: Stackable {
func doStuff() {
print("Foo is doing stuff.")
}
}
You can also extend existing types to make them conform to a protocol. Let's make String Stackable:
extension String: Stackable {
func doStuff() {
print("'\(self)' is pretending to do stuff.")
}
}
Let's try it out:
let stack: [Stackable] = [Foo(), "Cat"]
for item in stack {
item.doStuff()
}
/*
prints the following:
Foo is doing stuff.
'Cat' is pretending to do stuff.
*/
This worked for me:
var instance: AnyObject! = nil
let classInst = NSClassFromString(objectType) as! NSObject.Type
instance = classInst.init()
Question: how would one go about initializing objects using default property values? Can one override an init within a class to create the objects?
I want to avoid using the view controller to input the object's property data. I am not pulling in data from an external source. Just want to keep the data in a separate file, like a class file, because the application involves a ton of text.
The objects I have in mind would be something that looks like a typical class object with a number of properties: object1(prop1:"string", prop2: [string], prop3: [string] and so on ...) but created in the same class to be inserted into an array of objects.
Use a caseless enum, kind of like a name space, to store your text. Then just reference those in the default initializers.
enum StaticText {
static let A = "A"
static let B = "B"
static let C = "C"
}
class SomeClass {
let A = StaticText.A
let B = StaticText.B
let C = StaticText.C
}
Make an extension that uses a convenience initializer:
public class ToolBoardView: UIVisualEffectView {
public var closeButton = ToolBoardCloseButton()
public var imageSegments = UISegmentedControl (items: ["Subject","Frame"])
internal var sliderBackgrounds:[UILabel] = []
convenience internal init(
_ tag :Int,
_ p: inout [NSLayoutConstraint],
_ l: inout [NSLayoutConstraint],
_ target :Any,
_ hideAction :Selector
)
{
self.init(frame: CGRect.zero)
self.effect = UIBlurEffect(style: .light)
self.tag = tag
switch tag {
case 0:
break
case 1:
addFilterControls(&p, &l)
default:
break
}
closeButton = ToolBoardCloseButton(tag: tag, target: target, action: hideAction)
self.addSubview(closeButton)
turnOffAutoResizing()
makeControlsHidden(true)
}
}
That's just part of one I have, but it should get you going.
Here is a very good description of what I just posted.
I want to automate this piece of code:
let objectType = json["object"]["type"].stringValue;
switch objectType {
case "Message":
activity.item = MessageLib.make(json["object"]) as! MessageItem;
default:
()
}
I want to make an object based on its type. But I don't want to write a new case for each new ObjectType. I just want to make a new object class.
OBJECTTYPEItem
If your class is not a pure swift class and inherits somewhere from NSObject, I would recommend using something like NSClassFromString and creating object from it.
let objectType = json["object"]["type"].stringValue;
var classType = NSClassFromString(objectType)
var classVariable = classType()
You can typecast this class to any of your classes using
var myClassType = classType as! MyClass.type
This is my factory which follows #Shamas S directions, including some added code to make it of more general use. The compiler gives no error if ContentItemFactory does not inherits from NSObject, but you have to import the Foundation framework to use NSClassFromString
In my case all the created class-objects are subclasses of "ContentItem" class, which is initialized by init(fromDictionary dictionary: [String:Any]), so I have to use the required keyword before that initializer.
import Foundation
class ContentItemFactory {
class func create(fromDictionary dictionary: [String:Any]) -> ContentItem{
if let objectType = dictionary["type"] as? String{
let namespace = (Bundle.main.infoDictionary!["CFBundleExecutable"] as! String).replacingOccurrences(of: " ", with: "_")
let myClass = NSClassFromString("\(namespace).\(objectType)") as! ContentItem.Type
return myClass.init(fromDictionary:dictionary)
}
return ContentItem.init(fromDictionary: dictionary)
}
}
class ContentItem {
required init(fromDictionary dictionary: [String:Any]){
}
}
In the example below T refers to a type that extends NSManagedObject, so why can I not call.
I do not have access to an instance of the class
private func getNewManagedObject <T: NSManagedObject>(type: T.Type) -> T {
// Let's assume all Entity Names are the same as Class names
let className = "" /*Somehow get class name from type ("User")*/
return NSEntityDescription.insertNewObjectForEntityForName(className, inManagedObjectContext: managedObjectContext) as T
}
getNewManagedObject(User.self);
Swift classes can be given a custom Objective-C name, what will make NSStringFromClass print a nicer output in a playground.
import CoreData
#objc(User) class User : NSManagedObject {
}
let className = NSStringFromClass(User.self) // className will be "User"
Without it, NSStringFromClass will print 'ModulName.ClassName' which is arguably better than 'ClassName' only. The ugliness of the playground output is due to the fact that playgrounds have some cryptic implicit module names.
With some experimenting I found out the following. In Playground you can do
class User : NSManagedObject {
}
let s = NSStringFromClass(User) // cryptic output: "__lldb_expr_XX.User"
The XX is some random number. At this point you can get the entity name with
let entityName = s.pathExtension // "User"
It's a bit hacky but maybe it could work for you.
quickly in Swift:
let className = String(YourClass)
Extension variant (in my opinion, it's more convenient):
extension NSObject {
class var string: String{
get {
return String(self)
}
}
}
//using:
let className = YourClass.string
Switf 2 solutions
let className = String(Int.self) // String
let className2 = String(4.dynamicType) // Int
func doThings<T>(type: T.Type) -> String {
return String(T) // Whatever type passed
}