Fable.Lit - Call code in sub-component from user code - lit

I have two Lit components (Main and Sub, Main embeds Sub) defined as follows:
module MySubComponent
type IMySubComponent =
inherit Element
abstract member MyMethod: string -> unit
let register() = ()
...
[<LitElement("my-sub-component")>]
let mySubComponent() =
...
let myFunction() = <do something (e.g. change background color)>
...
module MyMainComponent
open MySubComponent
[<LitElement("my-main-component")>]
let myMainComponent() =
...
let _subRef = Hook.useRef<IMySubComponent>()
let buttonClick _ =
let sub = _subRef.Value.Value
sub.MyMethod()
...
let mySubComponent() = Lit.ofImport(MySubComponent.register, fun _ -> html $"""<my-sub-component {Lit.refValue _subRef}></my-sub-component>""")
html $"""
<button #click={Ev(buttonClick)}>Button</button>
{mySubComponent()}
"""
...
With this, clicking the button in the Main component raises an error 'MyMethod is not a function', which is expected.
What I am looking to obtain is that when sub.MyMethod() is called in the main component, this results in a call of the function myFunction() in the Sub component.
I know that there are alternatives to obtain similar results (through properties and custom events rather than Lit.useRef, for example), but I think the Lit.refValue is more natural and should be possible.
What is the code missing to make this call possible?

Related

What is the difference between "lazy var funcName: () -> Void" and regular function declaration in Swift

