Convenience initialization of UINavigationController subclass makes subclass constant init twice - ios

I have subclasses of UINavigationController and UITableViewController.
To initialize subclasses I decided to use some convenience init methods that call some designated initializer of the superclass. Also, each subclass has some let constant:
let someValue: SomeClass = SomeClass()
Each class is successfully initialized by calling its newly created convenience init method.
The problem is that the let constant is initialized TWICE in UINavigationController subclass.
import UIKit
import PlaygroundSupport
final class Navigation: UINavigationController {
convenience init(anyObject: Any) {
self.init(rootViewController: UIViewController())
}
let service = Constant("Constant Initialization -> Navigation")
}
final class Table: UITableViewController {
convenience init(anyObject: Any) {
self.init(style: .plain)
}
let service = Constant("Constant Initialization -> Table")
}
class Constant: NSObject {
init(_ string: String) {
super.init()
debugPrint(string)
}
}
Navigation(anyObject: NSNull())
Table(anyObject: NSNull())
Are we allowed to use convenience init like above? Why?
Why is the convenience init behavior is different in these two cases?
Checked with: Version 8.2 beta (8C30a), Version 8.2 (8C38), Version 8.2.1 (8C1002)
P.S. Playground log of the code above:
"Constant Initialization -> Navigation"
"Constant Initialization -> Navigation"
"Constant Initialization -> Table"

So this seems to be weirdly "expected behavior" with Swift. The reason it's happening is due to the constant initialized property service. Namely, this post summarizes the issue your seeing pretty well: blog post
Essentially, the Obj-C underlying super classes are leaking memory and your service property is initialized twice because of this pattern of Obj-C initialization:
- (id)init {
self = [super init];
if (self) {
self = [[self.class alloc] initWithNibName:nil bundle:nil];
}
return self;
}
A simple solution to avoid this is the following pattern:
import UIKit
final class NavigationController: UINavigationController {
var service: ObjectTest?
convenience init() {
self.init(rootViewController: UIViewController())
self.service = ObjectTest("init nav")
}
}
class ObjectTest: NSObject{
init(_ string: String) {
super.init()
print(string)
}
}
All that's changing from your implementation is initializing the service only after your class itself is initialized.
Another solution, though, is to use the designated initializer for the superclass your initializing. Meaning that you don't use a convenience initializer which would employ the above Obj-C initialization pattern.

Related

Property not initialized at super.init call using MVVM with convenience init

I have a View Model that subclasses NSObject as it is my UICollectionViewDataSource
I'd like to pass a service into this via dependancy injection.
I'm getting an error though
Property 'self.chatService' not initialised at super.init call
class ChatQuestionsViewModel: NSObject {
fileprivate var chatService: ChatService
convenience init(chatService: ChatService = ChatService()) {
self.init()
self.chatService = chatService
}
private override init() {
super.init()
}
}
And it appears to be focused on the super.init() line.
How can I initialise this class? I am unsure what I am doing wrong.
You cannot have a convenience initializer in this case. The rules of initializers state that you have to guarantee that all stored properties get initialized.
Convenience initializers are just that: for convenience only. This means it must not be necessary to use them to create an instance of the object. However if code uses your only non-convenience init, there's no initialization of chatService. (Never mind that your non-convenience init is private; that doesn't help).
Fixed result:
class ChatQuestionsViewModel: NSObject {
fileprivate var chatService: ChatService
init(chatService: ChatService = ChatService()) {
self.chatService = chatService
super.init()
}
}

Swift 3.1 deprecates initialize(). How can I achieve the same thing?

