Using key-value programming (KVP) with Swift - swift

In Objective-C with Cocoa a lot of tasks can be accomplished without explicit loops by using Key-Value Programming (KVP). For example, I can find the largest number in an array with a single line of code:
NSNumber * max = [numbers valueForKeyPath:#"#max.intValue"];
How can I do the same thing with swift? Arrays do not appear to support valueForKeyPath method.

The array will actually respond to valueForKeyPath function - you just need to cast the array to AnyObject so that the compiler doesn't complain. As follows:
var max = (numbers as AnyObject).valueForKeyPath("#max.self") as Double
or even, for a union of objects:
(labels as AnyObject).valueForKeyPath("#unionOfObjects.text")
If labels above is a collection of labels, the above will return an array of all the strings of the text property of each label.
It is also equivalent to the following:
(labels as AnyObject).valueForKey("text")
... just as it is in Objective-C :)

You can also use the reduce function of Array
let numbers = [505,4,33,12,506,21,1,0,88]
let biggest = numbers.reduce(Int.min,{max($0, $1)})
println(biggest) // prints 506
Good explanation here

You can still use (at least) the didSet willSet provided by Swift on properties. I guess it's better than nothing.

I'm not sure about KVP, but KVO isn't currently supported in Swift. See also this dev forums thread:
https://devforums.apple.com/thread/227909

Related

How can I assign a Swift struct by reference?

As we know, in Swift, classes are reference objects whereas structs other data-types are value types. I'm trying to get the reference of a CGRect into a variable so I can change its value with a shorter name. Is there a way to achieve this? Attempting with an Objective-C or C++ pointer syntax is of no use here :(
let reference = someView.frame
frame = ...
If I stay at the view level it's ok because it's a reference type, but I want to include the frame in the reference.
You probably don't really want to work with references... I could be wrong, and you might have a good reason for wanting a reference. But looking at what you are trying to do, this might be a better approach:
Assign the frame that you want to work with to a variable:
var workingFrame = someView.frame
Work with the copy, making any changes that you want to make:
workingFrame = someNewRect
Update someView.frame to the new value:
someView.frame = workingFrame
There is, technically, a way to deal directly with pointers to memory addresses, but unless you have an amazingly good reason for going there, I think that most people would recommend that you avoid it.
[Edit:]
If you really want to try to work with pointers to memory addresses, then you may want to look at UnsafePointer<T>, UnsafeMutablePointer<T>, unsafeBitCast: and unsafeAddressOf:. Those types and functions will give you pointers to a struct.
For example, you can get a mutable pointer to an Int value like this:
let x = 5
let ptr: UnsafeMutablePointer<Int> = UnsafeMutablePointer(unsafeAddressOf(x))
Working with values, pointers, and memory addresses this way is discouraged, but yes, it is possible.
However, using unsafeAddressOf converts the Int to a class, so even that isn't really a pointer to the original struct. You may end up needing to initialize an UnsafeMutablePointer, allocate memory for it, and then assign a value to that memory. Then you can perform operations on the data at that memory address. Check out the documentation for UnsafeMutablePointer here.
And if you can give any more detail as to what, precisely, you are trying to do, there may be a more elegant solution. Swift does not make it easy to work with pointers, but it often provides other tools that allow you to accomplish what you need in a different way.
Here's the solution:
func pointerTo<T>(inout object: T) -> UnsafeMutablePointer<T> {
return withUnsafeMutablePointer(&object) {UnsafeMutablePointer<T>($0)}
}

Array with capacity in Swift

How to initialise an array with maximum capacity without RepeatedValues?
var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
Like in this example with repeatedValue. Can we initialise without a value?
CMD-clicking the Array type in Xcode finds me the following function definition (along with the doc comment):
/// Ensure the array has enough mutable contiguous storage to store
/// minimumCapacity elements in. Note: does not affect count.
/// Complexity: O(N)
mutating func reserveCapacity(minimumCapacity: Int)
So in a way, you can tell an Array to pre-allocate storage for the given capacity by doing something like this:
var results: [T] = []
results.reserveCapacity(100)
And theoretically, hundred appends on the array should then performs better than without the capacity reservation call.
To enforce "maximum" capacity though, there is no way to do that short of a custom code manually putting nils into an array of Optional<T>s capped to the maximum size as suggested by #Bryan in the question comments.
For Swift 3.0, Value must be an optional type
var arrImages = [UIImage?](repeating: nil, count: 64)
UPDATE: As #chakrit says, you can use reserveCapacity now. This was added in later betas and is now available in the release version of Swift.
Arrays in Swift work differently than they do in Objective-C. In Swift you can't create an Array that has pre-allocated memory but does not contain elements. Just create an Array() and add as many elements as you want (3 in your case).
If you absolutely must do it this way, then use NSMutableArray like this:
var anotherThreeDoubles = NSMutableArray(capacity: 3)
I hope I understood the question correctly. Feel free to explain further.
As Jernej said, you can use NSMutableArray in this case. Note that both NSMutableArray and Swift Arrays do not actually limit how many elements you can add:
var anotherThreeDoubles = Array(count: 3, repeatedValue:10)
anotherThreeDoubles += 10 //another 3 doubles now has 4 elements
var arr: Array = NSMutableArray(capacity: 3)
arr.append(10)
arr.append(10)
arr.append(10)
arr.append(10) //arr now has 4 elements
Even though there's a reserveCapacity function, it's not advised
from the docs:
The Array type’s append(:) and append(contentsOf:) methods take care of this detail for you, but reserveCapacity(:) allocates only as much space as you tell it to (padded to a round value), and no more. This avoids over-allocation, but can result in insertion not having amortized constant-time performance.
Apple says it's better to let the array's append and append(contentsOf:) functions to take care of this for you unless you know the capacity of the array without calling the count property on a collection. They also have an example on the page I linked above.

iOS Obj-C: Variable object that can be assigned as a double or a string?

I'm pretty new to iOS development, and I want to figure out if there's a good way to handle this issue. Basically, I'm making a technical calculator that returns some product specifications based on user input parameters. The product in question has specs for some, but not all user parameters, so I . In a constants file, I have a bunch of ATTEN_SPEC_X variables which are const double or const NSString *. Now, it's perfectly okay to be missing a spec, so my plan was to leverage NSArray's ability to hold different types and use introspection later to handle strings vs doubles before I report the returned specs.
Here's an incomplete example of one method I'm implementing. It's just a big conditional tree that should return a two-element array of the final values of spec and nominal.
- (NSArray *)attenuatorSwitching:(double *)attenuator{
double spec, nominal;
{...}
else if (*attenuator==0){
spec=ATTEN_SPEC_3; //this atten spec is a string!
nominal=ATTEN_NOM_3;
}
{...}
return {array of spec, nominal} //not actual obj-c code
So instead of making spec and nominal doubles, can I make them some other general type? The really important thing here is that I don't want to use any special handling within this method; another coder should be able to go back to the constants file, change ATTEN_NOM_3 to a double, and not have to retool this method at all.
Thanks.
The problem you'll run into is that NSArrays can't directly handle doubles. However, you can get around this if you start using NSNumber instances instead - you can return an NSArray * containing an NSString * and an NSNumber * with no problems. If you need even more general typing, the Objective-C type id can be used for any object instance (though still not with primitives; you can't make a double an id).
Later, when you get an array, you can use the NSObject method -isKindOfClass: to determine the type of object you're pulling out of the array, and deal with the string or number depending on the resultant type. If you need to convert your NSNumber back to a double, just use the NSNumber instance method -doubleValue to unbox your double. (+[NSNumber numberWithDouble:] goes the other way, giving you an NSNumber out of a double.)
If you're using a recent enough version of Xcode, you can even make these things literals, rather than having to litter calls to +numberWithDouble: all over the place:
return #[ #3, #"number of things" ]

confused about Objective-C syntax [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Method Syntax in Objective-C
I just started learning Objective-C and I'm a little confused about this statement.
+(NSMutableArray *) array;
This is what I understand:
+ means this is a method that responds to a class (which makes this a static method)
NSMutableArray is an array that can have it's size change
* pointer ( memory location reference)
array is the name of the array that is of type NSMutableArray.
() Why does this method need parentheses around the object pointer '(NSMutableArray *)'
I don't know how to conceptualize what these part mean as a whole. Can you explain this to me?
in C syntax this would be written as:
static NSMutableArray *array();
NSMutableArray * is the return type. array is the name of the method. There are no arguments, but if there were it would be like this:
+ (ReturnType)methodName:(ArgType)argName;
or if there were multiple arguments:
+ (ReturnType)firstPartOfMethodName:(Arg1Type)arg1Name secondPartOfMethodName:(Arg2Type)arg2Name;
This can be a point of confusion for a lot of Obj-C newcomers. The fact that the method name is split between the arguments can be extremely confusing to most programmers coming from other languages.
The reason that it's ordered that way is to give clarity to the arguments. When using methods like:
- (id)initWithBitmapDataPlanes:(unsigned char **)planes
pixelsWide:(NSInteger)width
pixelsHigh:(NSInteger)height
bitsPerSample:(NSInteger)bps
samplesPerPixel:(NSInteger)spp
hasAlpha:(BOOL)alpha
isPlanar:(BOOL)isPlanar
colorSpaceName:(NSString *)colorSpaceName
bitmapFormat:(NSBitmapFormat)bitmapFormat
bytesPerRow:(NSInteger)rowBytes
bitsPerPixel:(NSInteger)pixelBits
(This is a real method from the Cocoa framework, known for being the longest), it's very helpful that you know which argument to place first, second, third, etc.
The 1,2,3 statements are correct. But fourth one is, array is the name of method. The fifth one is, the return type of array method is NSMutableArray *.
Finally array is the class method and it has NSMutableArray * return type and also does not any arguments.
It look like - (void)viewDidLoad. viewDidLoad is the instance method and it has void return type and also does not any arguments.

Don't have the right syntax when comparing strings in iPhone

I am trying to compare the following values:
gType = [[UILabel alloc]init];
if (gType = [NSString string:#"BUSINESS"]) {
I get a warning that 'NSString' may not respond to '+string:'
I am unsure what is wrong. gType is a value that I populate from a db query. Other text values from the same query show up fine in a UITableView, so I am pretty confident I have created it properly.
thx,
Your code is calling the "String" class method on the NSString class. This doesn't accept any arguments, which is your problem here.
The correct way to write your code would be something like:
if ([gType.text isEqualToString:#"BUSINESS"])
For starters, = is the assignment operator in C and does not compare anything. Secondly, even if you were using a comparison operator there, you'd be comparing pointer addresses, not the textual contents of the objects.
Read this
You're looking for:
if ([someString isEqual:#"Something else"]) { ... }
As NSD said, you have a few fundamental problems with your code there.
If you want to compare strings in Cocoa Touch, you can use the -isEqualToString: method on NSString.