-
Notifications
You must be signed in to change notification settings - Fork 234
Increasing safety in loadFromNib #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…conforming to FrameInitializable protocol
|
Thanks for the PR! 👍 Indeed this is surprising that Anyway, that should definitely be addressed indeed. But I'm wondering why this I mean, maybe not all UIView subclasses would be guaranteed to have an Whatever the solution will be, it's an interesting problem, and might reveal some surprising behaviour of ObjC bridging vs Swift init rules… |
|
I think this is a compiler bug, because in runtime
The same happens even if replacing no-argument initializer with So, this |
|
I see. Worth filling a radar then. Wouldn't that change mean that we'd have to make every UIView we want to use here conform to FrameInitializable explicitly? 😢 |
|
You need to conform to Like in |
|
Oh you're right, indeed! Fair enough. |
|
Just a thought, but after re-reading https://oleb.net/blog/2016/12/protocols-have-semantics/ — and also given that this is merely a bug, I wonder if Introspection to see if |
|
Great post! Thanks! 👍 Probably it would be better to check if So I propose two solutions:
I would recommend the second solution, seems to be very elegant. Do you agree? |
|
Good point on the optionality of the init… Actually, even though the second solution is elegant, for our specific case it seems to complexify the solution to give such a closure just to let the responsibility to All that to say, if we continue that reasoning, we should even pass a closure So, in practice, wouldn't it be much simpler (in terms of "having a more simple and understandable API" but also in terms of architecture and readability) to… just not have a default value for that Then I think the two first solutions are not bringing that much simplicity compared to the already existing third one, right? So overall, I think the simple solution to this bug is to simply get rid of the default parameter value altogether. What do you think? Also, to balance the removal of the possibility to call |
|
Thinking about it more, even if for |
|
That's true we do not know which other frame to use. And if one needs to create a view directly in code then one probably knows which frame would be the best to use, so it makes sense to get rid of that frame from this closure. However, as you pointed out it is almost the same as passing in an instance to All things considered, it totally makes sense for NibOwnerLoadable to have an instance method instead of a static one! |
|
Cool 😎 Also don't forget to add an entry to the CHANGELOG.md file to credit yourself! (Note that this change would mean a major new version as it would be a breaking change in the API. But for the better) |
|
Sure! I will push a commit. Thanks. We could even change the instance method name to something more appropriate, since it is not just loading a view from nib (like in NibLoadable case) but rather filling out or populating the owner with the nib content. |
|
Just use I agree a more appropriate name for the instance methods could be nice. I think we should keep the term "load" in the method name though, as it mirrors the "Loadable" part in the protocol name. What about |
|
You're right, being consistent with the protocol name is better. |
|
Quick note: don't forget to also update the example project to use that new instance methods, and to update the README file accordingly too 😉 |
…t() instance method in NibOwnerLoadable protocol, removing the old static loadFromNib(owner:) method from NibOwnerLoadable protocol
…tionHeaderView and MyXIBIndexSquaceCell
|
@AliSoftware |
Sources/View/NibOwnerLoadable.swift
Outdated
| (and to which you want to add the XIB's views as subviews). | ||
| - returns: A `NibOwnerLoadable`, `UIView` instance | ||
| */ | ||
| @discardableResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method doesn't return any result anymore so @discardableResult can be removed here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@discardableResult and documentation comments too 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch!
README.md
Outdated
| Overriding `init?(coder:)` allows your `MyCustomWidget` custom view to load its content from the associated XIB `MyCustomWidget.xib` and add it as subviews of itself. | ||
|
|
||
| _💡 Note: overriding `init(frame:)`, even just to call `super.init(frame: frame)` might seems pointless, but seems necessary in some cases due to a strange issue with Swift and dynamic dispatch not being able to detect and call the superclass implementation all by itself: I've sometimes seen crashes when not implementing it explicitly, so better safe than sorry._ | ||
| _💡 Note: possible to override `init(frame:)`, in order to be able to create an instance of that view programatically and call `loadNibContent()` to fill with views if needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Note: it is also possible to …"
| - returns: A `NibOwnerLoadable`, `UIView` instance | ||
| */ | ||
| @discardableResult | ||
| static func loadFromNib(owner: Self = Self()) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that the new instance method doesn't return any value, @discardableResult can be removed too (as it doesn't make sense on a function returning Void)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh at first I thought my comment on this line had disappeared, so I reposted it as you saw above… but now I see it's present twice… on the same line… but presented as 2 separated comments if it was 2 different files?! GitHub bug?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering about that too. I don't know if that's github bug, however, it made me noticed that documentation comments needs to be changed 😄
|
|
||
| * Fixing table view controller scene to display cells above UITabBar | ||
| [@Skoti](https://github.com/Skoti) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rework your entry so that it follows the same format as the other entries.
In particular, use a period + 2 spaces at the end of the lines describing the changes so that its properly formatted when the markdown is rendered (as a "new line in the same paragraph")
May I suggest something along those lines instead?
## Unreleased
### Breaking changes
* `static func loadFromNib(owner:)` of `NibOwnerLoadable` has been replaced by instance method `func loadNibContent()`.
This is more consistent and also avoids possible crashes when used with `UIView` subclasses not implementing non-required initializers `init()`/`init(frame:)`.
[@Skoti](https://github.com/Skoti)
[#40](https://github.com/AliSoftware/Reusable/pull/40)
### Enhencements
* Fixing documentation typos for `CollectionHeaderView` and `MyXIBIndexSquaceCell`.
[@Skoti](https://github.com/Skoti)
* Fixing table view controller scene in Example project, to display cells above `UITabBar`.
[@Skoti](https://github.com/Skoti)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, although I'm gonna use "Enhancements"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops 😄 👍
|
Thanks for the changes! We're close to the solution I think; just some very little nitpickings before we can merge then we should be good to go 👍 |
| override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { | ||
| let view = MyHeaderTableView.loadFromNib() | ||
| let view = MyHeaderTableView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: self.tableView(tableView, heightForHeaderInSection: section))) | ||
| view.loadNibContent() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe to better demonstrate the possibility of loading NibOwnerLoadable views from code, it would be better in this case to override the init(frame: ) of MyHeaderTableView so it calls self.loadNibContent() in that subclass' initializer? (instead of having to think about calling loadNibContent() on every new MyHeaderTableView instance we create like here?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, I'm gonna use it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be the occasion to:
- Break that line 30 in 2 lines (
let frame = CGRect(…)+let view = MyHeaderTableView(frame: frame) - Add a comment just above this call to
initto remind the reader of the example project that "this calls the overriden init ofMyHeaderTableViewwhich loads its content from its Nib automatically vialoadNibContent()" or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like that?
"This calls the overriden init of MyHeaderTableView which loads its content from the default nib (see NibOwnerLoadable extension) automatically via loadNibContent() instance method."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, maybe just shorten a bit like this?
// See the overridden `MyHeaderTableView.init(frame:)` initializer, which
// automatically loads the view content from its nib using loadNibContent()…cumentation comments
…e view content from its nib using loadNibContent()
…e a proper markdown format
|
I'll leave the CI finish its work tonight, I'm going to bed. Will merge it tomorrow (and probably do a release alongside) 😉 Thanks again for spotting this bug and for your work on that PR, that was awesome 👍 |
|
Thanks for your big help and great ideas too 😄 |
|
Yeah, idk what's wrong with Travis, I'll have to migrate to Circle-CI some time! |
In NibOwnerLoadable extension there is this dangerous line where it is assumed that a class which inherits from UIView also implements
init(frame: CGRect)initializer:static func loadFromNib(owner: Self = Self()) -> Selfand if it does not it will crash in runtime.
I'm surprised that this even compiles, because according to the Swift documentation:
and since:
required init?(coder aDecoder: NSCoder)is the only required initializer in UIView class then it should be the only initializer to be possibly called onSelfwhich is a subclass of UIView in this extension.It is getting even more fun when you realize that this initializer is not called directly because in loadFromNib declaration, no-argument initializer
init()fromNSObjectis used (which underneath seems to fallback toinit(frame:)withCGRect.zeropassed as an argument).This is even more surprising in terms of compilation since the code below (which mimics NSObject and UIView initializers) does not compile:
and produces compile-time error saying:
or if we change
where Self : Subclasstowhere Self : Basewhich is the best description of what is happening.
But for some reasons it compiles when using UIView ...
Having all this in mind, I have created a pull request for increased safety when using
init(frame: CGRect)to explicitly say that this is possible by implementingFrameInitializableprotocol.So
loadFromNib()with no arguments version is added to an extension of NibOwnerLoadable for UIView implementingFrameInitializableprotocol and uses the argument version from NibOwnerLoadable extension for UIView. Consequently, the non-argument version does not need@discardableResultbecause one for sure needs to use the returned object, otherwise it is pointless calling this function.This is a simple and small change and it expresses more explicitly what is actually needed for UIView to safely use
loadFromNib()without owner parameter.