Swift: Make function universal using generics? - swift

Ok so I have a function in a few of my Classes for different table cells that looks like this.
func open() {
let array = [position, salary, jobDescription, applyButton]
switch openVerb {
case false:
for i in array {
i.hidden = true
}
openVerb = true
case true:
for i in array {
i.hidden = false
}
openVerb = false
}
}
Now this is used a few times so I wanted to make it a universal function so I am not repeating myself. The issue is that each class has a different size array made up of UIButton and UILabel. The function switches on a Bool and then makes all objects in the array hidden or shown.
I tried using generic but I think I have made a big mistake. Help please.
func open(inout theSwitch: Bool, inout array: [<T>:UIView]) {
switch theSwitch {
case false:
for i in array {
i.hidden = true
}
theSwitch = true
case true:
for i in array {
i.hidden = false
}
theSwitch = false
}
}

Why you think you need generics here? Here how I see your function:
func open(inout theSwitch: Bool, array: [UIView]) {
for item in array {
item.hidden = !theSwitch
}
theSwitch = !theSwitch
}
Usage:
var theSwitch = true
var array = [UIButton]()
var mixedViewsArray = [UIButton(), UILabel(), UIView()]
// switch time:
open(&theSwitch, array: array)
open(&theSwitch, array: mixedViewsArray)
You don't need to use reference to original array, since you only change properties in objects in that array, so you only use references from it. open function will work with any subclass of UIView class, like UIButton or UITableViewCell (you don't even need cast mixedViewsArray to proper type, its already [UIView]).

I think you may want to do something similar to this if you want to use generics. See the link for more info on generics and also review swift evolution on GitHub to see if any changes occur in Swift 3. You might also consider using the variant where you declare a variable of Generic type var elements = Array<UIView>() or maybe even var elements = Array<UIControl>() depending on what class best supplies the properties you'd like to access. Followed by passing that variable array to a function with this signature func openTableViewCells(inout theSwitch: Bool, inout array: [UIControl]){}. Please note that I haven't tested these and feel free to refer to the documentation. Another consideration might be to use a set of protocols on the parameter that you are passing... Check the header files for a set of protocols that encompass UIView or UIControl.
Also instead of looping, check out the forEach function where you may be able to update the array contents more efficiently.
...
(generic function)
func openTableViewCells<T:UIView>(inout theSwitch: Bool, inout array: [T]) {
switch theSwitch {
case false:
for i in array {
i.hidden = true
}
theSwitch = true
case true:
for i in array {
i.hidden = false
}
theSwitch = false
}
}
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html

Related

How can I make an instance conform to "Comparable"?

Did somebody have an idea how can I fix this?
In the following Line:
//Entry Image Shared
for shared in entryImagepostShared.sorted() {
postetImageShared.append(shared)
}
I get an Error Like: Referencing instance method sorted() on Sequence requires that Bool conform to Comparable
postetImages.removeAll()
postetImageComment.removeAll()
postetImageID.removeAll()
postetImageShared.removeAll()
for document in snapshot!.documents {
guard let entryImageURL = document["imageURL"] as? [String],
let entryImageComment = document["postText"] as? [String],
let entryImagepostShared = document["postShared"] as? [Bool] else { return }
//Entry Image URL's
for url in entryImageURL.sorted() {
postetImages.append(url)
}
let count = postetImageURL.count
imageURLCount += count
//Entry Image Comment
for comment in entryImageComment.sorted() {
postetImageComment.append(comment)
}
//Entry Image Shared
for shared in entryImagepostShared.sorted() {
postetImageShared.append(shared)
}
}
The Array's method sorted() requires that the array's values are comparable, i.e. there's a way to tell whether one value is less than another.
By default, the type Bool does not have this behavior.
If you need to sort an array of Bools, first you need to decide whether you want to see the false values first or the true values first.
Assuming the former, here are two ways to achieve this:
Use the sorted(by:) method which takes a closure. This closure takes two values and returns a Bool that indicates whether they are in increasing order:
let sorted = arrayOfBools.sorted { $0 == false || $1 == true }
Make the Bool conform to Comparable by implementing your own extension. Then you can just use sorted():
extension Bool: Comparable {
public static func < (lhs: Bool, rhs: Bool) -> Bool {
lhs == false || rhs == true
}
}
let sorted1 = arrayOfBools.sorted()

Avoid duplicates in Swift array

I created this singleton to access a shared array throughout my app:
class TranslationItems {
var delegate: TranslationItemsDelegate?
static let shared = TranslationItems()
var array = [Translation]() {
didSet {
delegate?.newItemAdded()
}
}
}
The problem is that this allows for duplication (the array may contain multiple items with the same hashValue). If I check for duplication inside the didSet setter and then change the array there (for example by doing array = Array(Set(array))) that leads to an infinite loop.
How do I remove duplicates in my class?
If you want to avoid duplicates why don't you use a Set anyway (Translation must conform to Hashable)?
var set = Set<Translation>()
However if you want to keep the array a more efficient way is to add an add method which filters the duplicates, Translation must conform to Equatable
func add(object: Translation) {
if !array.contains(object) {
array.append(object)
delegate?.newItemAdded()
}
}
Making a Set from the Array and then convert it back to Array is unnecessarily expensive.
You can do it exactly how you suggested. This doesn't lead to an infinite loop
didSet {
array = Array(Set(array))
...
}
Just add one instance method
class TranslationItems {
var delegate: TranslationItemsDelegate?
static let shared = TranslationItems()
private(set) var array = [Translation]() {
didSet {
delegate?.newItemAdded()
}
}
func set(array:[Translation]) {
self.array = Array(Set(array))
}
}

Swift function can be called only once

What is the simplest way to write a piece of code that can be executed only once?
I know a way but has a problem.
first, I write a Boolean variable that has negative value but can be set to positive and cannot change after that
var hasTheFunctionCalled : Bool = false {
didSet{
hasTheFunctionCalled = true
}
}
and then write the function and the code inside it:
func theFunction(){
if !hasTheFunctionCalled{
//do the thing
}
hasTheFunctionCalled = true
}
but the problem is that the variable can be changed from somewhere else in the scope and this solution doesn't really look so simple and concrete.
A simple solution is to take advantage of lazy variables in the following way:
// Declare your "once-only" closure like this
private lazy var myFunction: Void = {
// Do something once
}()
...
// Then to execute it, just call
_ = myFunction
This ensures that the code inside the myFunction closure is only executed the first time that the program runs _ = myFunction
Edit: Another approach is to use so called "dispatch once tokens". This comes from Objective-C and was available in Swift until Swift 3. It is still possible to make it work, however you will need to add a little bit of custom code. You can find more information on this post -> dispatch_once after the Swift 3 GCD API changes
Edit2: Should be _ = myFunction and not _ = myFunction(), as JohnMontgomery pointed out.
You might use a static bool inside a struct nested into the function itself doing so:
func theFunction(){
struct Holder { static var called = false }
if !Holder.called {
Holder.called = true
//do the thing
}
}
One possible technique is to put the code into the initializer of a static type property, which is guaranteed to be lazily initialized only once (even when accessed across multiple threads simultaneously):
func theFunction() {
struct Once {
static let once = Once()
init() {
print("This should be executed only once during the lifetime of the program")
}
}
_ = Once.once
}
(Compare Singleton in the "Using Swift with Cocoa and Objective-C" reference.)
Example:
print("Call #1")
theFunction()
print("Call #2")
theFunction()
print("Done")
Output:
Call #1
This should be executed only once during the lifetime of the program
Call #2
Done
You can do smth like:
class Once {
var already: Bool = false
func run(#noescape block: () -> Void) {
guard !already else { return }
block()
already = true
}
}
and than use it like
class ViewController: UIViewController {
let once = Once()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
once.run {
cameraMan.setup()
}
}
}
ref: https://dev.to/onmyway133/how-to-run-action-once-in-swift-3k7o
Depending on what you are doing inside your method : you may check if the end result has already been accomplished :
e.g. if you instantiate a class, check if it is different from nil
You can also use UserDefaults, and the knowledge that the default UserDefault Bool is false:
if !UserDefaults.standard.bool(forKey: "ExecuteOnce") {
func()
UserDefaults.standard.set(true, forKey: "ExecuteOnce")
}
This code will execute exactly once.

