Type Inference in tuples? - ios

let http404Error = (statusCode: 404, statusMessage: "Not found")
print(http404Error.0, http404Error.1)
I got some questions and "problems" with tuples:
Question 1:
But what if I want the statusCode to be an Int and an Int only?
Because "statusCode: Int = 404" doesn´t seem to work?
Question 2:
What if I want to shorten the part "print(http404Error.0, http404Error.1)?
Is there a short way to write it, something like print(http404Error.[0, 1])?
Thanks for your help :)

You can try
let http404Error:(Int,String) = (statusCode: 404, statusMessage: "Not found")

For specifying the type, you can define a type alias like:
typealias HttpStatus = (statusCode: Int, statusMessage: String)
let http404Error = HttpStatus(403, "Not found")
print(http404Error.0, http404Error.1)
For shortening the print statement, I don't think there is an easy way to do that. One thing you can do is, you can create a custom function which takes the tuple as argument, formats the values and returns a string.
func getStatus(_ status: HttpStatus) -> String {
return "\(status.statusCode) \(status.statusMessage)"
}
print(getStatus(http404Error))

Related

iOS Swift4 how to reconcile T.Type and type(of:) to pass dynamic class type as function parameter?

I'm trying implement generic storage of configuration parameters by using class type string as a dictionary key. The idea is that the retrieve function will return an object of proper type. Each type is unique in the storage. However, when I call the function, I'm getting a Swift compiler error and am not sure how interpret it:
Compiler Error:
Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
I checked the documentation, and it seems like the type(of:) method is supposed to return runtime class, while the compiler makes it look like it's complaining because it thinks I'm passing a type of Any
How do I pass Swift class name as a function parameter without using an instance of that class?
func retrieve<T>(type: T.Type) -> T? {
let valueKey = String(describing: type)
print("retrieving: \(valueKey)")
return dictionary[valueKey] as? T
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let t = type(of: testCase)
let retrieved = subject.retrieve(type: t)
//check for equality
}
//this works
expect(subject.retrieve(type: Int.self)).to(equal(1))
expect(subject.retrieve(type: Double.self)).to(equal(2.0))
expect(subject.retrieve(type: String.self)).to(equal("text"))
I've done more testing, and see that it appears my array does not honor the type(of: ) documentation, and this function returns the same object as "Any":
func retrieve<T>(sample: T) -> T? {
let valueKey = String(describing: type(of: sample))
print("retrieving: \(valueKey)") //always retrieves same object "Any"
return dictionary[valueKey] as? T
}
Updated: Thank you for responses, to clarify - test cases were intended to begin with simple types, then progress to more complex classes. The actual implementation would store completely unique instances of custom types, not Strings or Ints.
let testCases: [Any] = [ConnectionConfig(...),
AccountID("testID"),
AccountName("testName")]
The tests recognize the generic nature of the retrieve function and assign appropriate types, as evidenced by code completion:
expect(subject.retrieve(type: ConnectionConfig.self)?.ip).to(equal("defaultIP"))
expect(subject.retrieve(type: AccountID.self)?.value).to(equal("testId"))
The intended end use within RxSwift context: provide the generic storage to a class and allow it to pull the appropriate values for configuration parameters. If no value exists, an error is thrown and is handled by a separate error handler:
class RxConfigConsumer: ConfigConsumer {
var connection: ConnectionConfig?
var accountID: AccountID?
init(with provider: ConfigProvider) {
connection = provider.retrieve(type: ConnectionConfig.self)
accountID = provider.retrieve(type: AccountID.self)
//etc
}
}
The combination of a generic with a metatype (.Type) is very weird and is probably what's tripping you up. If you get rid of the generic things work as you would expect:
func retrieve(_ T:Any.Type) {
print(type(of:T))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type
If you really want the generic, then get rid of the .Type and write it like this:
func retrieve<T>(_ t:T) {
print(type(of:t))
}
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
retrieve(type(of:testCase))
}
// Int.Type, Double.Type, String.Type
Even then, however, it's unclear to me what the point is of passing the metatype.
The short answer is what you're trying to do is impossible, because there is no way to type-annotate the following line of code:
let retrieved = subject.retrieve(type: t)
What is the static type, known at compile-time, of retrieved? It can't change at run-time. It certainly can't change from iteration to iteration. The compiler needs to allocate space for it. How much space does it require? Does it require space on the stack or heap? There's no way to know. The best we can say is that it's Any and put a box around it. 1 doesn't even have a proper type anyway. It's just an integer literal. It could be a Float or many other things (try let x: Float = 1 and see).
The answer is you can't build a loop like this. Your individual test cases are the right ones. Once you create an [Any], it is very difficult to get "real" types back out. Avoid it. If you have a more concrete problem beyond the example you've given, we can discuss how to deal with that, but I believe outside of a unit test, this specific problem shouldn't come up anyway.
This is an interesting question and you can run the following codes in a playground.
The first step is to solve the T.Type parameter. It's hard to put it into a function call. So to achieve your goal, we can use T but T.Type.
class MySubject {
var dictionary : [String : Any] = [:]
func retrieve<T>(type1: T) -> T? {
let valueKey = String(describing: (type(of: type1)))
print("retrieving: \(valueKey)")
return dictionary[valueKey] as? T
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
}
var subject : MySubject = MySubject()
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let retrieved = subject.retrieve(type1: testCase)
//check for equality
}
The compilation is correct. But as you said, the return value is nil as a result of a generic retrieve Function. In order to achieve your goal, we may skip the generic way, use Any directly.
class MySubject {
var dictionary : [String : Any] = [:]
func retrieve(type1: Any) -> Any? {
let valueKey = String(describing: (type(of: type1)))
print("retrieving: \(valueKey)")
return dictionary[valueKey]
}
func update(with value: Any) {
let valueKey = String(describing: type(of: value))
print("Updating: \(valueKey)")
dictionary[valueKey] = value
}
}
var subject : MySubject = MySubject()
let testCases: [Any] = [1, 2.0, "text"]
for testCase in testCases {
subject.update(with: testCase)
//Compiler Error: Cannot invoke 'retrieve' with an argument list of type '(type: Any.Type)
let retrieved = subject.retrieve(type1: testCase)
//check for equality
}
Currently everything is perfect as you wish. But this brings up an interesting thought about generic. Is it O.K. or right to use generic here? As we know, there is a presumption in generic, which is represented by letters T, U, V. They have one common meaning: Type. When we try to use generic, we assume every parameter should have only one unique type. So in first case, Any is the only type and should be accepted without question in a generic call. There is no other type will be revealed during function call.
This kind of misunderstanding roots from the use of "let testCases: [Any] = [1, 2.0, "text"]. Although swift allows you to write this way, they are not a normal array. They are a list which contains different type essentially. So you can ignore fancy generic here without any regrets. Just pick the Any to solve your problem.