Objective-C declares a class function, initialize(), that is run once for each class, before it is used. It is often used as an entry point for exchanging method implementations (swizzling), among other things.
Swift 3.1 deprecates this function with a warning:
Method 'initialize()' defines Objective-C class method 'initialize',
which is not guaranteed to be invoked by Swift and will be disallowed
in future versions
How can this be resolved, while still maintaining the same behaviour and features that I currently implement using the initialize() entry point?
Easy/Simple Solution
A common app entry point is an application delegate's applicationDidFinishLaunching. We could simply add a static function to each class that we want to notify on initialization, and call it from here.
This first solution is simple and easy to understand. For most cases, this is what I'd recommend. Although the next solution provides results that are more similar to the original initialize() function, it also results in slightly longer app start up times. I no longer think
it is worth the effort, performance degradation, or code complexity in most cases. Simple code is good code.
Read on for another option. You may have reason to need it (or perhaps parts of it).
Not So Simple Solution
The first solution doesn't necessarily scale so well. And what if you are building a framework, where you'd like your code to run without anyone needing to call it from the application delegate?
Step One
Define the following Swift code. The purpose is to provide a simple entry point for any class that you would like to imbue with behavior akin to initialize() - this can now be done simply by conforming to SelfAware. It also provides a single function to run this behavior for every conforming class.
protocol SelfAware: class {
static func awake()
}
class NothingToSeeHere {
static func harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)
}
}
Step Two
That's all good and well, but we still need a way to actually run the function we defined, i.e. NothingToSeeHere.harmlessFunction(), on application startup. Previously, this answer suggested using the Objective-C code to do this. However, it seems that we can do what we need using only Swift. For macOS or other platforms where UIApplication is not available, a variation of the following will be needed.
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}
}
Step Three
We now have an entry point at application startup, and a way to hook into this from classes of your choice. All that is left to do: instead of implementing initialize(), conform to SelfAware and implement the defined method, awake().
My approach is essentially the same as adib's. Here's an example from a desktop application that uses Core Data; the goal here is to register our custom transformer before any code mentions it:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
override init() {
super.init()
AppDelegate.doInitialize
}
static let doInitialize : Void = {
// set up transformer
ValueTransformer.setValueTransformer(DateToDayOfWeekTransformer(), forName: .DateToDayOfWeekTransformer)
}()
// ...
}
The nice thing is that this works for any class, just as initialize did, provided you cover all your bases — that is, you must implement every initializer. Here's an example of a text view that configures its own appearance proxy once before any instances have a chance to appear onscreen; the example is artificial but the encapsulation is extremely nice:
class CustomTextView : UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame:frame, textContainer: textContainer)
CustomTextView.doInitialize
}
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
CustomTextView.doInitialize
}
static let doInitialize : Void = {
CustomTextView.appearance().backgroundColor = .green
}()
}
That demonstrates the advantage of this approach much better than the app delegate does. There is only one app delegate instance, so the problem isn't very interesting; but there can be many CustomTextView instances. Nevertheless, the line CustomTextView.appearance().backgroundColor = .green will be executed only once, as the first instance is created, because it is part of the initializer for a static property. That is very similar to the behavior of the class method initialize.
If you want to fix your Method Swizzling in Pure Swift way:
public protocol SwizzlingInjection: class {
static func inject()
}
class SwizzlingHelper {
private static let doOnce: Any? = {
UILabel.inject()
return nil
}()
static func enableInjection() {
_ = SwizzlingHelper.doOnce
}
}
extension UIApplication {
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
SwizzlingHelper.enableInjection()
return super.next
}
}
extension UILabel: SwizzlingInjection
{
public static func inject() {
// make sure this isn't a subclass
guard self === UILabel.self else { return }
// Do your own method_exchangeImplementations(originalMethod, swizzledMethod) here
}
}
Since the objc_getClassList is Objective-C and it cannot get the superclass (e.g. UILabel) but all the subclasses only, but for UIKit related swizzling we just want to run it once on the superclass. Just run inject() on each target class instead of for-looping your whole project classes.
A slight addition to #JordanSmith's excellent class which ensures that each awake() is only called once:
protocol SelfAware: class {
static func awake()
}
#objc class NothingToSeeHere: NSObject {
private static let doOnce: Any? = {
_harmlessFunction()
}()
static func harmlessFunction() {
_ = NothingToSeeHere.doOnce
}
private static func _harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)
}
}
You can also use static variables since those are already lazy and refer them in your top-level objects' initializers. This would be useful for app extensions and the like which doesn't have an application delegate.
class Foo {
static let classInit : () = {
// do your global initialization here
}()
init() {
// just reference it so that the variable is initialized
Foo.classInit
}
}
If you prefer Pure Swift™! then my solution to this kind of thing is running at _UIApplicationMainPreparations time to kick things off:
#UIApplicationMain
private final class OurAppDelegate: FunctionalApplicationDelegate {
// OurAppDelegate() constructs these at _UIApplicationMainPreparations time
private let allHandlers: [ApplicationDelegateHandler] = [
WindowHandler(),
FeedbackHandler(),
...
Pattern here is I'm avoiding the Massive Application Delegate problem by decomposing UIApplicationDelegate into various protocols that individual handlers can adopt, in case you're wondering. But the important point is that a pure-Swift way to get to work as early as possible is dispatch your +initialize type tasks in the initialization of your #UIApplicationMain class, like the construction of allHandlers here. _UIApplicationMainPreparations time ought to be early enough for pretty much anybody!
Mark your class as #objc
Inherit it from NSObject
Add ObjC category to your class
Implement initialize in category
Example
Swift files:
//MyClass.swift
#objc class MyClass : NSObject
{
}
Objc files:
//MyClass+ObjC.h
#import "MyClass-Swift.h"
#interface MyClass (ObjC)
#end
//MyClass+ObjC.m
#import "MyClass+ObjC.h"
#implement MyClass (ObjC)
+ (void)initialize {
[super initialize];
}
#end
Here is a solution that does work on swift 3.1+
#objc func newViewWillAppear(_ animated: Bool) {
self.newViewWillAppear(animated) //Incase we need to override this method
let viewControllerName = String(describing: type(of: self)).replacingOccurrences(of: "ViewController", with: "", options: .literal, range: nil)
print("VIEW APPEAR", viewControllerName)
}
static func swizzleViewWillAppear() {
//Make sure This isn't a subclass of UIViewController, So that It applies to all UIViewController childs
if self != UIViewController.self {
return
}
let _: () = {
let originalSelector = #selector(UIViewController.viewWillAppear(_:))
let swizzledSelector = #selector(UIViewController.newViewWillAppear(_:))
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
method_exchangeImplementations(originalMethod!, swizzledMethod!);
}()
}
Then on AppDelegate:
UIViewController.swizzleViewWillAppear()
Taking from the following post
Init static stored property with closure
[static stored property with closure]
One more example to execute something once using
extension MyClass {
static let shared: MyClass = {
//create an instance and setup it
let myClass = MyClass(parameter: "parameter")
myClass.initialize()//setup
return myClass
}()
//() to execute the closure.
func initialize() {
//is called once
}
}
//using
let myClass = MyClass.shared
I think that is a workaround way.
Also we can write initialize() function in objective-c code, then use it by bridge reference
Hope the best way.....

Swift: Using protocol extension results in "unrecognized selector sent to instance"

I'm trying to add a on-tap functionality to all UIViewControllers where they conform to protocol MyProtocol.
Below is how i'm doing it:
import UIKit
protocol MyProtocol: class{
var foo: String? {get set}
func bar()
}
extension MyProtocol where Self: UIViewController {
func bar() {
print(foo)
}
}
class TestViewController: UIViewController, MyProtocol{
var foo: String?
override func viewDidLoad() {
super.viewDidLoad()
foo = "testing"
let tapGesture = UITapGestureRecognizer(target: self, action: "bar")
}
Which results in following when screen is tapped:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: unrecognized selector sent to instance
I understand the error but don't know how to fix it. Can anyone suggest how this can be done?
The problem is that Objective-C knows nothing of protocol extensions. Thus, you cannot use a protocol extension to inject a method into a class in such a way that Objective-C's messaging mechanism can see it. You need to declare bar in the view controller class itself.
(I realize that this is precisely what you were trying to avoid doing, but I can't help that.)
A sort of workaround might be this:
override func viewDidLoad() {
super.viewDidLoad()
foo = "testing"
let tapGesture = UITapGestureRecognizer(target: self, action: "baz")
self.view.addGestureRecognizer(tapGesture)
}
func baz() {
self.bar()
}
We are now using Objective-C's messaging mechanism to call baz and then Swift's messaging mechanism to call bar, and of course that works. I realize it isn't as clean as what you had in mind, but at least now the implementation of bar can live in the protocol extension.
(Of course, another solution would be to do what we did before protocol extensions existed: make all your view controllers inherit from some common custom UIViewController subclass containing bar.)
Matt's answer is correct. You can play in your playground to see exactly how it works. In next snippet you can to see that even though some object can perform objc selector, if not properly used, it could crash your code :-)
//: Playground - noun: a place where people can play
import Foundation
protocol Bar {}
extension Bar {
func bar()->Self {
print("bar from protocol Bar extension")
dump(self)
return self
}
}
class C: NSObject {
func dummy() {
print("dummy from C")
dump(self)
}
}
extension NSObject {
func foo()->NSObject {
print("foo from NSObject extension")
dump(self)
return self
}
}
let c = C()
c.respondsToSelector("foo") // true
c.respondsToSelector("dummy") // true
c.foo()
let i = c.performSelector("foo")// prints the same and DON'T crash
c.dummy() // works
//c.performSelector("dummy") // if uncommented then
/* prints as expected
dummy from C
▿ C #0
- super: <__lldb_expr_517.C: 0x7fcca0c05a60>
*/
// and crash!!! because performSelector returns Unmanaged<AnyObject>!
// and this is not what we return from dummy ( Void )
let o = NSObject()
o.foo()
extension NSObject: Bar {}
// try to uncomment this extension and see which version is called in following lines
/*
extension NSObject {
func bar() -> Self {
print("bar implemented in NSObject extension")
return self
}
}
*/
c.bar() // bar protocol extension Bar is called here
o.bar() // and here
o.respondsToSelector("foo") // true
o.respondsToSelector("bar") // false as expected ( see matt's answer !! )
o.performSelector("foo") // returns Unmanaged<AnyObject>(_value: <NSObject: 0x7fc40a541270>)
There is a big gotcha with performSelector: and friends: it can only be used with arguments and return values that are objects! So if you have something that takes e.g. a boolean or return void you're out of luck.
I would like to suggest further reading for everybody who use mixture of Swift and ObjectiveC

Cannot assign self as delegate in property initializer

I am getting a strange error message when I try to access self when initializing a property on my class. It seems like my class has a weird type.
This is the error:
Cannot assign a value of type 'ViewController -> () ViewController' to a value of type 'ModelControllerDelegate?'
This is the code, simple example in a playground:
import Foundation
import UIKit
protocol ModelControllerDelegate
{
func modelControllerDidFinishTask()
}
class ModelController
{
var delegate : ModelControllerDelegate?
}
class ViewController : UIViewController, ModelControllerDelegate
{
var modelController : ModelController =
{
let controller = ModelController()
controller.delegate = self
return controller
}()
func modelControllerDidFinishTask()
{
}
}
You are trying to use self when not fully initialised. Might work if you use a lazy initialisation:
lazy var modelController : ModelController = { ...
When initialising a property with a closure like this the closure is run before the instance is fully initialised. This means there are a few things you can't do here:
Access other property values
Use self
Call any instance methods
To get around this you could either initialise modelController entirely in the ViewController's init, or at least defer the setting of its delegate until then.

Compiler error when assigning the Delegate for a Protocol in Swift iOS

I have a problem assigning the delegate for an object that is an instance of a class that defines a protocol in Swift as follows:
I simplified the code to the bare bones to exemplify the issue:
This is the class with the protocol
protocol TheProtocol {
func notifyDelegate()
}
class ClassWithProtocol: NSObject {
var delegate: TheProtocol?
fire() {
delegate?.notifyDelegate()
}
}
This is the class the conforms to the Protocol
class ClassConformingToProtocol: NSObject, TheProtocol {
var object: ClassWithProtocol?
func notifyDelegate() {
println("OK")
}
init() {
object = ClassWithProtocol()
object?.delegate = self // Compiler error - Cannot assign to the result of this expression
object?.fire()
}
}
I have tried all sort of alternatives to assign the delegate without success. Any idea what I am missing?
The Known Issues section of the Release Notes says:
You cannot conditionally assign to a property of an optional object.
(16922562)
For example, this is not supported:
let window: NSWindow? = NSApplication.sharedApplication.mainWindow
window?.title = "Currently experiencing problems"
So you should do something like if let realObject = object { ... }

Resources