Swift: How to assign a variable by reference, not by value?

I'm trying to get a reference to an Array and make modifications to it. Because Arrays in Swift are value types, instead of reference types, if I assign my array to a variable first, I am getting a copy of the array instead of the actual array:
var odds = ["1", "3", "5"]
var evens = ["2", "4", "6"]
var source = odds
var destination = evens
var one = odds.first!
source.removeFirst() // only removes the first element of the `source` array, not the `odds` array
destination.append(one)
When we look at the odds and evens arrays, they are unaltered because we changed the source and destination arrays.
I know that I can use the inout parameter attribute on a function to pass them by reference, instead of by value:
func move(inout source: [String], inout destination: [String], value:String) {
source.removeAtIndex(source.indexOf(value)!)
destination.append(value)
}
move(&odds, destination: &evens, value:one)
Is there a way to assign these arrays to a variable by reference, instead of by value?
Array is a struct, which means it's a value type in Swift. Because of this, arrays always behave according to value and not reference semantics. The problem here is that you're attempting to use mutable, reference based logic to operate on values types.
You don't want to rely on mutations occurring inside the function to propagate back to the caller. As you've found, this is only possible with inout parameters. What you should do instead is return the mutated array from the function back to the caller. The point of value oriented programming is that it shouldn't matter which array you have, but rather that any two equivalent arrays or values types are interchangeable.
It's slightly easier to imagine with another value type. Take an Int for example, and this function that does some math.
func addFive(int: Int) -> Int {
return int + 5
}
Now consider a similar function, but written in the reference oriented style that you're attempting to use:
func addFive(inout int: Int) {
int = int + 5
}
You can see it's simply not natural to operate on value types this way. Instead just return the updated value (the modified arrays) from your function and carry on from there.
Here is your function refactored with value semantics.
func move(source: [String], destination: [String], value:String) -> ([String], [String]) {
var mutableSource = source
var mutableDestination = destination
mutableSource.removeAtIndex(source.indexOf(value)!)
mutableDestination.append(value)
return (mutableSource, mutableDestination)
}
let (updatedSource, updatedDestination) = move(odds, destination: evens, value:one)
You cannot assign an array to a variable by reference in Swift.
"In Swift, Array, String, and Dictionary are all value types..."
Source: https://developer.apple.com/swift/blog/?id=10
If you need arrays that can be manipulated by reference you can create a class that encapsulates an array and use it for your variables.
here's an example:
class ArrayRef<Element>:CustomStringConvertible
{
var array:[Element]=[]
init() {}
init(Type:Element.Type) {}
init(fromArray:[Element]) { array = fromArray }
init(_ values:Element ...) { array = values }
var count:Int { return array.count }
// allow use of subscripts to manipulate elements
subscript (index:Int) -> Element
{
get { return array[index] }
set { array[index] = newValue }
}
// allow short syntax to access array content
// example: myArrayRef[].map({ $0 + "*" })
subscript () -> [Element]
{
get { return array }
set { array = newValue }
}
// allow printing the array example: print(myArrayRef)
var description:String { return "\(array)" }
// delegate append method to internal array
func append(newElement: Element)
{ array.append(newElement) }
// add more delegation to array methods as needed ...
}
// being an object, the ArrayRef class is always passed as a reference
func modifyArray(X:ArrayRef<String>)
{
X[2] = "Modified"
}
var a = ArrayRef("A","B","C")
modifyArray(a)
print(a) // --> a is now ["A", "B", "Modified"]
// various means of declaration ...
var b = ArrayRef<String>()
b[] = ["1","2","3"]
var c = ArrayRef(fromArray:b[])
// use .array to modify array content (or implement delegation in the class)
c.array += a[] + ["X","Y","Z"]
Note that you could also define your arrays as NSMutableArrays which are Obj-C classes and are passed by reference. It does a similar thing and does present differences with a regular array for the methods that are available.
I recommend this the following only for didactic purpose only, I advise against using it in production code.
You can circulate a "reference" to something via an UnsafePointer instance.
class Ref<T> {
private var ptr: UnsafePointer<T>!
var value: T { return ptr.pointee }
init(_ value: inout T) {
withUnsafePointer(to: &value) { ptr = $0 }
}
}
var a = ["1"]
var ref = Ref(&a)
print(a, ref.value) // ["1"] ["1"]
a.append("2")
print(a, ref.value) // ["1", "2"] ["1", "2"]
ref.value.removeFirst()
print(a, ref.value) // ["2"] ["2"]
Thus, you can simulate a reference to a variable via the above class, which stores a pointer to the given variable reference.
Please note that this is a simple use case, and will behave as expected only if if the variable doesn't get destroyed before the pointer, as in that case the memory initially occupied by the variable will be replaced by something else, and the unsafe pointer will no longer be valid. Take for example the next code:
var ref: Ref<[String]>!
// adding an inner scope to simulate `a` being destroyed
do {
var a: [String] = ["a"]
ref = Ref(&a)
print(a, ref.value)
a = ["b"]
print(a, ref.value)
}
// `a` was destroyed, however it's place on the stack was taken by `b`
var b: [String:Int] = ["a": 1]
// however `b` is not an array, thus the next line will crash
print(ref.value)