I have recently encountered such code block when I was working on a different codebase.
I was wondering is there any significant difference (in terms of memory impact etc.) between these two declarations other than syntactic ease? I regularly use lazy stored properties but I couldn't visualize how a function can be "lazy". Can you guys also enlighten me how functions are processed (or share an article explaining this topic) by the swift compiler as well?
Thanks in advance, wish you bug free codes.
A few differences I can think of off the top of my head:
With functions, you can use parameter labels to make the call site more readable, but since you can't add parameter labels to function types, you can't do that with the lazy var declaration.
// doesn't work
lazy var say: (message: String, to person: String) -> Void = {
...
}
// works
func say(message: String, to person: String) {
...
}
You can only invoke a function type with no parameter labels at all :( say("Hello", "Sweeper") instead of say(message: "Hello", to: "Sweeper").
lazy vars are variables, so someone could just change them:
helloFunc = { /* something else */ }
you could prevent this by adding a private setter, but that still allows setting it from within the same class. With a function, its implementation can never be changed at runtime.
lazy vars cannot be generic. Functions can.
// doesn't work
lazy var someGenericThing<T>: (T) -> Void = {
...
}
// works
func someGenericThing<T>(x: T) {
...
}
You might be able to work around this by making the enclosing type generic, but that changes the semantics of the entire type. With functions, you don't need to do that at all.
If you're implementing a language, the magic you need to implement 'lazy' as a feature is to make the value provided silently be wrapped in a function (of no arguments) that is automatically evaluated the first time the property is read.
So for example if you have
var x = SomeObject() // eagerly construct an instance of SomeObject
and
lazy var x = SomeObject() // construct an instance if/when someone reads x
behind the scenes you have, for the lazy var x, either x has not yet been read, or it has been and therefore has a value. So this is like
enum Lazy<T> {
case notReadYet(() -> T) // either you have a function that makes the initial T
case read(T) // or you have a value for T
}
var thing: Lazy<SomeObject> = .notReadYet { return SomeObject() }
and with a suitable property wrapper or other language magic, wrap the calls to the getter for thing with a switch on the case, which checks if you are in the notReadYet case, and if so automatically invoke the function to produce the T and then set the property to .read(t) for the particular value of t.
If you substitute in your type: () -> Void from your example you have something like:
var thing: Lazy<() -> Void> = .notReadYet({ return { print("Hello") } })
This is all rather odd because it's a void value and not much point making it lazy, e.g. it's not expensive to compute; it doesn't help break a two-phase initialisation loop, so why do it?

How to set the value of lazy computed property via a closure in Swift?

So I've been stuck on this problem for a while, and can't find questions addressing my particular problem online.
I am trying to set the value in description, which is defined as a lazy computed property and utilizes a self-executing closure.
To get the book's description, I make an API call, passing in another handler to the API completion handler so that I can set the book's description inside the lazy computed property.
I know my below code is wrong, since I get the error:
Cannot convert value of type '()' to specified type 'String'
class Book : NSObject {
func getInfo(for name: String, handler: #escaping (_ string: String) -> String) {
let task = URLSession.shared.dataTask(with: "foo_book.com" + name) { (data, response, error) in
guard let data = data else {return}
descriptionStr = String(data: data, encoding: .utf8) ?? "No description found"
handler(descriptionStr)
}
}
lazy var description: String = {
getInfo(for: self.name) { str in
return str
}
}()
}
How can I set the value of description?
I've tried two methods. Using a while loop to wait for a boolean: inelegant and defeats the purpose of async. Using a temp variable inside description - doesn't work because getInfo returns before the API call can finish.
In case you wonder my use case: I want to display books as individual views in a table view, but I don't want to make api calls for each book when I open the tableview. Thus, I want to lazily make the API call. Since the descriptions should be invariant, I'm choosing to make it a lazy computed property since it will only be computed once.
Edit: For those who are wondering, my solution was as the comments mentioned below. My approach wasn't correct - instead of trying to asynchronously set a property, I made a method and fetched the description in the view controller.
Already the explanation in comments are enough for what's going wrong, I will just add on the solution to your use case.
I want to display books as individual views in a table view, but I
don't want to make api calls for each book when I open the tableview.
Thus, I want to lazily make the API call.
First of all, does making lazy here make sense. Whenever in future you will call description, you are keeping a reference for URLSession and you will do it for all the books. Looks like you will easily create a memory leak.
Second, task.resume() is required in getInfo method.
Third, your model(Book) should not make the request. Why? think, I have given one reason above. Async does mean parallel, all these network calls are in the queue, If you have many models too many networks calls in the event loop.
You can shift network call responsibility to service may be BookService and then have a method like this BookService.getInfo(_ by: name). You Book model should be a dumb class.
class Book {
let description: String
init(desc: String) {
self.description = desc
}
}
Now your controller/Interactor would take care of calling the service to get info. Do the lazy call here.
class BookTableViewController: ViewController {
init(bookService: BookService, book: [String]) {
}
# you can call when you want to show this book
func loadBook(_ name: String) -> Book {
BookService.getInfo(name).map { Book(desc: str) }
}
func tableView(UITableView, didSelectRowAt: IndexPath) {
let bookName = ....
# This is lazy loading
let book = loadBook(bookName)
showThisBook()
}
}
Here, you can do the lazy call for loadBook. Hope this helps.

Swift FileManager.ContentsOfDirectory returns nil

I am very new to Apple development and I am writing a simple app where i am trying to access certain images inside my project. To do this I made a simple function which returns (in theory) the file names in an array.
class Tools{
public func GetFiles(filePath : String) -> Array<NSString>{
let fm = FileManager.default
let path = Bundle.main.resourcePath! + filePath
let items = try? fm.contentsOfDirectory(atPath: path)
return items! as Array<NSString>;
}
}
And in my main class I am calling this function and passing along the directory where it can find my files
class LoadingView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tool = Tools()
var logoAnim = tool.GetFiles(filePath: "/Path/To/My/Files");
}
}
That is all I'm doing code-wise... Now here is how i have my asset structure
And I also made sure to include each file as a target member of this product
When I debug this it returns nil every time. Can someone help me understand what am I doing wrong? Is there a special way I need to reference the path, or, am I missing an extra step?

The following code works when I manually pass the value but fails when I unwrap the value

