A common idiom in C for callback functions is:
void set_callback(
/* the object using the callback */
void *obj,
/* a pointer to user data */
void *context,
/* the callback function, first parameter is context above */
int (*callback_func)(void *context, obj *a, obj *b),
/* an optional destructor function for cleaning up context */
void (*destructor_function)(void *)
);
destructor_function is called when obj is destroyed to clean up any memory associated with context.
I'm trying to figure out the best way to utilize this code structure in Swift. Specifically, I'm writing an SQLite wrapper and I'd like to be able to add new collation sequences with an API like:
typealias StringComparator = (String, String) -> ComparisonResult
func add(collation name: String, _ block: #escaping StringComparator)
In Obj-C I would Block_copy(block) as context, use the copied block in the callback itself, and Block_release(context) in the destructor function.
Thus far I haven't found a solution I like equally well in Swift, but I have come up with two working alternatives. I've omitted most error handling in the code for brevity.
Option One
// Assume `db` is an `sqlite3 *` object from somewhere
func add(collation name: String, _ block: #escaping StringComparator) {
let storage = UnsafeMutablePointer<StringComparator>.allocate(capacity: 1)
storage.initialize(to: block)
sqlite3_create_collation_v2(db, name, SQLITE_UTF8, storage, { (context, lhs_len, lhs_data, rhs_len, rhs_data) -> Int32 in
guard let lhs = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: lhs_data.unsafelyUnwrapped), length: Int(lhs_len), encoding: .utf8, freeWhenDone: false) else { return 0 }
guard let rhs = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: rhs_data.unsafelyUnwrapped), length: Int(rhs_len), encoding: .utf8, freeWhenDone: false) else { return 0 }
let storage = context.unsafelyUnwrapped.assumingMemoryBound(to: StringComparator.self)
return Int32(storage.pointee(lhs, rhs).rawValue)
}, { context in
let storage = context.unsafelyUnwrapped.assumingMemoryBound(to: StringComparator.self)
storage.deinitialize()
storage.deallocate(capacity: 1)
})
}
While this works in my limited testing, I am not sure whether block is actually copied when assigned to storage. I have a feeling this is a fragile solution.
Option Two
class CollationSequence {
var comparator: StringComparator
init(_ comparator: #escaping StringComparator) {
self.comparator = comparator
}
}
func add(collation name: String, _ block: #escaping StringComparator) {
let collationSequence = CollationSequence(block)
let storage = Unmanaged.passRetained(collationSequence)
sqlite3_create_collation_v2(db, name, SQLITE_UTF8, storage.toOpaque(), { (context, lhs_len, lhs_data, rhs_len, rhs_data) -> Int32 in
guard let lhs = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: lhs_data.unsafelyUnwrapped), length: Int(lhs_len), encoding: .utf8, freeWhenDone: false) else { return 0 }
guard let rhs = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: rhs_data.unsafelyUnwrapped), length: Int(rhs_len), encoding: .utf8, freeWhenDone: false) else { return 0 }
let collationSequence = Unmanaged<CollationSequence>.fromOpaque(context.unsafelyUnwrapped).takeUnretainedValue()
return Int32(collationSequence.comparator(lhs, rhs).rawValue)
}, { context in
_ = Unmanaged<CollationSequence>.fromOpaque(context.unsafelyUnwrapped).takeRetainedValue()
})
}
I feel more confident about the memory management in this solution, but I dislike it because of the introduction of a class that doesn't serve as much more than a coat hanger.
Which of these solutions is preferable? Is there a more elegant solution I've missed?
Related
Suppose you have an array, and you want to iterate over each element in the array and call a function obj.f which accepts that element as a parameter.
f is asynchronous, and completes nearly instantly, but it invokes a callback handler found in obj.
What would be the best way to match each element only after the previous finishes?
Here is one way:
let arr = ...
var arrayIndex = 0
var obj: SomeObj! // Required
obj = SomeObj(handler: {
...
arrayIndex += 1
if arrayIndex < arr.count {
obj.f(arr[arrayIndex])
}
})
obj.f(arr[0]) // Assumes array has at least 1 element
This works fine, but isn't ideal.
I could use a DispatchSemaphore, but that isn't great because it blocks the current thread.
Also, the reason why each operation must run only when the previous has finished is because the api I'm using requires it (or it breaks)
I was wondering if there was a better/more elegant way to accomplish this?
You say:
Suppose you have an array, and you want to iterate over each element in the array and call a function ... which accepts that element as a parameter.
The basic GCD pattern to know when a series of asynchronous tasks are done is the dispatch group:
let group = DispatchGroup()
for item in array {
group.enter()
someAsynchronousMethod { result in
// do something with `result`
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
// note, don't use the results here, because the above all runs asynchronously;
// return your results in the above `notify` block (e.g. perhaps an escaping closure).
If you wanted to constrain this to, say, a max concurrency of 4, you could use the non-zero semaphore pattern (but make sure you don't do this from the main thread), e.g.
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 4)
DispatchQueue.global().async {
for item in array {
group.enter()
semaphore.wait()
someAsynchronousMethod { result in
// do something with `result`
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
}
An equivalent way to achieve the above is with a custom asynchronous Operation subclass (using the base AsynchronousOperation class defined here or here), e.g.
class BarOperation: AsynchronousOperation {
private var item: Bar
private var completion: ((Baz) -> Void)?
init(item: Bar, completion: #escaping (Baz) -> Void) {
self.item = item
self.completion = completion
}
override func main() {
asynchronousProcess(bar) { baz in
self.completion?(baz)
self.completion = nil
self.finish()
}
}
func asynchronousProcess(_ bar: Bar, completion: #escaping (Baz) -> Void) { ... }
}
Then you can do things like:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4
let completionOperation = BlockOperation {
// do something with all the results you gathered
}
for item in array {
let operation = BarOperation(item: item) { baz in
// do something with result
}
operation.addDependency(completionOperation)
queue.addOperation(operation)
}
OperationQueue.main.addOperation(completion)
And with both the non-zero semaphore approach and this operation queue approach, you can set the degree of concurrency to whatever you want (e.g. 1 = serial).
But there are other patterns, too. E.g. Combine offers ways to achieve this, too https://stackoverflow.com/a/66628970/1271826. Or with the new async/await introduced in iOS 15, macOS 12, you can take advantage of the new cooperative thread pools to constrain the degree of concurrency.
There are tons of different patterns.
you could try using swift async/await, something like in this example:
struct Xobj {
func f(_ str: String) async {
// something that takes time to complete
Thread.sleep(forTimeInterval: Double.random(in: 1..<3))
}
}
struct ContentView: View {
var obj: Xobj = Xobj()
let arr = ["one", "two", "three", "four", "five"]
var body: some View {
Text("testing")
.task {
await doSequence()
print("--> all done")
}
}
func doSequence() async {
for i in arr.indices { await obj.f(arr[i]); print("--> done \(i)") }
}
}
I'm looking to make an observable property without relying on a reactive 3rd party lib / framework.
I read this and came up with a similar solution to their Observable Properties answer...
https://blog.scottlogic.com/2015/02/11/swift-kvo-alternatives.html
Theirs
class Observable<T> {
let didChange = Event<(T, T)>()
private var value: T
init(_ initialValue: T) {
value = initialValue
}
func set(newValue: T) {
let oldValue = value
value = newValue
didChange.raise(oldValue, newValue)
}
func get() -> T {
return value
}
}
Mine
public class Observable<V> {
public var value: V { didSet { for observer in observers { observer(value) } }}
private var observers = [(V) -> Void]()
public init(_ initital: V) {
value = initital
}
public func observe(with closure: #escaping (V) -> Void) {
observers.append(closure)
}
}
the only difference is I want to capture an array of closures instead of using Event and addHander... the reason being I want to provide the syntax of passing the value through rather than have the consumers of my code make a function every time and again to not rely on any 3rd party code.
I'm not sure how these closures could automatically be removed from the array once their owners are deallocated. I'm guessing they can't which is why addHandler is used, I'm just hoping someone out there more knowledgable than me can shed some light on the issue.
Thanks for your time.
So I come up with this solution:
class Wrapper<V> {
var observer: (V) -> Void
public init(_ b: #escaping (V) -> Void) {
observer = b
}
}
class Observable<V> {
public var value: V { didSet {
let enumerator = observers.objectEnumerator()
while let wrapper = enumerator?.nextObject() {
(wrapper as! Wrapper<V>).observer(value)
}
}}
private var observers = NSMapTable<AnyObject, Wrapper<V>>(keyOptions: [.weakMemory], valueOptions: [.strongMemory])
public init(_ initital: V) {
value = initital
}
public func observe(_ subscriber: AnyObject, with closure: #escaping (V) -> Void) {
let wrapper = Wrapper(closure)
observers.setObject(wrapper, forKey: subscriber)
}
}
The final API require subscriber to identify themselves at calling:
Observable.observe(self /* <-- extra param */) { /* closure */ }
Although we cannot weak ref a closure, but with NSMapTable, we can weak ref the subscriber object, then use it as a weak key to track observer closure. This allow deallocation of subscriber thus automatically cleanup outdated observers.
Finally, here's the code for a demo. Expand the snippet and copy-paste to swift playground and see it live.
import Foundation
func setTimeout(_ delay: TimeInterval, block:#escaping ()->Void) -> Timer {
return Timer.scheduledTimer(timeInterval: delay, target: BlockOperation(block: block), selector: #selector(Operation.main), userInfo: nil, repeats: false)
}
class Wrapper<V> {
var observer: (V) -> Void
public init(_ b: #escaping (V) -> Void) {
observer = b
}
}
class Observable<V> {
public var value: V { didSet {
let enumerator = observers.objectEnumerator()
while let wrapper = enumerator?.nextObject() {
(wrapper as! Wrapper<V>).observer(value)
}
}}
private var observers = NSMapTable<AnyObject, Wrapper<V>>(keyOptions: [.weakMemory], valueOptions: [.strongMemory])
public init(_ initital: V) {
value = initital
}
public func observe(_ subscriber: AnyObject, with closure: #escaping (V) -> Void) {
let wrapper = Wrapper(closure)
observers.setObject(wrapper, forKey: subscriber)
}
}
class Consumer {
private var id: String
public init(_ id: String, _ observable: Observable<Int>) {
self.id = id
observable.observe(self) { val in
print("[\(id)]", "ok, i see value changed to", val)
}
}
deinit {
print("[\(id)]", "I'm out")
}
}
func demo() -> Any {
let observable = Observable(1)
var list = [AnyObject]()
list.append(Consumer("Alice", observable))
list.append(Consumer("Bob", observable))
observable.value += 1
// pop Bob, so he goes deinit
list.popLast()
// deferred
setTimeout(1.0) {
observable.value += 1
observable.value += 1
}
return [observable, list]
}
// need to hold ref to see the effect
let refHolder = demo()
Edit:
As OP #Magoo commented below, the Wrapper object is not properly deallocated. Even though the subscriber object is successfully deallocated, and corresponding key is removed from NSMapTable, the Wrapper remain active as an entry held in NSMapTable.
Did a bit of test and found this is indeed the case, unexpectedly. Some further research reveal an unfortunate fact: it's a caveat in NSMapTable's implementation.
This post explain the reason behind thoroughly. Quote directly from Apple doc:
However, weak-to-strong NSMapTables are not currently recommended, as the strong values for weak keys which get zero’d out do not get cleared away (and released) until/unless the map table resizes itself.
Hmm, so basically Apple just think it's ok to keep them alive in memory until a resize takes place. Reasonable from GC strategy POV.
Conclusion: no chance it'd be handled if NSMapTables implementation remains the same.
However it shouldn't be a problem for most case. This Observer impl works as intended. And as long as the Wrapper don't do anything fishy and closure don't hold strong ref, only negative impact is just some extra memory footprint.
I do have a fix though, you can use weak -> weak map, so Wrapper as a weak value get dealloc too. But that'll require .observe() returns the Wrapper then Consumer get hold to a ref to it. I'm not keen on this idea, API not ergonomic to end user. I'd rather live with some memory overhead in favor of better API.
Edit 2:
I don't like the aforementioned fix cus the resulting API is not friendly. I saw no way around but #Magoo managed to NAIL IT! Using objc_setAssociatedObject API, which I never heard about before. Make sure to checkout his answer for detail, it's awesome.
OK so #hackape answer with objc_setAssociatedObject
public class Observable<V> {
private class ClosureWrapper<V> {
var closure: (V) -> Void
public init(_ closure: #escaping (V) -> Void) {
self.closure = closure
}
}
private var observers = NSMapTable<AnyObject, ClosureWrapper<V>>(keyOptions: [.weakMemory], valueOptions: [.weakMemory])
public var value: V { didSet { notify() } }
public init(_ initital: V) {
value = initital
}
public func addObserver(_ object: AnyObject, skipFirst: Bool = true, closure: #escaping (V) -> Void) {
let wrapper = ClosureWrapper(closure)
let reference = "observer\(UUID().uuidString)".replacingOccurrences(of: "-", with: "")
observers.setObject(wrapper, forKey: object)
// Giving the closure back to the object that is observing
// allows ClosureWrapper to die at the same time as observing object
objc_setAssociatedObject(object, reference, wrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
if !skipFirst { closure(value) }
}
private func notify() {
let enumerator = observers.objectEnumerator()
while let wrapper = enumerator?.nextObject() { (wrapper as? ClosureWrapper<V>)?.closure(value) }
}
}
This guy also remade the NSMapTable in Swift using a really similar method https://codereview.stackexchange.com/questions/85709/generic-nsmaptable-replacement-written-in-swift
The easiest, and likely safest, solution would be use the exact implementation you have, but make sure all callers use [weak self] and guard that self still exists before performing any actions/side effects.
This way when the array of closures is executed, any that had their creator already dealloc will simply immediately return when called.
// called from outside class
observer.observe { [weak self] in
guard strongSelf = self else { return }
// do work using `strongSelf`
}
If the observer is going to be used by a lot of instances that are constantly deallocating, I'd recommend adding a remove observer function. To do this you'd probably want to return a string on the observe call and then use it for removing the closure. Something along the lines of this:
public typealias ObserverIdentifier = String
public class Observable<V> {
public var value: V { didSet { for observer in observers.values { observer(value) } }}
private var observers = [ObserverIdentifier : (V) -> Void]()
public init(_ initital: V) {
value = initital
}
#discardableResult public func observe(with closure: #escaping (V) -> Void) -> ObserverIdentifier {
let identifier = UUID().uuidString
observers[identifier] = closure
return identifier
}
public func remove(identifier: ObserverIdentifier) {
observers.removeValue(forKey: identifier)
}
}
Because you are using [weak self], removing the observer on deallocation is simply a nice thing to do to avoid some additional no-ops, but still completely safe if not removed.
I've got two arrays made from different structs and both of them have the same information in different positions of the array.
I've tried making a double for-in loop with an if statement to check that they're the same value, but the way that I do it in other languages like PHP is not working.
Please help me, here's what I've got so far:
Singleton One File
struct oneStruct {
var oneTitle: String
var onePic: UIImage?
var oneCategory: String
}
class OneSingleton {
static let sharedInstance = OneSingleton()
private init() {}
var oneArray: [OneStruct] = [//you get the gist, this is full of array stuff]
}
Singleton Two File
struct TwoStruct {
var twoName: String
var twoPic: UIImage?
var twoSubject: String
var twoStuff1: String
// etc. I can't get into much more here, sorry.
}
class TwoSingleton {
static let sharedInstance = TwoSingleton()
private init() {}
var twoArray: [TwoStruct] = [//you get the gist, this is full of array stuff as well]
}
View Controller
//the singleton stuff is needed
var oneArray = [OneSingleton.sharedInstance.oneArray]
var twoArray = [TwoSingleton.sharedInstance.twoArray]
var filterArray: [TwoStruct] = []
override func viewDidLoad() {
super.viewDidLoad()
for two in twoArray {
for one in oneArray {
if two == one {
filterArray.append(contentsOf: two)
}
}
}
}
It's giving me a compile error
Binary operator '==' cannot be applied to operands of type '[TwoStruct]' and '[OneStruct]'
I hope this all makes sense.
Thanks in advance.
To compare implement Equatable for both and change the lhs or rhs to the other object
struct OneStruct :Equatable {
var value1: String
var value2: UIImage? /// image comparsion is not an easy way you may add id of image
var value3: String
// also you can omit any static method part from any class
static func ==(lhs: TwoStruct, rhs: OneStruct) -> Bool {
return lhs.value1 == rhs.value1 && lhs.value3 == rhs.value3
}
}
struct TwoStruct :Equatable {
var value1: String
var value2: UIImage? /// image comparsion is not an easy way you may add id of image
var value3: String
// also you can omit any static method part from any class
static func ==(lhs: OneStruct, rhs: TwoStruct) -> Bool {
return lhs.value1 == rhs.value1 && lhs.value3 == rhs.value3
}
}
//
let s1 = OneStruct(value1: "", value2: UIImage.init(named: ""), value3: "")
let s2 = TwoStruct(value1: "", value2: UIImage.init(named: ""), value3: "")
if s1 == s2 {
}
It seems you have, for each of your types OneStruct and TwoStruct, one respective property that you would like to use for comparison; category and subject, respectively. So to start out, lets blueprint two protocols for these two properties:
protocol Categorizable {
var category: String { get }
}
protocol Subjectable {
var subject: String { get }
}
For the purpose of this example, minimal examples of OneStruct and TwoStruct would then conform to Categorizable and Subjectable, respectively:
struct OneStruct: Categorizable {
var category: String
init(_ category: String) { self.category = category }
}
struct TwoStruct: Subjectable {
var subject: String
init(_ subject: String) { self.subject = subject }
}
Now, we don't really want to talk about equality when comparing two different types, rather equivalent classes or bi-predicates over which instances of these two types can be compared. So let's define e.g. a bi-predicate that can be used to compare two instances conforming to Categorizable and Subjectable, respectively:
struct BiPredicate<T: Categorizable, U: Subjectable> {
static func compare(_ lhs: T, _ rhs: U) -> Bool {
return lhs.category == rhs.subject
}
static func compare(_ lhs: U, _ rhs: T) -> Bool {
return compare(rhs, lhs)
}
}
Based on your own example code, it seems that you want two filter an array of TwoStruct (namely, an array of a type conforming to Subjectable) based on whether, for each element in the array, there exist a member in an array of OneStruct (namely, an array of a type conforming to Categorizable) for which the equivalent class or bi-predicate holds true.
/// Returns `lhs` argument filtered based on its BiPredicate intersection
/// with the `rhs` argument.
/// Time complexity: O(n^2)
func filterByIntersect<T: Subjectable, U: Categorizable>(_ lhs: [T], over rhs: [U]) -> [T] {
return lhs.filter { l in rhs.contains { r in BiPredicate.compare(l, r) } }
}
Which we may use as:
let oneArray = [OneStruct("foo"), OneStruct("bar"), OneStruct("baz"), OneStruct("bax")]
let twoArray = [TwoStruct("baxx"), TwoStruct("foo"), TwoStruct("bax")]
let result = filterByIntersect(twoArray, over: oneArray)
for twoElement in result { print(twoElement.subject) } /* foo
bax */
Now, this was mostly a somewhat interesting exercise, but in the end ended up in quite a lot of boilerplate code for something that should be quite a simple task. You could always drop the generics and just go with a simple function for testing a single equivalent class over OneStruct and TwoStruct:
func compareCategoryWithSubject(_ lhs: OneStruct, _ rhs: TwoStruct) -> Bool {
return lhs.category == rhs.subject
}
/// Returns `lhs` argument filtered based on intersection over comparing
/// the `subject` properties of the elements of `lhs` with the `category`
/// properties of the elements of `rhs`.
/// Time complexity: O(n^2)
func filterByIntersect(_ lhs: [TwoStruct], over rhs: [OneStruct]) -> [TwoStruct] {
return lhs.filter { l in rhs.contains { r in
compareCategoryWithSubject(r, l) } }
}
Or, for a possibly even simple and also asymptotically more performant approach, placing all the logic into the filterByIntersect(...) function:
/// Returns `lhs` argument filtered based on intersection over comparing
/// the `subject` properties of the elements of `lhs` with the `category`
/// properties of the elements of `rhs`.
/// Time complexity: O(n) [contains(_:) is O(1) for Set)
func filterByIntersect(_ lhs: [TwoStruct], over rhs: [OneStruct]) -> [TwoStruct] {
let rhsCategories = Set(rhs.map { $0.category })
return lhs.filter { l in rhsCategories.contains(l.subject) }
}
Both the above yielding the same result as above:
let oneArray = [OneStruct("foo"), OneStruct("bar"), OneStruct("baz"), OneStruct("bax")]
let twoArray = [TwoStruct("baxx"), TwoStruct("foo"), TwoStruct("bax")]
let result = filterByIntersect(twoArray, over: oneArray)
for twoElement in result { print(twoElement.subject) } /* foo
bax */
I'm writing a card game with a number of cards collected together in one array.
The data structure of the cards is the same, but the data is different.
Note: My shuffle does work.
I'm wanting to filter this array, and only shuffle the cards I have filtered.
However, whilst I can shuffle the cards, I've noticed that my originating array of cards does not change at all.
I believe that this issue is caused because my card model is a struct and not a class.
More background info.
Whilst the data for each card is different, the structure is exactly the same; both types have a name, and a numeric value.
This is modelled thusly;
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
I create my cards in a static function like this:
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx, owner: nil)
properties.append(property)
}
return properties
}
}
Okay, so now I only want to shuffle my property cards within this cards array.
So in my XCTest file first filter all cards that are of type: property
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
This calls:
// Inside my FSCardAPI
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
The issue:
When I run my XCTest, I notice that whilst the propertyCards is shuffled, my gameModel.cards are not shuffled;
Test Case 'testShuffleProperties' started.
// contents of `propertyCards`
[FSCard(type: FSCardType.property, name: "Property #4", value: 4, owner: nil),
FSCard(type: FSCardType.property, name: "Property #16", value: 16, owner: nil),
// .. etc
// contents of `gameModel.cards`
[FSCard(type: FSCardType.property, name: "Property #1", value: 1, owner: nil),
FSCard(type: FSCardType.property, name: "Property #2", value: 2, owner: nil),
// .. etc
Summary
I have an array of cards (ie: 30 cards)
Cards are separated by types (ie: property)
I want to filter the property cards and shuffle those cards only
I want the original array to reflect these changes
In my test, the array gets shuffled; but the original array remains the same.
One way I can think of is that I do all the shuffling, and then sort the array by card types and then update the gameModel.cards, but that seems a bit over the top.
Another obvious way I can think about solving this is to change my struct to a class or; perhaps I need another layer in between the struct?
So my query is:
How do I filter an array of structs, only shuffle those results and change the state of the originating array?
Many thanks
Edit:
The properties array is the only item to shuffle.
If I add another array to the list it should not shuffle the entire contents.
IE; if I do this:
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
let cheqs:[FSCard] = FSCardAPI.createCheques()
cards.append(contentsOf: properties)
cards.append(contentsOf: cheqs)
return cards
}
I should only shuffle the property cards within themselves without impacting the cheqs.
I guess I could make it easier and just have 2 arrays, but at the same time I think there is no reason to do this because the data structure is the same.
You are not assigning to gameModel.cards and not actually changing the array. so I would not expect gameModel.cards to be shuffled.
You should either just assign the shuffled array back to gameModel.cards like so:
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
gameModel.cards = propertyCards
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
Or if you want to mutate the array directly you should look at passing the cards by reference, or in Swift.. using an inout parameter. An inout parameter passes the value to the function by reference, or passed the memory addres of the value, so that it can be modified directly. Check the shuffle cards function below (how it is defined and used)
(I replaced the shuffle function with a swift extension for ease of use in a playground)
extension MutableCollection where Indices.Iterator.Element == Index {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
guard d != 0 else { continue }
let i = index(firstUnshuffled, offsetBy: d)
swap(&self[firstUnshuffled], &self[i])
}
}
}
extension Sequence {
/// Returns an array with the contents of this sequence, shuffled.
func shuffled() -> [Iterator.Element] {
var result = Array(self)
result.shuffle()
return result
}
}
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:inout [FSCard]) {
cards = cards.shuffled()
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx)
properties.append(property)
}
return properties
}
}
var cards = FSCardAPI.createCards()
FSCardAPI.shuffleCards(cards: &cards)
Output:
Read up on inout parameters here in the In-Out parameters section.
Exert from the documentation:
Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.
You write an in-out parameter by placing the inout keyword right before a parameter’s type. An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value. For a detailed discussion of the behavior of in-out parameters and associated compiler optimizations, see In-Out Parameters.
You can only pass a variable as the argument for an in-out parameter. You cannot pass a constant or a literal value as the argument, because constants and literals cannot be modified. You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.
EDIT: Try this updated shuffle function, pass in the whole array and see if does what you need.
public static func shuffleCards(cards:inout [FSCard]) {
let propertyCards = cards.filter({ $0.type == .property })
let indexes = propertyCards.map { Int(cards.index(of: $0)!) }
let shuffledCards = propertyCards.shuffled()
for (idx, val) in indexes.enumerated() {
cards[val] = shuffledCards[idx]
}
}
The problem is that after you shuffle your property cards, you are not doing anything with them. You should replace property cards in your original card list with the ones in your shuffled property card list.
var propertyCardsShuffledOriginalArray = originalArray.map {
var card = $0
if $0.type == .property {
card = shuffledPropertyCards.first as! FSCard
shuffledPropertyCards.removeFirst()
}
return card
}
propertyCardsShuffledOriginalArray is what you need
Edit: The problem is already solved by #vacawama. But if you are looking for an answer for NSObject classes, you should implement isEqual function which is NSObjectProtocol. Otherwise you gonna get an error says: " Redundant conformance of 'classname' to protocol 'Equatable' "
You can check this for details: Swift 2.2, Contains Method not working
In swift, how can i check if an object is in array?
I have a simple class like this;
class Test: {
private var _number: Int!
private var _type: String!
var number: Int {
return _number
}
var type: String {
return _type
}
init (number: Int, type: String) {
self._number = number
self._type = type
}
}
Also i have this class;
class TestRandom {
private let _numberArr: [Int] = [1,2,3,4,5,6,7,8,9,10]
private let _typeArr: [String] = ["x","y","z"]
public private(set) var _testArr: [Test] = []
private var _randomTest: Test!
func randomTestPicker () {
repeat {
let randomNumber = Int(arc4random_uniform(UInt32(self._numberArr.count)))
let randomType = Int(arc4random_uniform(UInt32(self._typeArr.count)))
self._randomTest = Test(number: self._numberArr[randomNumber], type: self._typeArr[randomType])
} while self._testArr.contains(_randomTest)
}
}
All i want to do is to pick different objects. Lets say i have x2,y4,x6,z3,z8,y2 in _testArr. When i call randomTestPicker, it should not pick x2 or z8. Because they are already in array.
I have tried contains as you see. However it did not work for me. Is there any solution that i can use for this purpose? Or what is the best way to do this?
Edit: I tried self._testArr.contains{$0 === _randomTest} but not working neither.
You can't use contains that way since your class doesn't conform to the Equatable protocol.
Add :Equatable to your class definition and implement the == function which compares two of your objects:
class Test: Equatable {
private var _number: Int!
private var _type: String!
var number: Int {
return _number
}
var type: String {
return _type
}
init (number: Int, type: String) {
self._number = number
self._type = type
}
}
func ==(lhs: Test, rhs: Test) -> Bool {
return lhs.number == rhs.number && lhs.type == rhs.type
}
The other way this could have been done is to use the predicate form of contains. The predicate takes two objects and returns a Bool indicating if they match. In that case, you would write:
self._testArr.contains { $0.number == _randomTest.number && $0.type == _randomTest.type }
As you can see, in this case the closure is essentially the == function from above, so implementing the Equatable protocol is the cleaner way to do it.
The closure { $0 === _randomTest } doesn't work because that only tests if the objects are the same instance. In your case, you need to check if the two objects have the same properties, and you are not interested if they are same instance. The way you are creating the objects, you never would create an instance that is already in the array, so this check would always return false.