Storing UIViewController generic with a protocol as a property - ios

Okay, so I'm pretty sure I'm overthinking this.
I am passing through a viewController that conforms to a protocol as a generic like so:
static func sortPage<T: UIViewController>(controller: T, err: NSError) where T: SortAlertDelegate { }
What I want to be able to do is store that controlleras a property so I can access all the functions UIViewController gives me and the functions thats the SortAlerDelegate gives me.
Any ideas?

You can't specify a type and protocol conformance for a property. You'll need to cast your property to the correct type whenever you want to use specific features. However, you can make this less painful with a bit of boilerplate:
let myProperty: UIViewController? = nil {
willSet(newValue) {
if (newValue as? SortAlertDelegate != nil) {
myProperty = newValue
} else {
myProperty = nil
}
}
}
This way, if you try to set the property to an object which doesn't conform to the protocol, the set will be aborted and the property will be set to nil.
You can also write read-only properties in order to get your property as the type you need at the moment:
let myPropertyAsViewController: UIViewController? {
get { return myProperty }
}
let myPropertyAsDelegate: SortAlertDelegate? {
get {
if let myProperty = myProperty {
return myProperty as! SortAlertDelegate
} else {
return nil
}
}
}

In general it is not possible unless you move the generic constraint on top of your class definition like:
class ViewController<T: UIViewController> : UIViewController where T: SortAlertDelegate {
let delegateController: T
}
But you can also make two references in your class like
class ViewController: UIViewController {
let controller: UIViewController
let delegate: SearchAlertDelegate
}
And then store the same object as two different references.

Related

Conditionally cast of generic view controller fails

Say I have the following:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController { //... }
class PersonSelectionViewController: ContentSelectableViewController<Person> { // ... }
class PlaceSelectionViewController: ContentSelectableViewController<Place> { // ... }
Then in an instance of one of these subclasses, I have some code:
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if viewController is ContentSelectableViewController {
log.info("Worked for \(viewController.description)")
}
if let vc = viewController as? ContentSelectableViewController {
// This should be equivalent to the above.
}
}
}
My question is, when I have a stack full of subclasses of this generic baseclass, it doesn't always return true (go into the if statement) when checking if they are of type ContentSelectableViewController and I don't understand why. They inherit from the same baseclass.
EDIT:
I'm guessing it's because of the generic nature of the class. The if statements evaluate to true for the subclass that calls it.
So, it does in fact have something to do with trying to type check a generic class. It would work for the one and not the other because the one making the call implicitly adds its type.
i.e. (Pseudo-Swift)
if viewController is ContentSelectableViewController<Person> { //... }
What I did instead was to define a protocol that ultimately makes these ContentSelectableViewController<T> selectable:
enum ContentSelectionRole: Int {
case none = 0 // no selection going on right now.
case root // i.e. the one wanting content
case branch // an intermediary. think of a folder when looking for a file
case leaf // like a file
}
enum ContentSelectability: Int {
case noSelections = 0
case oneSelection = 1
case multipleSelections = 2
}
protocol ContentSelection {
var selectedObjects: [NSManagedObject] { get set }
var selectionRole: ContentSelectionRole { get set }
var selectionStyle: ContentSelectability { get set }
func popToSelectionRootViewController() -> Bool
func willNavigateBack(from viewController: UIViewController)
}
Making the definition:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController, ContentSelection { //... }
And then, refactored the original post, to get:
#discardableResult func popToSelectionRootViewController() -> Bool {
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if let vc = viewController as? ContentSelection {
if vc.selectionRole == .root {
vc.willNavigateBack(from: self)
navCtrl.popToViewController(viewController, animated: true)
return true
}
}
}
}
return false
}
I still don't quite understand the aspect of the language that makes it fail, but this solution works.
Protocol-based Programming seems to be more Swifty anyway...

Swift cast AnyObject to protocol and assign property

I am trying to solve a dependency in Swift using an external injector like this:
class DependencyInjector {
var networkManager:NetworkQueueManager
protocol InjectorDelegateNetworkQueue{
var networkManager:NetworkQueueManager {get set}
}
func injectDependencies(object:AnyObject){
if object is InjectorDelegateNetworkQueue{
object.networkManager = networkManager
}
}
}
Obviously, this won't work since AnyObject does not have a property called networkManager, only the cast object has one.
I mean to call this method inside the init method of other classes, by calling
DependencyInjector.sharedInstance().injectDependencies(self)
How can I get this to work in Swift?
can maybe go like:
func injectDependencies(object:AnyObject){
var thing:InjectorDelegateNetworkQueue? = object as? InjectorDelegateNetworkQueue
if thing {
thing.networkManager = networkManager
}
}
Change your func to this.
func injectDependencies(object:AnyObject){
if object is InjectorDelegateNetworkQueue{
(object as InjectorDelegateNetworkQueue).networkManager = networkManager
}
}

Swift - Dynamic cast class unconditional?