Hangman Program 2

I have asked a question before about this program, but it seems that not all problems are resolved. I am currently experiencing an error that states: "Cannot convert value of type 'String' to expected argument type '_Element' (aka 'Character') on the "guard let indexInWord" line:
guard let letterIndex = letters.indexOf(sender)
else { return }
let letter = letterArray[letterIndex]
guard let indexInWord = word.characters.indexOf(letter)
else {
print("no such letter in this word")
return
}
// since we have spaces between dashes, we need to calc index this way
let indexInDashedString = indexInWord * 2
var dashString = wordLabel.text
dashString[indexInDashedString] = letter
wordLabel.text = dashString
I tried converting the String 'letter' to Character but it only resulted in more errors. I was wondering how I can possibly convert String to argument type "_Element." Please help.
It is hard to treat a string like a list in swift, mostly because the String.characters is not a typical array. Running a for loop on that works, but if you are looking for a specific character given an index, it is a bit more difficult. What I like doing is adding this function to the string class.
extenstion String {
func getChars() -> [String] {
var chars:[String] = []
for char in characters {
chars.append(String(char))
}
return chars
}
}
I would use this to define a variable when you receive input, then check this instead of String.characters

Cannot call value of non-function type 'String'

I'm trying to pass the ILTItem variable into my ILTViewController, triggered by AppDelegate.swift when the user launches my app via a deeplink.
The code I have errors with:
Cannot call value of non-function type 'String'
on the line where I define ilt.
Here's the code I have at the moment:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var ilt = ILT(homeworkID: 1234, title: "History ILT", subject: "History", teacher: "Miss A Smith", teacherCode: "asmith", studentID: 12345, description: "Description....", due: 1450137600, status: "In Progress", hasAttachments: true)
var newVC = ILTViewController()
newVC.ILTitem = ilt
appDelegate.window?.addSubview(newVC.view)
Why could this be? In my ILTViewController class I have:
class ILTViewController: UIViewController {
// accept the incoming ILT struct
var ILTitem: ILT!
IlT Struct Declaration:
struct ILT {
let homeworkID: Int
let title: String
let subject: String
let teacher: String
let teacherCode: String
let studentID: Int
let description: String
let due: Double
let status: String
let hasAttachments: Bool
}
The error is telling you that you are trying to call a String instead of a method (struct constructor in your case). You've probably declared a String variable named ILT (uppercase) somewhere else and that's why it fails.
Your posted code works fine so the error must be somewhere else in your code.
Wrap your let statement in if eg:
if let xxx = yyy {
... do something
}
Works:
let works = ["foo", "bar"].first(where: { ($0 == "foo") } )
let works = ["foo", "bar"].first(where: { (_ in true) } )
Fails:
let fails = ["foo", "bar"].first(where: { (true) } )
// Cannot call value of a non-function type 'String?'
You must be sure to use the parameter ($0 or _ in) in the closure expression.
Use _ in or $0 to discard or reference the parameter. You cannot simple move directly into the closure body and return true or you will receive this (extremely unhelpful) error.
Had a similar issue in this code
array.first { $0 == item }
The problem was with $0 not conforming to Equatable protocol. In my case it conformed to NSObjectProtocol and simple pointer comparison was enough, so I fixed the issue with
array.first { $0 === item }
So I had a problem with a similar error message. I am writing a structure to handle Scalars for my library and needed a square root.
Error was
Cannot call value of non-function type 'Vsip.Scalar'
when calling sqrt. Fixed it by explicitly calling sqrt as shown below. Hope this helps.
public var sqrt: Scalar {
switch self.type {
case .f:
return Scalar(sqrtf(self.realf))
case .d:
let x = Foundation.sqrt(self.reald)
return Scalar(x)
case .cf:
return Scalar(vsip_csqrt_f(self.vsip_cf))
case .cd:
return Scalar(vsip_csqrt_d(self.vsip_cd))
default:
precondition(false, "sqrt not supported for type \(self.type)")
}
}
In messing around with Swift's various closure syntax types + autocomplete I often find myself in a mess of variables, return types, and using too few or too many sets of () or {}
I ended up with something like:
filenames.first(where: { $0 == filename } ) {
}
Which was giving the error
Cannot call value of non-function type
Solution was to remove the trailing { }, which is not correct in this form.
Should just be filenames.first(where: { $0 == filename } )
Check that you have not incorrectly applied a set of braces to the end of your non-function, etc., or some other hard to spot error in your current chosen Swift closure syntax.
Check if you haven't missed ? for optionals.
That error can be shown for a different issue in a heavy-loaded schemes, like when I forget to mark optional.
services.first(where: { $0.category?.id == categoryID })

Generic Swift function to test for object type

I'm trying to write a function that takes an object and a type as arguments and returns a boolean indicating whether or not the object is of the given type. There doesn't seem to be a Type type, so I'm not sure how to do this. The best I've been able to do is
func objectIsType<T>(object: AnyObject, someObjectOfType: T) -> Bool {
return object is T
}
So I can do objectIsType(x, 5), to test if x is an Int or objectIsType(x, "hi") to see if it's a string, but I'd like to be able to call objectIsType(x, Int) to see if x is an Int and objectIsType(x, String) to see if it's a String. Is something like this possible?
Edit:
Airspeed Velocity improved my function and made a great point about it doing exactly what is already does. The new function is this:
func objectIsType<T>(object: Any, someObjectOfType: T.Type) -> Bool {
return object is T
}
What I'm trying to do is to validate that the values of a [String: Any] dictionary are of the type that I expect. For instance:
let validator: [String: Any.Type] = [
"gimme an int": Int.self,
"this better be a string": String.self
]
let validatee: [String: Any] = [
"gimme an int": 3,
"this better be a string": "it is!"
]
for (key, type) in validator {
if !objectIsType(validatee[key], type) {
selfDestruct()
}
}
But I'm getting the error, <>protocol.Type is not convertible to T.type. I've looked at the Metatype documentation, but I'm still a bit confused.
If you want to supply a type as the argument, not a value, you can do the following:
func objectIsType<T>(object: Any, someObjectOfType: T.Type) -> Bool {
return object is T
}
let a: Any = 1
objectIsType(a, Int.self) // returns true
NB, AnyObject can only refer to classes, not structs or enums. Int and String are structs. If you change your code, as I have above, to take an Any, it works with structs too.
It might have seemed like your original worked without this change, but really what was happening was the interop was converting your Int into an NSNumber which is a bit of a roundabout way of doing things and won't adapt to the metatype-based approach.
But the question really is, why do you think you need this? is already does exactly this.

Returning a tuple in Swift

sorry for such a basic question but I ve only just started working with tuples
this is my code
func test() -> (authorName:String, numberOfViews:Int) {
let author : String = ""
let numberViews = 0
return(authorName : author, numberOfView : numberViews)
}
can anyone provide the correct way to do this
thanks in advance
according to the Apple's swift book:
func test() -> (authorName:String, numberOfViews:Int) {
let author : String = ""
let numberViews = 0
return(author, numberViews)
}
you define the return object at the declaration. and in the return statement just put the values.
For create a tuple simply put it in normal brackets and separate each other with comas, you also can do it on te return function
Example :
let exampleTuple = (23, "A string", 5.583)
The article from Apple :
Tuples group multiple values into a single compound value. The values within a tuple can be of any type and do not have to be of the same type as each other.In this example, (404, "Not Found") is a tuple that describes an HTTP status code. An HTTP status code is a special value returned by a web server whenever you request a web page. A status code of 404 Not Found is returned if you request a webpage that doesn’t exist.
let http404Error = (404, "Not Found")

Resources