Swift Generics issue

Right now I want to be able to see if an object is included inside an Array so:
func isIncluded<U:Comparable>(isIncluded : U) -> Bool
{
for item in self
{
if (item == isIncluded)
{
return true
}
}
return false
}
If you notice this function belongs to an Array extension. The problem is if add it to this:
extension Array{
}
I receive the following error:
Could not find an overload for '==' that accepts the supplied arguments
I understand that I could probably need to tell what kind of objects should be inside the Array like so: T[] <T.GeneratorType.Element: Comparable>. But it doesn't work as well:
Braced block of statements is an unused closure
Non-nominal type 'T[]' cannot be extended
Expected '{' in extension
With Swift, we'll need to think whether there's a function that can do the trick -- outside the methods of a class.
Just like in our case here:
contains(theArray, theItem)
You can try it in a playground:
let a = [1, 2, 3, 4, 5]
contains(a, 3)
contains(a, 6)
I discover a lot of these functions by cmd-clicking on a Swift symbol (example: Array) and then by looking around in that file (which seems to be the global file containing all declarations for Swift general classes and functions).
Here's a little extension that will add the "contains" method to all arrays:
extension Array {
func contains<T: Equatable>(item: T) -> Bool {
for i in self {
if item == (i as T) { return true }
}
return false
}
}
To add, the problem is that T is already defined and the Array's definition of T does not conform to Equatable. You can either accomplish what you want by casting (like the accepted answer), and risking an invalid cast, or you could pass in a delegate where no casting would be required.
Consider modifying like so:
extension Array {
func contains(comparator: (T)->Bool) -> Bool {
for item in self {
if comparator(item) {
return true
}
}
return false
}
}
Example usage:
class Test {
func arrayContains(){
var test: Int[] = [0,1,3,4,5]
//should be true
var exists = test.contains({(item)->Bool in item == 0});
}
}
Not to say that it's impossible, but I haven't yet seen a way to extend structs or classes to put conditions on the original generics, for instance to guarantee Equatable or Comparable on an Array. However, for your particular issue, instead of extending, you can do something like the following:
var arr = [1, 2, 3]
var isIncluded : Bool = arr.bridgeToObjectiveC().doesContain(1)