It doesn't seem like I can cast a generic type to another? Swift is throwing DynamicCastClassException.
Basically here is the problem:
// T is defined as T: NSObject
let oebj1 = NetworkResponse<User>()
let oebj2 = oebj1 as NetworkResponse<NSObject>
Here is why I need to do this casting
class BaseViewController: UIViewController {
// Not allowed to make a generic viewController and therefore have to cast the generic down to NSObject
func fetchData(completion: (NetworkResponse<NSObject>)->()) {
fatalError("You have to implement fetchData method")
}
}
class UsersViewController: BaseViewController {
override func fetchData(completion: (NetworkResponse<NSObject>)->()) {
userNetworkManager.fetchUsers { networkUSerResponse in
completion(networkUSerResponse as NetworkResponse<NSObject>)
}
}
}
class UserNetworkManager {
func fetchUsers(completion: (NetworkResponse<User>)->()) {
// Do stuff
}
}
In general, there doesn't seem to be a way to do this. The basic problem is that NetworkResponse<NSObject> and NetworkResponse<User> are essentially completely unrelated types that happen to have identical functionality and similar naming.
In this specific case, it really isn't necessary since you're throwing away the known Userness of the result anyway, meaning that if you really want to treat it as a User later you'll have to do a conditional cast back. Just remove the generic from NetworkResponse and it will all work as expected. The major drawback is that within UserVC.fetchData you won't have access to the returned User result without a (conditional) cast.
The alternative solution would be to separate out whatever additional information is in NetworkResponse from the payload type (User/NSObject) using a wrapper of some sort (assuming there's significant sideband data there). That way you could pass the NetworkResponse to super without mutilation and down-cast the payload object as needed.
Something like this:
class User : NSObject {
}
class Transaction {
let request:NSURLRequest?
let response:NSURLResponse?
let data:NSData?
}
class Response<T:NSObject> {
let transaction:Transaction
let payload:T
init(transaction:Transaction, payload:T) {
self.transaction = transaction
self.payload = payload
}
}
class UserNetworkManager {
func fetchUsers(completion: (Response<User>) -> ()) {
completion(Response(transaction:Transaction(), payload:User()))
}
}
let userNetworkManager = UserNetworkManager();
class BaseVC {
func fetchData(completion: (Response<NSObject>) -> ()) {
fatalError("Gotta implement fetchData")
}
}
class UserVC : BaseVC {
override func fetchData(completion: (Response<NSObject>) -> ()) {
userNetworkManager.fetchUsers { response -> () in
completion(Response(transaction: response.transaction, payload: response.payload))
}
}
}
Although at that point, you're probably better off just separating the transaction information and payload information into separate arguments to the callback.

Swift, Self from AnyObject

Is it possible to get Self from AnyObject?
Take this example:
// Superclass
class ManagedObject {
class func findByID(id: String) -> AnyObject? {
let objects = objectsWithPredicate(NSPredicate(format: "id == %#", id))
return objects.firstObject() // Returns AnyObject
}
}
// Subclass
class User : ManagedObject {
class func returnFirstSelf() -> Self? {
return findById("1") // This doesn't work because it returns AnyObject, but I need Self.
}
}
If not, what is the best alternative way to ensure that when calling User.returnFirstSelf(), the compiler gives back a User, and when calling UserSubclass.returnFirstSelf(), it gives back a UserSubclass.
You can simply return User? from your class function, if this is an option:
public class func returnFirstSelf() -> User? {
if let found = findByID("1") as? User {
return found
}
return nil
}
There's currently no way (I'm aware of) to return Self? with Swift as it stands. The problem is that Self has a somewhat... "dynamic" meaning, separate from concrete types, protocols, and even generics. A particular example that demonstrates this is: What if you have a class StudentUser that extends User? If you tried to implement it like this:
class func returnFirstSelf() -> Self? {
if let found = findById("1") as? Self { // Note the check that 'found' is a 'Self'
return found
}
return nil
}
Then you encounter a compiler error because you cannot use Self outside the result of a protocol or class method. And if you try to implement it like this:
class func returnFirstSelf() -> Self? {
if let found = findById("1") as? User { // Note 'User' instead of 'Self'
return found
}
return nil
}
Then you run the risk of Self actually meaning StudentUser, and even if you pass the check that requires found to be a User, it doesn't guarantee that found will be a StudentUser. This will occur in the case that StudentUser does not override the method to ensure that the as? checks against StudentUser.
The critical flaw here in my opinion is that you cannot use the required keyword on class methods, requiring subclasses to override them. This would allow the compiler to ensure that any subclasses have overridden the method and provided an implementation that can guarantee type safety.
Craig Otis' answer is spot on. However, one option is to create a copy constructor. This may not work for the asker's scenario where they're inheriting from a NSManagedObject, but it should work for non-managed objects.
required init(user: User) {
super.init(user: User) // or super.init() if top of inheritance.
self.name = user.name
self.email = user.email
// etc.
}
class func returnFirstSelf() -> Self? {
if let found = findById("1") { // Note the check that 'found' is a 'Self'
return self.init(copy: found as! User)
}
return nil
}

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