Last active
August 29, 2015 14:02
-
-
Save loganwright/ee0b7f94c9ddf4c075e1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Playground - noun: a place where people can play | |
import Cocoa | |
/* Classes and Structures | |
Traditionally, we've referred to instances of a class as "objects" however, because Swift classes and structures are so much alike, it's preferable to use the more general term -- 'Instance' | |
Even though these two Types have a lot of similarity, they are handled by the system very differently. ***Structures are values which are always copied when they are passed around, whereas Classes use reference counting.*** This means more than one variable can reference a specific class instance, but you will never have multiple references to the same specific instance of a Struct | |
Here's what Structs and Classes have in common in Swift: | |
1. Define properties to store values | |
2. Define methods to provide functionality | |
3. Define subscripts to provide access to their values using subscript syntax | |
4. Define initializers to set up their initial state | |
5. Be extended to expand their functionality beyond a default implementation | |
6. Conform to protocols to provide standard functionality of a certain kind | |
And here's what classes add in functionality: | |
7. Inheritance enables one class to inherit the characteristics of another. | |
8. Type casting enables you to check and interpret the type of a class instance at runtime. | |
9. Deinitializers enable an instance of a class to free up any resources it has assigned. | |
10. Reference counting allows more than one reference to a class instance. | |
There's a lot more to say about this, but let's back up a bit and define our first Struct. In Swift, as in Objective-C it is preferable to name our Structs and Classes using camel-case typing beginning with a capital letter | |
*/ | |
struct SomeStruct { | |
} | |
/* That's it! Pretty easy. It doesn't do very much yet though. Let's create another struct that has a couple of values for us to work with */ | |
struct GeoPoint { | |
var latitude = 0.0 | |
var longitude = 0.0 | |
} | |
/* | |
This is a fairly straightforward declaration that defines a new struct of type 'GeoPoint' . We give this struct two properties and declare these properties as type double. Although you don't see any explicit double declaration, by setting their default value to 0.0 we signify to Swift that these properties are Doubles through type inference. | |
Now let's use this struct. So far we've only defined what a GeoPoint looks like, if we want to interact with it, we must create an instance. The syntax for a new instance is similar to the class initialization and looks like this | |
*/ | |
var newGeoPoint = GeoPoint() | |
// We can then set and interact with its properties using traditional dot syntax | |
newGeoPoint.latitude = 42.8572 | |
newGeoPoint.longitude = -12.4222 | |
/* | |
However, Swift automatically generates a 'memberwise initializer' for structures that allows you to set all of its properties at initialization. | |
*/ | |
var memberwiseGeoPoint = GeoPoint(latitude: 12.324, longitude: 29.1111) | |
/* | |
Now let's look at a bit more advanced version of a struct loosely based on Apple's implementation of CGRect that also includes a method, and other structs as properties. We'll also show a slightly different declaration and instantiation syntax for demonstration. | |
Methods are just functions that are associated with a particular type. By declaring a function within the brackets of our struct, we indicate that this function is a method for that Type. More on methods later. | |
*/ | |
struct Point { | |
var x: Int, y: Int | |
} | |
struct Size { | |
var width: Int, height: Int | |
} | |
struct Rect { | |
var origin: Point, size: Size | |
func center() -> Point { | |
var x = origin.x + (size.width / 2) | |
var y = origin.y + (size.height / 2) | |
return Point(x: x, y: y) | |
} | |
} | |
/* | |
There are a couple things different about these. The first thing you probably noticed is that we declared all of our variables on one line. This is a language feature of Swift and it can be used in other situations throughout the language. For example, | |
*/ | |
// Variables | |
var one = 1, two = 2, three = 3 | |
one | |
two | |
three | |
// Constants | |
let uno = 1, dos = 2, tres = 3 | |
uno | |
dos | |
tres | |
/* | |
This can be used at your discretion where it would aid in readability. You probably also noticed that we declared explicit types for our values. This is because we don't set any default values and Swift can't use type inference to know what kind of variable these should be. Let's try and instantiate a new Rect like we did before and see what happens w/o any default values. | |
*/ | |
// ERROR | |
// var newRect = Rect() | |
/* | |
This will throw an error. "Missing argument for parameter 'origin' in call ...". Swift doesn't like empty values, and we must have something set in all of our properties at initialization time. This is a language feature designed to avoid common programming errors. If a value must be able to be nil, you should declare this possibility explicitly via an optional (technically setting a default value of nil). Luckily, as we mentioned earlier, there is a 'memberwise initializer` automatically generated for us. We can use this to make sure that all of our values are set on initialization. */ | |
// Point Memberwise | |
var origin = Point(x: 0, y: 0) | |
// Size Memberwise | |
var size = Size(width: 100, height: 120) | |
// Rect Memberwise | |
var rect = Rect(origin: origin, size: size) | |
// We can now interact with our rect as needed, for example to alter its width | |
rect.size.width = 80 | |
rect | |
/* | |
Now let's make use of that method we declared earlier to find what the center of our Rect is | |
*/ | |
var center = rect.center() | |
/* | |
As we discussed earlier, Structs are value types which means they are always copied when they are assigned to a variable/constant, or passed as an argument. Let's look at what this means in practice. Let's use our point structure because it's a little simpler for explanation. | |
*/ | |
// Create new instance of Point | |
var pointOne = Point(x: 10, y: 10) | |
// 'pointTwo' will have identical values of pointOne, but it is technically a new instance of the value. | |
var pointTwo = pointOne | |
// modify pointTwo | |
pointTwo.x = 20 | |
// They are now different | |
pointOne.x // Still 10 | |
pointTwo.x // Now 20 | |
/* | |
There's one more thing I'd like to discuss about structs before we move on to Classes. This is the concept of 'mutating' methods. If a method inside of a struct will alter some value of that struct, it must be explicitly declared as a mutating method. Let's look at an example. | |
The 'mutating' keyword can also be used on methods within an enum | |
*/ | |
/* ERROR | |
struct Foo { | |
var someProperty = 0.0 | |
func incrementSomeProperty(increment: Double = 1) { | |
self.someProperty += increment | |
} | |
} | |
*/ | |
/* | |
This will show a compiler error "Could not find an overload for '+=' ... ". This is triggered because the function hasn't been given permission to mutate the instance itsself. That doesn't mean it can't be done, we just need to notify the compiler that we're going to do that via the keyword 'mutating'. We also make use of the default value that was discussed earlier in functions. | |
*/ | |
struct Foo { | |
var someProperty = 0.0 | |
mutating func incrementSomeProperty(increment: Double = 1.0) { | |
self.someProperty += increment | |
} | |
} | |
var myFoo = Foo() | |
myFoo.incrementSomeProperty() // Default +1.0 | |
myFoo.someProperty // now 1.0 | |
myFoo.incrementSomeProperty(increment: 2.0) // Increments +2 | |
myFoo.someProperty // now 3.0 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment