More elegant way to assign optionals to a lazy property - swift

Imagine we have a lazy imageView and we want to initialize it only if there is an image to assign. So we need to check for the image value each time:
lazy var imageView1 = UIImageView()
lazy var imageView2 = UIImageView()
var image: UIImage?
var someOptional: SomeType?
func addImage1IfNeeded() {
guard let image = image else { return }
imageView1.image = image
}
func addImage2IfNeeded() {
guard let image = someOptional?.someChildOptional?.image else { return }
imageView2.image = image
}
So if we have lots of optionals and lazy variables and some optional chaining situations, we will have thons of repetitive codes.
What is more elegant way to do this? (Maybe using an operator? an extension on optional?)
Please note that This is not for optimization. This example is summarized and the original issue is happening when you want to not adding UI elements if there is no data to present. Also in other situations.

One way is to use an operator like this:
infix operator ?=>
func ?=>(lhs: Any?, rhs: #autoclosure ()->()) {
guard lhs != nil else { return }
rhs()
}
Usage:
image ?=> (imageView1.image = image)
I don't know if we can combine the first and last argument since they are alwayse the same.
Update - Option 2
Following #Sweeper's comment:
func ?=><T>(lhs: T?, rhs: (T)->()) {
guard let lhs = lhs else { return }
rhs(lhs)
}
Usage:
someOptional?.someChildOptional?.image = { imageView2.image = $0 }
It's better for long optional changings but yet requires curly braces and $ argument (that I forgot it's name)

Using keypaths, I've been able to extract what you are doing into a function:
func assignNonNilImage<R, V>(_ root: R, _ keyPath: ReferenceWritableKeyPath<R, V>, _ value: V?) {
if let nonNilValue = value {
root[keyPath: keyPath] = nonNilValue
}
}
Usage:
class Foo {
lazy var imageView = UIImageView()
var image: UIImage?
func f() {
// here!
assignNonNilImage(self, \.imageView.image!, image)
}
}
I'm not sure if it's any "better" though...
You can also write this as a method in your class:
func assignNonNilImage<V>(_ keyPath: ReferenceWritableKeyPath<Foo, V>, _ value: V?) {
if let nonNilValue = value {
self[keyPath: keyPath] = nonNilValue
}
}

Related

Swift UITextField target-actions as closure, problem with not removing target-actions

I have such code a little modified from code of Eric Armstrong
Adding a closure as target to a UIButton
But there is the problem with both codes. Those from Eric does remove all target-actions on
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside)
And modified code on the other hand do not remove target-actions at all. Of course it is caused by if condition, but it also means that there are no targets stored properly in Storable property.
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = NSMutableDictionary()
static func makeProperty() -> NSMutableDictionary? {
return NSMutableDictionary()
}
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: #escaping (_ sender: Any) ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
if let target = property[key] as? Target {
removeTarget(target, action: target.action, for: controlEvent)
property[key] = nil
}
}
}
// Wrapper class for the selector
class Target {
private let t: (_ sender: Any) -> ()
init(target t: #escaping (_ sender: Any) -> ()) { self.t = t }
#objc private func s(_ sender: Any) { t(sender) }
public var action: Selector {
return #selector(s(_:))
}
}
// Protocols with associatedtypes so we can hide the objc_ code
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
static func makeProperty() -> PropertyType?
}
extension PropertyProvider {
static func makeProperty() -> PropertyType? {
return nil
}
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
// Extension to make the property default and available
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get {
let key = String(describing: type(of: Storable.self))
guard let obj = objc_getAssociatedObject(self, key) as? Storable else {
if let property = Property.makeProperty() {
objc_setAssociatedObject(self, key, property, .OBJC_ASSOCIATION_RETAIN)
}
return objc_getAssociatedObject(self, key) as? Storable ?? Property.property
}
return obj
}
set {
let key = String(describing: type(of: Storable.self))
return objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
My aim is to precisely register target-actions with closures and remove them without removing all other target-actions added to given UITextField via #selector. Now I can have removed ALL or NONE of target-actions while using this approach for closure-style target actions.
UPDATE
Based on Eric Armstrong answer i have implemented my version.
But what I have experienced in version proposed by Eric was that when adding target actions to TextField on TableView list while cells appear and then removing this target actions from Text Fields while cells diseappear the previous code seems to remove all target actions on removeTarget(for:) exection. So when in other place in code like UITableViewCell I have added additional target action on totaly different target (UITableViewCell object, not this custom Target() objects) while cells was disappearing and then again appearing on screen and removeTarget(for) was executed then this other (external as I call them target actions) also was removed and never called again.
I consider that some problem was usage of [String: Target] dictionary which is value type and it was used in case of property getter in objc_getAssociatedObject where there was
objc_getAssociatedObject(self, key) as? Storable ?? Property.property
So as I understand it then there wasn't objc object for given key and Storable was nil and nil-coalescing operator was called and static value type Property.property return aka [String : Dictionary]
So it was returned by copy and Target object was stored in this copied object which wasn't permanently stored and accessed in removeTarget(for:) always as nil. So nil was passed to UIControl.removetTarget() and all target actions was always cleared!.
I have tried simple replacing [String: Target] Swift dictionary with NSMutableDictionary which is a reference type so I assume it can be stored. But this simple replacement for static variable and just returning it via nil-coalesing operator caused as I assume that there as only one such storage for Target objects and then while scrolling Table View each removeForTarget() has somehow remove all target actions from all UITextFields not only from current.
I also consider usage of String(describing: type(of: Storable.self)) as being wrong as it will be always the same for given Storable type.
Ok, I think I finally solved this issue
The main problem was usage of AssociatedKey! it needs to be done like below
https://stackoverflow.com/a/48731142/4415642
So I ended up with such code:
import UIKit
/**
* Swift 4.2 for UIControl and UIGestureRecognizer,
* and and remove targets through swift extension
* stored property paradigm.
* https://stackoverflow.com/a/52796515/4415642
**/
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = NSMutableDictionary()
static func makeProperty() -> NSMutableDictionary? {
return NSMutableDictionary()
}
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: #escaping (_ sender: Any) ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
print("ADDED \(ObjectIdentifier(target)), \(target.action)")
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
if let target = property[key] as? Target {
print("REMOVE \(ObjectIdentifier(target)), \(target.action)")
removeTarget(target, action: target.action, for: controlEvent)
property[key] = nil
}
}
}
extension UIGestureRecognizer: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property: Target?
}
func addTarget(target: #escaping (Any) -> ()) {
let target = Target(target: target)
addTarget(target, action: target.action)
property = target
}
func removeTarget() {
let target = property
removeTarget(target, action: target?.action)
property = nil
}
}
// Wrapper class for the selector
class Target {
private let t: (_ sender: Any) -> ()
init(target t: #escaping (_ sender: Any) -> ()) { self.t = t }
#objc private func s(_ sender: Any) { t(sender) }
public var action: Selector {
return #selector(s(_:))
}
deinit {
print("Deinit target: \(ObjectIdentifier(self))")
}
}
// Protocols with associatedtypes so we can hide the objc_ code
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
static func makeProperty() -> PropertyType?
}
extension PropertyProvider {
static func makeProperty() -> PropertyType? {
return nil
}
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
// Extension to make the property default and available
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get {
guard let obj = objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable else {
if let property = Property.makeProperty() {
objc_setAssociatedObject(self, &AssociatedKeys.property, property, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable ?? Property.property
}
return obj
}
set {
return objc_setAssociatedObject(self, &AssociatedKeys.property, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}
private struct AssociatedKeys {
static var property = "AssociatedKeys.property"
}

Compare / Equatable weak generics in Swift

I want to create an array of weak referenced delegates like so...
fileprivate class WeakDelegate<T:AnyObject> {
weak var value:T?
init (value:T) {
self.value = value
}
}
class Radio {
private var delegates = [WeakDelegate<AnyObject>]()
}
So far so good...? what I'd like to do also is tidy up my array in these two ways...
1.
func removeDeadDelegates() {
let liveDelegates = delegates.filter { $0.value != nil }
delegates = liveDelegates
}
and 2.
func remove<T>(specificDelegate:T) {
let filteredDelegates = delegates.filter { $0.value != specificDelegate }
listeners = filteredDelegates
}
Says Cannot convert value of type 'T' to expected argument type '_OptionalNilComparisonType'
Now I can just add this to make the warning go away like this...
let liveDelegates = delegates.filter {
if let d = specificDelegate as? _OptionalNilComparisonType {
return $0.value != d
}
return true
}
but this cast doesn't work...
I'm concerned because I'm not sure what this means... can anyone explain why I can't compare generics with == and why this cast is failing?
Thanks for your time
EDIT
Like this?
func remove<T:AnyObject>(delegate:T) {
let filteredDelegates = delegates.filter { $0.value != delegate }
delegates = filteredDelegates
}
No joy sadly...
Instances of a class type can be compared with the “identical to”
=== and “not identical to” !== operators:
func remove(specificDelegate: AnyObject) {
let filteredDelegates = delegates.filter { $0.value !== specificDelegate }
delegates = filteredDelegates
}
The same works for a generic method
func remove<T:AnyObject>(specificDelegate: T) {
let filteredDelegates = delegates.filter { $0.value !== specificDelegate }
delegates = filteredDelegates
}
(but I do not yet see the advantage of doing so).

Blank constant when trying to get list of classes that have adopted a Protocol

I am trying to get a list of classes that have adopted a certain Protocol Migration: Preparation, and then to append those classes into an array. Here is the function in question:
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
var migrationsList = [Preparation.Type]()
var count = UInt32(0)
let classList = objc_copyClassList(&count)!
for i in 0..<Int(count) {
let classInfo = ClassInfo(classList[i])!
if let cls = classInfo.classObject as? Migration.Type {
migrationsList.append(cls)
print(cls.description)
}
}
return migrationsList
}
}
In principle all that should work, but when debugging I note that the classInfo variable is referring to each class in the iteration, but when assigning and casting in the if let as line, the constant cls is always blank - neither a value/class nor nil, just completely blank.
Any idea what I got wrong with that code?
I am also open to suggestions for any better way to get a list of all classes that have adopted a particular protocol...
EDIT: I forgot to provide the code for ClassInfo
import Foundation
struct ClassInfo: CustomStringConvertible, Equatable {
let classObject: AnyClass
let className: String
init?(_ classObject: AnyClass?) {
guard classObject != nil else { return nil }
self.classObject = classObject!
let cName = class_getName(classObject)!
self.className = String(cString: cName)
}
var superclassInfo: ClassInfo? {
let superclassObject: AnyClass? = class_getSuperclass(self.classObject)
return ClassInfo(superclassObject)
}
var description: String {
return self.className
}
static func ==(lhs: ClassInfo, rhs: ClassInfo) -> Bool {
return lhs.className == rhs.className
}
}
I can't explain why cls is always blank, like I said in my comment it's something I run into every time I'm dealing with meta types. As for making the code work as intended, I found this q&a and updated it with Swift 3 to get this code which should cover your situation. It's important to stress that this will only work if you correctly expose Swift to the Objective-C runtime.
Drop this code anywhere and call print(Migrations.getMigrations()) from a convenient entry point.
struct Migrations {
static func getMigrations() -> [Preparation.Type] {
return getClassesImplementingProtocol(p: Preparation.self) as! [Preparation.Type]
}
static func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
let classes = objc_getClassList()
var ret = [AnyClass]()
for cls in classes {
if class_conformsToProtocol(cls, p) {
ret.append(cls)
}
}
return ret
}
static func objc_getClassList() -> [AnyClass] {
let expectedClassCount = ObjectiveC.objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(expectedClassCount))
let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
let actualClassCount:Int32 = ObjectiveC.objc_getClassList(autoreleasingAllClasses, expectedClassCount)
var classes = [AnyClass]()
for i in 0 ..< actualClassCount {
if let currentClass: AnyClass = allClasses[Int(i)] {
classes.append(currentClass)
}
}
allClasses.deallocate(capacity: Int(expectedClassCount))
return classes
}
}
class Migration: Preparation {
}
#objc
protocol Preparation {
}

Extend Key-Value Coding for Geometric structures in Swift

As a newbie in macOS programming and in particular with Swift, I’ve been disappointed to discover that the structure’s properties (attributes) do not work with Cocoa Bindings. In my data model (for my convenience) I made extensive use of the geometric structures exposed by CoreGraphics framework and I don’t want to restructure all of the code in order to use the native Binding mechanism with UI controls provided by Cocoa.
So I'm trying to extend the protocol KVC / KVO to support KeyPath in all basic geometric structures - exactly as does the CoreAnimation framework in CALayer class.
The result seems to work as I expected but I am worried about not having fully understood all the rules under the mechanism and having produced an implementation thus weak and prone to errors.
Below the code I’m working on. (For “easy-reading” this is a cut-off that only intercepts the CGSize structure but the full version is also identical for the other geometry types).
I turn to experts for advice and some suggestions over the weaknesses of this approach and eventually an alternative way that allows bindings between the individual properties of a geometric structure and the various user interface controls.
[Dev on Xcode 8.2.1 (Swift 3.0.1)]
Thank you all
1. GeometryBindableObject class: the NSObject subclass used to extend KVC/KVO capabilities
open class GeometryBindableObject: NSObject {
open override func value(forKeyPath keyPath: String) -> Any? {
var key = String(), subPath = String()
if keyPath.contains(".") {
let keys = keyPath.characters.split(separator: ".", maxSplits: 1).map { String($0) }
(key, subPath) = (keys[0], keys[1])
} else {
key = keyPath
}
var value = self.value(forKey: key)
if (!subPath.isEmpty) {
if let object = value as? CGSize {
value = getAttribute(subPath, for: object)
} else
...
if let object = value as? NSObject {
value = object.value(forKeyPath: subPath)
}
}
return value
}
open override func setValue(_ value: Any?, forKeyPath keyPath: String) {
var key = String(), subPath = String()
if keyPath.contains(".") {
let keys = keyPath.characters.split(separator: ".", maxSplits: 1).map { String($0) }
(key, subPath) = (keys[0], keys[1])
} else {
key = keyPath
}
if (!subPath.isEmpty) {
let keyValue = self.value(forKey: key)
if var object = keyValue as? CGSize {
setAttribute(value, subPath, for: &object)
setValue(object, forKey: key)
} else
...
if let object = keyValue as? NSObject {
object.setValue(value, forKeyPath: subPath)
}
} else {
setValue(value, forKey: key)
}
}
open override func addObserver(_ observer: NSObject, forKeyPath keyPath: String, options: NSKeyValueObservingOptions = [], context: UnsafeMutableRawPointer?) {
var hackedKeyPath = keyPath
if keyPath.contains(".") {
let keys = keyPath.characters.split(separator: ".", maxSplits: 1).map { String($0) }
let key = keys[0]
var value = self.value(forKey: key)
if let object = value as? CGSize {
hackedKeyPath = key
}
}
super.addObserver(observer, forKeyPath: hackedKeyPath, options: options, context: context)
}
}
2. GeometryBindableObject extension: helper functions to get and set properties on handled value types
private extension GeometryBindableObject {
// getter & setter for CGSize attributes
func getAttribute(_ key: String, for object: CGSize) -> Any? {
switch key {
case "width":
return object.width
case "height":
return object.height
default:
return value(forUndefinedKey: key)
}
}
func setAttribute(_ value: Any?, _ key: String, for object: inout CGSize) {
switch key {
case "width":
object.width = CGFloat(value as! NSNumber)
case "height":
object.height = CGFloat(value as! NSNumber)
default:
setValue(value, forUndefinedKey: key)
}
}
}
...and just to try in a playground environment...
class myObject: GeometryBindableObject {
var size: NSSize = NSMakeSize(100, 100)
}
class myContainer: NSObject {
var object = myObject()
}
let aBox = myContainer()
aBox.value(forKeyPath: "object.size.width") //100
aBox.setValue(200, forKeyPath: "object.size.width")
aBox.value(forKeyPath: "object.size.width") //200

Unwrapping generic type

Let's say I have some variable with type
let someVariable: SomeType<AnotherType?>
and I am sure that this concrete instance not contain any nil of AnotherType?. Is there is general way to convert it to SomeType<AnotherType>? For example, I need this convert for use someVariable in some function.
It could go along these lines:
protocol _Optional {
associatedtype _Wrapped
func unveil() -> _Wrapped?
}
extension Optional: _Optional {
typealias _Wrapped = Wrapped
func unveil() -> _Wrapped? { return self }
}
extension SomeType where T: _Optional {
func rewrap() -> SomeType<T._Wrapped>? {
guard let _value = value.unveil() else { return nil }
return SomeType<T._Wrapped>(value: _value)
}
}
struct SomeType<T> {
let value: T
}
let someValue = SomeType<Int?>(value: 42) // SomeType<Int?>
let rewrappedValue = someValue.rewrap() // SomeType<Int>?
let forceUnwrappedValue = someValue.rewrap()! // SomeType<Int>
Specifics depend on details of the particular implementation for SomeType (I use a simplest assumption in my example).