Last active
August 29, 2015 14:02
-
-
Save loganwright/b22f225b12d7dcf43b67 to your computer and use it in GitHub Desktop.
Classes
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 | |
// Just a restatement for myself because it applies to Classes and Structures -- No need to read this part. | |
/* 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 | |
*/ | |
// ---- BEGINNING PROPER | |
/* Let's declare our first class */ | |
class BoringClass { | |
} | |
/* | |
That's it, we have a class! As you can see, its similar to our struct declaration with the 'class' prefix instead of 'struct'. You'll also probably notice that there is no SuperClass. It isn't necessary to declare a superclass unless you want to. We'll talk about that a bit more later. For now, let's add a bit of functionality to our class with a couple of properties. The syntax will be the same as when we declared these in a struct. | |
*/ | |
class Car { | |
var model = "" | |
var year = 0 | |
} | |
/* That looks fine, and incorporates the default values we have come to expect with Swift. What happens if we don't declare a default value? */ | |
/* ERROR | |
class Person { | |
let name: String | |
var age: Int | |
var nickname: String | |
} | |
*/ | |
/* | |
There are a few things you should notice about this. | |
1. Because we're not setting a default value, we need to notify the compiler of what we intend to store in that variable by giving it a Type explicitly. | |
2. We declare the name as a constant via `let` because we are making the assumption that a person's name will not change. | |
3. The above declaration gives the error "Stored property ' ' without initial value prevents synthesized initializers" & "Class 'Person' has no initializers". This is because as we discussed in structs, properties must be explicitly optional(which defaults to nil) or have a value by the end of initialization. Remember when structs automatically generated a 'memberwise initializer'. Classes don't do this, we'll have to implement it ourselves | |
*/ | |
class Person { | |
let name: String | |
var age: Int | |
var nickname: String? | |
init(name: String, age: Int, nickname: String? = nil) { | |
self.name = name | |
self.age = age | |
self.nickname = nickname | |
} | |
} | |
/* | |
This satisfies the compiler because all property values will be set by the end of initialization. You'll also notice the keyword self being used to differentiate between the parameters in the init method. In Objective-C, having the name of an argument match the name of an ivar might cause a namespace error, but in Swift this isn't a problem. It's important to remember that within the scope of a function, the argument parameters take precedence over the global properties. This means that if we want to access a global property of the same name, we must explicitly attach it to self. Xcode will aid in distinction w/ syntax highlighting. Most of the time, calling self won't be necessary within an class | |
Let's also talk about the 'nickname' property. For this class, we're assuming that not everybody will have a nickname, so we'd like to have that be an optional. Because that variable is an optional, it is defaulted to nil and we don't need to include it in our initializer. However, because it might be useful to also set the nickname during initialization, we include it with a default value explicitly setting it to nil. In this way, it isn't necessary to be included in the initialization, but it can be if the user prefers. | |
Let's initialize this class. Remember, we have to provide a 'name' and an 'age' with our initializer, but the 'nickname' is optional. | |
*/ | |
// ERRORS | |
// var errorPerson = Person() | |
var personOne = Person(name: "John", age: 26) | |
var personTwo = Person(name: "Fred", age: 36, nickname: "T-Bone") | |
/* We can access these properties through familiar dot syntax the same way we did with our structs */ | |
// Errors because declared 'let' | |
// personOne.name = "Jim" | |
// Can assign just fine | |
personOne.age = 35 | |
/* | |
Up to this point, we haven't declared our classes as subclasses explicitly. This time, let's do just that and make a subclass of our Person class. | |
*/ | |
class Mutant: Person { | |
var level: Int | |
var superPower: String | |
init(name: String, age: Int, superPower: String, level: Int, nickname: String? = nil) { | |
self.level = level | |
self.superPower = superPower | |
super.init(name: name, age: age, nickname: nickname) | |
} | |
func isMorePowerfulThanMutant(mutant: Mutant) -> Bool { | |
return (level > mutant.level) | |
} | |
} | |
/* | |
This follows a lot of the same conventions of our original class method, but let's start with the order of our init. | |
1. Setting the value of properties that the subclass declares. | |
2. Calling the superclass’s initializer. | |
3. Doing anything else it needs to do | |
If you try to do it out of order, the compiler will give you a warning. We can now use our mutant subclass like so: | |
*/ | |
var jim = Mutant(name: "Jim Neutron", age: 23, superPower: "Flight", level: 7, nickname: "Flyin' Jim") | |
var janet = Mutant(name: "Janet Jackson", age: 32, superPower: "Telepathy", level: 8, nickname: "Brainiac") | |
janet.isMorePowerfulThanMutant(jim) // true | |
/* | |
Before we move on to talk about reference type in more detail, I want to mention one more common class functionality. The Class Method, or in the case of Swift, the 'Type Method'. It is declared using syntax we're already comfortable with, but with the added 'class' prefix. | |
*/ | |
class SomeClass { | |
// Type Method | |
class func typeMethod(#string: String) -> String { | |
return string + "_ModifiedInClassMethod" | |
} | |
} | |
/* | |
A Type Method doesn't require the creation of an instance to be useful. | |
*/ | |
var strToModify = "Happy String" | |
var modifiedStr = SomeClass.typeMethod(string: strToModify) | |
/* | |
Up to this point, we've covered some core class functionality in Swift, now let's talk a bit about how Class type instances are retained. If you remember when we talked about structs, we said they were 'value type' This means that they are always copied when they are passed as an argument, or assigned to a different variable, etc. Class instances function a bit differently, and are reference counted. This means that multiple variables or constants could have references to the same instance. This concept can be fairly complex, so we're just going to cover the basics of what this means. Take this simple example | |
*/ | |
class SimpleClass { | |
var stringProperty = "My String" | |
} | |
let variableOne: SimpleClass = SimpleClass() | |
variableOne.stringProperty = "Hello World!" | |
/* Now let's add another variable to reference that instance */ | |
let variableTwo = variableOne | |
/* | |
At this point, variableOne and variableTwo both point to the same instance of SimpleClass in memory. If we modify one, the value associated with both variables changes. When we assign 'variableTwo' there is no new initialization of a 'SimpleClass', it merely adds a reference to the already existing instance. | |
If we were to change the values through one variable, the other variable would also contain the same values. | |
*/ | |
variableOne.stringProperty = "Changed" | |
variableTwo.stringProperty // == "Changed" as well | |
/* | |
Before we wrap up talking about classes, there's one more language feature I'd like to point out. We can now edit struct subproperties as properties of a class without creating new instances! This may not seem like much, but it's really helpful. Let's clarify this with a basic example. | |
*/ | |
class BasicClass { | |
var rect: CGRect = CGRect(x:0, y:0, width:100, height:100) | |
} | |
/* | |
I brought CGRect back into the mix just because its a familiar comparison from ObjC. If this were an ObjC class and we wanted to change the width of our rect, we'd have to create a new one, or do something like this: | |
*/ | |
var basicInstance = BasicClass() | |
var rect = basicInstance.rect | |
rect.size.width = 200 | |
basicInstance.rect = rect | |
/* | |
This got cumbersome at times and felt a bit unnatural. Well, with Swift, this is no more! You can edit the subproperties of your struct without problem! | |
*/ | |
basicInstance.rect.origin.x = 10 | |
basicInstance.rect.origin.y = 10 | |
basicInstance.rect // [x 10 y 10 w 200 h 100] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment