Playing with reflections in swift 2.0 i'm trying to type check a child value.
The problem: each element of the children array in the Mirror of Any item is not optional, but his type can be optional... What happens is that of course i have the child value even if the value is nil
Maybe it is not clear so i put here some code to explain better.
For convenience i defined a subscript in a Mirror extension that fetches the child object with a given label
extension Mirror {
public subscript(key: String)->Child?{
var child = children.filter {
var valid = false
if let label = $0.label {
valid = label == key
}
return valid
}.last
if child == nil,
let superMirror = superclassMirror() {
child = superMirror[key]
}
return child
}
}
perfect, now let's say i have this class
class Rule: NSObject, AProtocol {
var hello: String?
var subRule: Rule?
}
Ok, now the problem
let aRule = Rule()
let mirroredRule = Mirror(reflecting:aRule)
if let child = mirroredRule["subRule"] {
//child.value always exists
//i can't do child.value is AProtocol? because child.value is not optional
//child.value is AProtocol of course returns false
//child.dynamicType is Optional(Rule)
if let unwrapped = unwrap(child.value) where unwrapped is AProtocol {
//This of course works only if child.value is not nil
//so the unwrap function returns an unwrapped value
//this is not a definitive solution
}
}
child.value has not been initialized so it is nil, and i can't check his type using the unwrap function. I'm writing a deserializer so i need to check the var also if it is nil because in the dictionary that will be used for the deserialization it could be defined.
private func unwrap(subject: Any) -> Any? {
var value: Any?
let mirrored = Mirror(reflecting:subject)
if mirrored.displayStyle != .Optional {
value = subject
} else if let firstChild = mirrored.children.first {
value = firstChild.value
}
return value
}
I hope the problem is clear. Any suggestions?
Based on this answer, I recommend using if case Optional<Any>.some(_).
I did something recently to make sure I have at least one optional set on my struct. You can paste into playgrounds:
struct ErrorResponse: Codable {
let message: String?
let authorizationException: [String: String]?
let validationException: String?
let generalException: String?
var isValid: Bool {
var hasAtLeastOneNonNilErrorValue = false
Mirror(reflecting: self).children.forEach {
if case Optional<Any>.some(_) = $0.value {
hasAtLeastOneNonNilErrorValue = true
}
}
return hasAtLeastOneNonNilErrorValue
}
}
let errorTest = ErrorResponse(message: "some message", authorizationException: nil, validationException: nil, generalException: nil)
let errorTest2 = ErrorResponse(message: nil, authorizationException: nil, validationException: nil, generalException: nil)
print("is valid: \(errorTest.isValid)") //is valid: true
print("is valid: \(errorTest2.isValid)") //is valid: false
Related
I am following an e-class tutorial for SWIFT // XCODE 11.4 and I have to fetch data from Open Weather API and display it on the interface where people can type in a city and the view controller will display temperature, cloud icon, and description.
Clima App Tutorial
I am using the MVC pattern design and the delegate design to accomplish this tutorial. My swift files are as followed:
Swift Files in MVC Design Pattern
Here are the codes in each of the important files:
I. Model folder
WeatherManager.swift
protocol WeatherManagerDelegate {
func didUpdateWeather(weather: WeatherModel)
}
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/d.5/weather?appid=c8b50079338280b47a65dd6082551e3b&units=imperial"
let delegate: WeatherManagerDelegate?
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
//create a URL
if let url = URL(string: urlString) {
//create a URLSession
let session = URLSession(configuration: .default)
//give session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return //exit out of the func if there is an error
}
if let safeData = data {
if let weather = self.parseJSON(weatherData: safeData) {
self.delegate?.didUpdateWeather(weather: weather)
}
}
}
//start the tast
task.resume()
}
}
func parseJSON (weatherData: Data) -> WeatherModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
let id = decodedData.weather[0].id
let temp = decodedData.main.temp
let name = decodedData.name
let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
return weather
} catch {
print(error)
return nil
}
}
}
WeatherData.swift
struct WeatherData: Codable {
let name: String
let main: Main
let weather: [Weather]
}
struct Main: Codable {
let temp: Double
}
struct Weather: Codable {
let id: Int
}
WeatherModel.swift
struct WeatherModel {
let conditionId: Int
let cityName: String
let temperature: Double
var temperatureString: String {
return String(format: "%.1f", temperature)
}
var conditionName: String {
switch conditionId {
case 200...232:
return "cloud.bolt"
case 300...321:
return "cloud.drizzle"
case 500...531:
return "cloud.rain"
case 600...622:
return "cloud.snow"
case 701...781:
return "cloud.fog"
case 800:
return "sun.max"
case 801...804:
return "cloud.bolt"
default:
return "cloud"
}
}
}
II. Controller
WeatherViewController.swift (place where the error is)
class WeatherViewController: UIViewController, UITextFieldDelegate, WeatherManagerDelegate {
#IBOutlet weak var conditionImageView: UIImageView!
#IBOutlet weak var temperatureLabel: UILabel!
#IBOutlet weak var cityLabel: UILabel!
#IBOutlet weak var searchTextField: UITextField!
var weatherManager = WeatherManager()
override func viewDidLoad() {
super.viewDidLoad()
weatherManager.delegate = self
searchTextField.delegate = self
}
#IBAction func searchPressed(_ sender: UIButton) {
searchTextField.endEditing(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
searchTextField.endEditing(true)
print(searchTextField.text!)
return true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if textField.text != "" {
return true
} else {
textField.placeholder = "Type something..."
return false
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
if let city = searchTextField.text {
weatherManager.fetchWeather(cityname: city)
}
searchTextField.text = ""
}
func didUpdateWeather(weather: WeatherModel) {
print(weather.temperature)
}
}
Here is the error message: Missing argument for parameter 'delegate' in call
Error message in WeatherViewControl.swift
And when I hit the run button, I also got this error in the debug console:
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
Error in the debug console
What should I do to get rid of these errors?
You need to change the keyword "let" to "var" in let delegate: WeatherManagerDelegate? in struct WeatherManager.
Missing argument for parameter 'delegate' in call
When a struct is create a value for each property is required.
If each property is specified with a default value and there is no user-defined initializer then Swift will create a default initializer for the struct.
If there is at least one property without a default value and there is no user-defined initializer then Swift will create a memberwise initializer which has one parameter for each property without a default value.
For example your type:
struct WeatherModel {
let conditionId: Int
let cityName: String
let temperature: Double
...
has three properties without default values. If you start typing:
let myWeatherModel = WeatherModel(
and then take the completion offered you will get (something like):
let wm = WeatherModel(conditionId: <Int>, cityName: <String>, temperature: <Double>)
The completion is showing the memberwise initializer.
Your type which produces the error is:
struct WeatherManager {
let weatherURL = "https://api.openweathermap.org/d.5/weather?appid=c8b50079338280b47a65dd6082551e3b&units=imperial"
let delegate: WeatherManagerDelegate?
which has two properties only one of which has a default value, and it has no initializers, so Swift will create a member wise initialiser automatically.
There is nothing wrong so far.
The line producing the error is:
var weatherManager = WeatherManager()
Here you are attempting to create a WeatherManager without invoking the member wise initalizer, so Swift gives you the error message.
If you click on the error message itself you will see a Fix is offered, click that and Swift will change your code to:
var weatherManager = WeatherManager(delegate: <WeatherManagerDelegate?>)
Select the <WeatherManagerDelegate?> and type the value you wish to pass.
HTH
import Foundation
struct answers : Codable {
var answer : String
var number : Int
}
var allAnswers: Array = [answers]()
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let archiveURL = documentsDirectory.appendingPathExtension("answers_test").appendingPathExtension("plist")
class ViewController: UIViewController {
#IBOutlet var vcLabels: [UILabel]!
#IBOutlet weak var vcTextField: UITextField!
var index : Int = 0
#IBAction func vcButtonPressed(_ sender: Any) {
vcLabels[index].text = vcTextField.text
var newAnswer = answers(answer: vcLabels[index].text!, number: index)
var newAnswers = [newAnswer]
print(archiveURL)
index += 1
if index == 4 {
index = 0
}
let propertyListEncoder = PropertyListEncoder()
let encodedNotes = try? propertyListEncoder.encode(newAnswers)
try? encodedNotes?.write(to:archiveURL, options: .noFileProtection)
}
First of all please conform to the naming convention that struct and class names start with a capital letter, the benefit it that the struct Answer and the property answer cannot be mistaken. And to avoid more confusion name the struct in singular form (in the line where a new answer is created you are creating semantically one answer, not one answers). And the properties can be declared as constants (let).
struct Answer : Codable {
let answer : String
let number : Int
}
Your issue is very clear: If you want to append something to the property list file you have to read it first from archiveURL, append the item(s) and write it back.
#IBAction func vcButtonPressed(_ sender: Any) {
vcLabels[index].text = vcTextField.text
let newAnswer = Answer(answer: vcLabels[index].text!, number: index)
print(archiveURL)
index += 1
if index == 4 {
index = 0
}
do {
let data = try Data(contentsOf: archiveURL)
var savedAnswers = PropertyListDecoder().decode([answers], from: data)
savedAnswers.append(newAnswer)
let encodedNotes = try PropertyListEncoder().encode(savedAnswers)
try encodedNotes?.write(to:archiveURL, options: .noFileProtection)
} catch { print(error) }
}
And don't ignore errors with try?. Handle them
for a unit test of mine, I wrote a small helper that can get me a property Value by name.
let m = Mirror(reflecting: self)
let child1 = m.descendant(name)
now the problem is that the child has Type Any? but the properties real type is e.g. Bool? So Any is actually an Optional!
Thats why if child1 is Bool? never fires because Any? isn't the Bool?.
But child1! is Bool? doesn't compile.
And child1! is Bool isn't true!
So how do I 'unbox' this reflected Any? value?
a small example of what I mean
import UIKit
class ViewController: UIViewController {
let name = "asd"
let boolvalue: Bool = true
var optboolvalue: Bool? = true
override func viewDidLoad() {
print( getNumberForBool("boolvalue") )
print( getNumberForBool("optboolvalue") )
}
func getNumberForBool( name: String ) -> NSNumber {
let m = Mirror(reflecting: self)
let child1 = m.descendant(name)
if(child1 != nil) {
//! only works for bool, not for bool?
if let b = child1 as? Bool {
return NSNumber(bool: b)
}
//this would be my interpretation of how to do it ... unwrap the any and unwrap it again. this doesn't compile though :)
// if let b = child1! as! Bool? {
// return NSNumber(bool: b!)
// }
}
return NSNumber(bool: false)
}
}
NOTE
The type of child1 for the case Bool?:
▿ Optional(Optional(true))
▿ Some : Optional(true)
- Some : true
solution
I worked around the issue of not being able to cast an to Bool? by using reflection again
if let any = child1, let maybeB = Mirror(reflecting: any).descendant("Some") as? Bool {
if let b = (maybeB as Bool?) {
return NSNumber(bool: b)
}
}
reflection ^ 2 :D
the complete gist:
https://gist.github.com/Daij-Djan/18e8ab9bcbaa3f073523
the ?? operator unwraps the optional in case there is Some inside. If there is not, the value that is passed after eg. let foo = someOptional ?? alternativeValue.
This would give you an unwrapped Any. Since Swift is strongly typed you will only be getting the Bool if you would cast.
if let b = child, let c = b as? Bool {
return NSNumber(bool: c)
}
Given this Model:
public class RSS2Feed {
public var channel: RSS2FeedChannel?
public init() {}
}
public class RSS2FeedChannel {
public var title: String?
public var description: String?
public init() {}
}
What would I need to do in order to get the property names and values of an RSS2FeedChannel instance?
Here's what I'm trying:
let feed = RSS2Feed()
feed.channel = RSS2FeedChannel()
feed.channel?.title = "The Channel Title"
let mirror = Mirror(reflecting: feed.channel)
mirror.children.first // ({Some "Some"}, {{Some "The Channel Title...
for (index, value) in mirror.children.enumerate() {
index // 0
value.label // "Some"
value.value // RSS2FeedChannel
}
Ultimately, I'm trying to create a Dictionary that matches the instance, using reflection, but so far I'm unable to get the properties name and values of the instance.
Documentation says that:
The optional label may be used when appropriate, e.g. to represent the name of a stored property or of an active enum case, and will be used for lookup when Strings are passed to the descendant method.
Yet I only get a "Some" string.
Also, the value property is returning a string with the Type RSS2FeedChannel when I would expect each children to be "An element of the reflected instance's structure."!
When i understand correct this should solve ur problem:
func aMethod() -> Void {
let feed = RSS2Feed()
feed.channel = RSS2FeedChannel()
feed.channel?.title = "The Channel Title"
// feed.channel?.description = "the description of your channel"
guard let channel = feed.channel else {
return
}
let mirror = Mirror(reflecting: channel)
for child in mirror.children {
guard let key = child.label else {
continue
}
let value = child.value
guard let result = self.unwrap(value) else {
continue
}
print("\(key): \(result)")
}
}
private func unwrap(subject: Any) -> Any? {
var value: Any?
let mirrored = Mirror(reflecting:subject)
if mirrored.displayStyle != .Optional {
value = subject
} else if let firstChild = mirrored.children.first {
value = firstChild.value
}
return value
}
just some little changes for swift 3:
private func unwrap(_ subject: Any) -> Any? {
var value: Any?
let mirrored = Mirror(reflecting:subject)
if mirrored.displayStyle != .optional {
value = subject
} else if let firstChild = mirrored.children.first {
value = firstChild.value
}
return value
}
You can use the descendent method on the Mirror object to get this information. It will return nil if the values aren't found or the optionals contain no value.
let mirror = Mirror(reflecting: feed.channel)
let child1 = mirror.descendant("Some", "title") // "The Channel Title"
// or on one line
let child3 = Mirror(reflecting: feed).descendant("channel", "Some", "title")
Is it possible to store closures in dictionaries (how we could store ObjC blocks in dictionaries)? Example:
data = [String:AnyObject]()
data!["so:c0.onSelection"] = {() in
Debug.log(.Debug, message: "Hello, World!")
}
You can, but with some restrictions. First of all, function types don't inherit from AnyObject and don't share a common base class. You can have a dictionary [String: () -> Void] and [String: (String) -> Int], but they can't be stored in the same dictionary.
I also had to use a typealias to define the dictionary so that swift would parse correctly. Here's an example based off of your snippet.
typealias myClosure = () -> Void
var data: [String: myClosure]? = [String: myClosure]()
data!["so:c0.onSelection"] = {() -> Void in
Debug.log(.Debug, message: "Hello, World!")
}
I have a different approach
I create a "holder" class to hold your closures something like this:
typealias SocialDownloadImageClosure = (image : UIImage?, error: NSError?) -> ()
typealias SocialDownloadInformationClosure = (userInfo : NSDictionary?, error: NSError?) -> ()
private class ClosureHolder
{
let imageClosure:SocialDownloadImageClosure?
let infoClosure:SocialDownloadInformationClosure?
init(infoClosure:SocialDownloadInformationClosure)
{
self.infoClosure = infoClosure
}
init(imageClosure:SocialDownloadImageClosure)
{
self.imageClosure = imageClosure
}
}
then i make the dictionary like this:
var requests = Dictionary<String,ClosureHolder>()
Now to add a closure to the dictionary just do this:
self.requests["so:c0.onSelection"] = ClosureHolder(completionHandler)
Connor is correct, I did try many ways to store variables and closures in the same dictionary, but I failed and couldn't parse it out, the swift decompiler will throw the error:
"Command failed due to signal: Segmentation fault: 11" (the hell is it?!)
For example:
//This won't work
var params:[String: Any] = ["x":100, "onFoundX": {println("I found X!")}]
if var onFoundX: (()->Void) = params["onFoundX"] as? (()->Void) {
onFoundX()
}
//This should work by separate into 2 dictionaries and declare the "typealias" obviously
var params:[String: Any] = ["x":"100"}]
var events:[String: (()->Void)] = ["onFoundX": {println("I found X!")]
if var onFoundX: (()->Void) = events["onFoundX"] as? (()->Void) {
onFoundX() // "I found X!"
}
if var x = events["x"] as? String {
println(x) // 100
}
I hope Swift will allow this to happen in the future..
Cheers!
This simple example helped me understand a bit more:
//Init dictionary with types (i.e. String type for key, Closure type for value):
var myDictionary: [String: ()->(String)] = [:]
//Make a closure that matches the closure signature above and assign to variable (i.e. no parameter and returns a String):
let sayHello: () -> (String) = {
return "Hello!"
}
//Add closure to dictionary with key:
myDictionary["myFunc"] = sayHello
//Access closure by known key and call it:
myDictionary["myFunc"]!() //"Hello!"