I just started learning Swift as my first coding language. My current challenge is trying to automate the transitions from a current level setup as LevelOne.sks to another level also created in Xcode level editor as LevelTwo.sks. What I'm attempting to do is trigger a transition to the next level with the following set of code.
In my base scene I have this function to send the player to the next level
private func goToNextLevel(nextLevel: String) {
//When hard coding the arguments as...
//loadScene(withIdentifier: .levelTwo)
//The level two scene loads and is playable...however,
//When trying to change the level argument in code
// by passing a nextLevel variable
// the optional unwraps nil and crashes the app.
loadScene(withIdentifier: SceneIdentifier(rawValue: nextLevel)!)
}
This is then passed to a SceneLoadManager File
enum SceneIdentifier: String {
case levelOne = "LevelOne"
case levelTwo = "LevelTwo"
// case levelThree = "LevelThree"
}
private let sceneSize = CGSize(width: 768, height: 1024)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
func loadScene(withIdentifier identifier: SceneIdentifier) {
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
let nextLevel = SKScene(fileNamed: identifier.rawValue)
nextLevel?.scaleMode = .aspectFill
self.view?.presentScene(nextLevel!, transition: reveal)
}
I think this has something to do with how I'm trying to set nextLevel. Currently I'm setting this up as follows
let nxtLvl = String?
nxtLvl = ".levelOne"
goToNextLevel(nextLevel: nxtLvl)
Hopefully you can make sense of what I'm trying to achieve and that I'm at least close to being on the right track here. Any help would be greatly appreciated. Thanks!
What could really help you to solve :
SceneIdentifier.levelOne.rawValue returns this -> "LevelOne"
BUT
SceneIdentifier(rawValue : "LevelOne") returns -> SceneIdentifier.levelOne
See the difference ?
In the first you get the string , in that case what you want
And in the second you get the "left member" (I don't know the therm)
An example to see clearer if you have an enum :
enum SceneIdentifier: String {
case case1 = "LevelOne"
case case2 = "LevelTwo"
}
SceneIdentifier(rawValue : "LevelOne") !
returns this : SceneIdentifier.case1
and SceneIdentifier.case1.rawValue
returns this : "levelOne"
Since you are initializing with a raw value, you need to actually use the raw value.
let nxtLvl = String?
nxtLvl = "LevelOne" //this is the rawValue of the .levelOne case
goToNextLevel(nextLevel: nxtLvl)
Alternatively, you could change your API so that goToNextLevel simply accepts a SceneIdentifier like this.
//tip: use the parameter name as part of the method name
private func goTo(nextLevel: SceneIdentifier) {
//Then calling the function
goTo(nextLevel: .levelOne)
Though, that might not be appropriate in the larger scope of your API.

How to determine which object/class is calling a global Swift function?

With this code:
func externalFunc() {
println("How can I know which object/class is calling me?")
}
class Test {
func callExternalFunc() {
externalFunc()
}
}
In the Objective-C runtime objc_msgSend passes two hidden parameters to every message we send. They are self and _cmd. (Source)
In the above example, is there any way to know who is calling externalFunc?
I'm not sure if there is a way to obtain this automatically, but you can get this info if you add a default param of type String to the function and set it to #function.
For example...
func externalFunc(callingFunctionName: String = #function) {
println("Calling Function: \(callingFunctionName)")
}
Then you would call it without the added default param...
let test = Test()
test.callExternalFunc()
And it would print the following...
"Calling Function: callExternalFunc()"
If you are willing to modify the method signature you could do something like below:
func externalFunc(file: String = #file, line: Int = #line) {
print("calling File:\(file) from Line:\(line)")
}
From apple's swift blog
Swift borrows a clever feature from the D language: these identifiers
(__FILE__ & __LINE__ ) expand to the location of the caller when
evaluated in a default argument list.
Note that __FILE__ and __LINE__ have been depreciated in Swift 2.2 and have been removed in Swift 3. They are replaced by #file, and #line.
Here's a great utility class I found on github:
https://github.com/nurun/swiftcallstacktrace
Use like this:
let callingMethodInfo = CallStackAnalyser.getCallingClassAndMethodInScope(false)
if let callingMethodInfo = callingMethodInfo {
NSLog("class: %#", callingMethodInfo.0)
NSLog("method: %#", callingMethodInfo.1)
}
In your question you are mentioning self and _cmd.
self is accessible in Swift exactly in the same way as in Obj-C (which is logical).
_cmd (selector of the current method) is not accessible. There is no reason for it to be accessible, Swift doesn't use selectors outside Obj-C contexts (in pure Swift you cannot call selectors dynamically). The only use case for it is to print the name of the current function for debugging purposes. The same can be achieved in Obj-C (or C) using __FUNCTION__ macro. The same can be achieved in Swift:
func getCurrentFunctionName(functionName: StaticString = #function) -> String {
return String(functionName)
}
func externalFunc() {
print("Function name: \(getCurrentFunctionName())") // prints "externalFunc"
}
Note that in your example externalFunc is a function, not a method. Even in Obj-C neither self or _cmd wouldn't be available for it.
If you want to know who has called your method (and I really suppose you want to know it for debugging purposes), then you can inspect your call stack:
func externalFunc() {
let stackSymbols = NSThread.callStackSymbols()
stackSymbols.forEach {
let components = $0.stringByReplacingOccurrencesOfString(
"\\s+",
withString: " ",
options: .RegularExpressionSearch,
range: nil
).componentsSeparatedByString(" ")
let name = components[3]
let demangledName = _stdlib_demangleName(name)
print("\(demangledName)")
}
}
which prints (for my project called SwiftTest):
SwiftTest.externalFunc () -> ()
SwiftTest.Test.callExternalFunc () -> ()
main
start
0x0