👨 🏻 💻 making Demo
Easy to remember:
- Enum, struct, class, protocol
- Enum: Value type, a common type provided by a limited set of possible related values
- Struct: value type, a collection of data of the same type or different types
- Class: Reference type, class inheritable, allows type conversion, has reference counting, destructor method to free resources
- Protocol: Reference type. There is no implementation of properties and methods in the protocol. It can be used as a type
preface
In the development process, we often need to use types other than the basic types provided by the system. Swift, as a growing language, naturally pays keen attention to this, so Swift allows us to build our own type system according to our own needs. In order to be more flexible and convenient to develop the program and called it named types.
Four types of Named types:
- enum
- struct
- class
- protocol
Swift makes enums and structs much more flexible and powerful than the three in Objective-C, and gives them a lot of class-like properties for richer functionality.
This article focuses on the definitions and new features of enums and structs in Swift and the similarities and differences between them and classes.
Enumeration (enum)
The definition of enumerations in Swift is slightly different from that of the C family, where the definition is “a generic type for a finite set of possible related values,” whereas in C/C++/C#, “an enumeration is a named set of integer constants.”
Enumerations allow you to type safely and suggestibly manipulate these values. Like structures and classes, enumerations are defined with the keyword enum, and the contents are defined within curly braces, including the use of the case keyword to list members.
Like the following:
enum StudentType {
case pupils
case middleSchoolStudent
case collegeStudent
}
Copy the code
The code above could read: if there is an instance of StudentType, it is either pupils, middleSchoolStudent, or collegeStudent.
Unlike C and Objective-C enums, enumerators in Swift are not assigned a default integer value when they are created. It is not mandatory to provide a value for every member of the enumeration.
If a value (a “raw value”) is to be supplied to each enumerator, the value can be a string, a character, an arbitrary integer value, or a floating-point type.
The enumerations defined in Swift only need to help us indicate that the situation is different. Its members can have no values or other types of values. Unlike OC, where enumerations are limited to integer types, the disadvantage is that you may not be used to writing OC again.
Enumerations have two confusing concepts: raw value and associated value.
- Raw value of enumeration
Enumerators can be prepopulated with default values of the same type, which we call raw values.
// The raw value of the enumeration
enum StudentType: Int{
case pupil = 10
case middleSchoolStudent = 15
case collegeStudents = 20
}
Copy the code
The three members of StudentType above are populated with Int types 10, 15, and 20 to indicate the ages of students at different stages.
Note that Int modifies the original type of the StudentType member, not the type of the StudentType. The StudentType is a new enumerated type from the beginning of its definition.
// The constant student1 is 10
let student1 = StudentType.pupils.rawValue
print("student1:\(student1)")
// Student2 is 15
let student2 = StudentType.middleSchoolStudent.rawValue
print("student2:\(student2)")
// Create a new instance of the 'StudentType' enumeration using the rawValue attribute of the member
let student3 = StudentType.init(rawValue: 15)
// Student3 is Optional
.type
print("student3:\(type(of: student3))")
// Student4 is nil because you can't get an instance of StudentType with the integer 30
let student4 = StudentType.init(rawValue: 30)
print("student4:\(student4)")
Copy the code
Student4 is an optional type of StudentType, because you can’t always find a StudentType for a given age. For example, in StudentType, there is no StudentType for a given age of 30 (most likely a 30-year-old already has a PhD).
In short, primitive values bind a fixed set of values (which may be integers, floating-point, character types, and so on) that must be of the same type and have different values for enumeration members. This helps explain why the original value is supplied with an equal sign.
- Associated values of enumerations
Unlike the original value, an associative value is more like a set of types bound to the members of an enumeration. Different members can be of different types (associative values are provided in parentheses). For example:
// Enumeration associated value
// Define an enumeration type for StudentType, which has three pupils, middleSchoolStudent, and collegeStudents
enum StudentTypeAssociated {
case pupils(String)
case middleSchoolStudent(Int.String)
case collegeStudents(Int.String)}Copy the code
Instead of specifying values for StudentType members, we bind them to different types: The String (pupil), middleSchoolStudent, and collegeStudents (Int, String) primiples. Now you can create different instances of the StudentType enumeration and assign values to their members.
//student1 is a constant of type StudentType. It's a pupil of 'have fun'.
let student1 = StudentTypeAssociated.pupils("have fun")
print("student1:\(student1)")
//student2 is a constant of type StudentType with a value of middleSchoolStudent, characteristic 7, "always study"
let student2 = StudentTypeAssociated.middleSchoolStudent(7."always study")
print("student2:\(student2)")
//student3 is a constant of type StudentType, collegeStudent, 7, "always LOL"
let student3 = StudentTypeAssociated.middleSchoolStudent(7."always LOL")
print("student3:\(student3)")
Copy the code
StudentType = StudentType; StudentType = StudentType; StudentType = StudentType;
switch student3 {
case .pupils(let things):
print("is a pupil and \(things)")
case .middleSchoolStudent(let day, let things):
print("is a middleSchoolStudent and \(day) days \(things)")
case .collegeStudents(let day, let things):
print("is a collegeStudent and \(day) days \(things)")}Copy the code
Console output: is a collegeStudent and 7 days always LOL. If you look at this, you might wonder if you can provide a raw value for an enumerator and bind the type. Because a member is given a fixed original value, it cannot change it later. Providing an associated value (binding type) for a member is intended to be assigned when an enumeration instance is created.
- Recursive enumeration
Recursive enumerations are enumerations that have another enumeration associated with a value as an enumerator.
Recursive enumerations can be broken down into two concepts: recursion + enumeration. Recursion refers to a way in which a function (or method) directly or indirectly calls itself during program execution. It is characterized by repeating a limited number of steps and a relatively simple format.
The following is a classic recursive algorithm for solving n! A function of factorial.
func factorial(n: Int)->Int {
if n > 0 {
return n * factorial(n: n - 1)}else {
return 1}}//1 * 2 * 3 * 4 * 5 * 6 = 720
let sum = factorial(n: 6)
Copy the code
Factorial (n: int)-> int clearly calls itself during execution. Combined with the concept of enumeration, we can simply be understood as recursive enumeration similar to the above situation of passing enumeration values themselves to members to judge.
As you can see, enumerations have become more flexible and complex in Swift, with the concept of recursive enumerations and many class-like features, such as computed properties that provide additional information about the current value of an enumeration; Instance methods provide functionality related to enumeration representation values; Define initializers to initialize member values; And be able to follow protocols to provide standard functionality and so on.
Structure (struct)
A structure is a collection of data with the same or different types of data. Structure is a data structure of value type. In Swift, structure is often used to encapsulate some attributes or even methods to form new complex types, in order to simplify operations.
A defined structure has an automatically generated member initializer, which is used to initialize the member properties of the structure instance.
struct Student {
var chinese: Int
var math: Int
var english: Int
}
Copy the code
See, when defining a structure type, its members can have no initial value. (Remind this class is not initialized)
- Struct instance creation
The syntax for creating instances of structures and classes is very similar, and both structures and classes can use the initializer syntax to generate new instances.
The simplest syntax is to enclose an empty parenthesis after the name of a class or structure, for example:
let student1 = Student(a)Copy the code
This creates a new instance of a class or structure, and any members are initialized to their default values (provided that all members have default values).
However, if you define a structure without setting an initial value, as in the above direct () method, the compiler will report an error.
// Create an instance of Student (variable or constant) using a struct of Student type and initialize three members.
let student2 = Student(chinese: 90, math: 80, english: 70)
Copy the code
All structs have an automatically generated member initializer that you can use to initialize members of new structs instances as above (provided there are no custom initializers). If we assign an initial value to a member of Student when we define it, the following code will compile:
struct Students {
var chinese: Int = 50
var math: Int = 50
var english: Int = 50
}
let student3 = Students(a)print("student3:\(student3)")
Copy the code
In summary, a structure type can be defined without its members having initial values, but the members of that instance must have initial values when a structure instance is created.
- Custom initializer
When we want to initialize an instance of Student in our own way, the member initializer provided by the system may not be sufficient. For example, we need custom initialization methods when we want to create instances as follows:
let student5 = Student(stringScore: "70,80,90")
Copy the code
Custom initialization method
struct Student {
var chinese: Int = 50
var math: Int = 50
var english: Int = 50
init() {}
init(chinese: Int, math: Int, english: Int) {
self.chinese = chinese
self.math = math
self.english = english
}
init(stringScore: String) {
let cme = stringScore.characters.split(separator: ",")
chinese = Int(atoi(String(cme.first!) )) math =Int(atoi(String(cme[1])))
english = Int(atoi(String(cme.last!) ))}}let student6 = Student(a)let student7 = Student(chinese: 90, math: 80, english: 70)
let student8 = Student(stringScore: "70,80,90")
Copy the code
Once we customize the initializer, the system automatic initializer does not work. If we need to use the system provided initializer, we must define it explicitly after we customize the initializer.
- Define other methods
If you need to modify the results of a certain subject, how to achieve it? You can define the following methods:
// Change a student's grade in a subject
func changeChinese(num: Int, student: inout Student){
student.chinese += num
}
changeChinese(num: 20, student: &student7)
Copy the code
At this point, the Chinese score of student7 has been changed from 70 to 90
But there are two obvious drawbacks to this approach:
Chinese is an internal member of the Student structure. A Student’s score in a certain subject does not need to be known by the user of the Student. That is, we only care about how much the students’ Chinese scores have changed, rather than how much the students’ Chinese scores themselves are.
2. Changing a Student’s language score is itself related to the calculation of the internal members of the Student structure. We prefer to achieve something like:
student7.changeChinese(num: 10)
Copy the code
Because only the students themselves know how much they need to change their language scores (more like the idea of object-oriented encapsulation). It is clear that the changeChinese(num:) method is internal to the Student structure and not external to it, so I define an internal method to modify a Student’s math score to be compared with the external method to modify a Student’s language score:
struct Studentes {
var chinese: Int = 50
var math: Int = 50
var english: Int = 50
// Modify your math score
mutating func changeMath(num: Int) {
self.math += num
}
}
var student7 = Studentes(chinese: 20, math: 30, english: 40)
student7.changeMath(num: 10)
print("student7:\(student7)")
Copy the code
Running results:
Although both can achieve the same effect, it makes more sense to define methods to modify structure members inside the structure and satisfy the characteristics of object-oriented encapsulation. These two points are why we added changeMath(num:) inside the Student structure to make type-related calculations appear more natural and unified, that is, our own things should be implemented in our own way and not be cared about by others.
It is important to note that internal struct methods that modify struct members should be preceded by the “mutating” keyword. Because a structure is a value type, Swift states that members cannot be modified directly in a structure’s methods (except initializers). The reason is simple: how can a structure, as a representation of a value, provide a way to change its value, but using mutating we can, and this is also different from a class.
- Common structures
Many of the basic data types in Swift are struct types. Here are some common struct types:
// A structure representing a numeric type:
Int.Float.Double.CGFloat.// Represents a structure of character and string types
Character.String.// Structure of position and size
CGPoint.CGSize.// Set type structure
Array.Set.Dictionary.Copy the code
A lot of times you might not realize that there are so many structures hidden in your random code if you don’t look carefully. In addition, the following situations sometimes occur when using classes and structures
Methods:
/ / the Person class
class Person {
var name: String = "jack"
let life: Int = 1
}
var s1 = Person(a)var s2 = s1
s2.name = "mike"
s1
Copy the code
Structure method:
// People data structure
struct People {
var name: String = "jack"
let life: Int = 1
}
var p1 = People(a)var p2 = p1
p2.name = "mike"
p1
Copy the code
Careful students may have spotted something weird. S1 and s2 are instances of the Person class. If the name attribute of S2 is modified, the name of S1 will also change. However, p1 and P2, as examples of People structure, modify the name attribute of P1, and the name of P2 will not change. Why is that?
Class can be changed because, class is a reference type, internal shallow copy processing, essentially refers to the same object, naturally can change the name; The structure is a value type, and the internal deep copy process regenerates an object. Modifying the data of an instance while copying does not affect the data of the copy.
The performance comparison
The essence is performance against ratio types and reference types, so the players in the ring are structs and classes.
Test 1: Create classes and structures through a loop
A. Perform 100 million class creation operations
/ / define the class
class StudentC{
var name:String
init(name:String) {
self.name = name
}
}
// Count time
let date = Date(a)for i in 0.100 _000_000{
let s = StudentC(name: "Go Cool.")}print(Date().timeIntervalSince(date))
Copy the code
Result of running three times:
19.3928480148315 20.9919492812921 20.7549253872943
B. Perform 1 billion structure creations
// Define the structure
struct StudentS{
var name:String
init( name:String) {
self.name = name
}
}
let date = Date(a)for i in 0.1000 _000_000{
let s = StudentS(name: "Go Cool.")}print(Date().timeIntervalSince(date))
Copy the code
Result of running three times:
9.99221454212455 10.9281648273917 10.7281881727434
Our above property is a basic data type, let’s change the property to an object to test the speed
C. Create 10_000_000 objects
class StudentC{
var date = NSDate()}for i in 0.10 _000_000{
let s = StudentS()}Copy the code
Test results:
6.38509398698807 6.43649202585222 6.39519000053406
D. Create 10_000_000 structure instances
struct StudentS{
var date = NSDate()}for i in 0.10 _000_000{
let s = StudentS()}Copy the code
Test results:
4.38509398698807 4.43649202585222 4.39519000053406
Conclusion: Creating structures is faster than creating objects
Test 2: Create 1000_000 objects or structures in an array and check the memory usage
A. Create 1000_000 objects in a loop
class StudentC{
var name:String
init( name:String) {
self.name = name
}
}
var students:[StudentC] = []
/ / create
for i in 0.1000 _000{
let s = StudentC(name: "Go Cool.")
students.append(s)
}
Copy the code
Running results:
The memory usage is 61.8MB
B. Loop to create 1000_000 structures
struct StudentS{
var name:String
init( name:String) {
self.name = name
}
}
var students:[StudentS] = []
for i in 0.1000 _000{
let s = StudentS(name: "Go Cool.")
students.append(s)
}
Copy the code
Running results:
The memory usage is 32.6MB
Again, we change the base properties to objects to continue testing
C. 10_000_000 objects are added to the array
class StudentC{
var date = NSDate()}var students:[StudentC] = []
for i in 0.10 _000_000{
let s = StudentC()
students.append(s)
}
Copy the code
Test results:
538.7 MB of memory
D. 10_000_000 structures are added to the array
struct StudentS{
var date = NSDate()}for i in 0.10 _000_000{
let s = StudentS()
students.append(s)
}
Copy the code
Test structure:
Take up 225.7 MB
Conclusion: Creating structures with the same properties saves more memory than classes
Test 3: Sort 1_000_000 structure entities and objects and measure the elapsed time
A. Sort the 1_000_000 structure entities
let date = Date()
students.sort { (stu1, stu2) -> Bool in
return stu1.name > stu2.name
}
print(Date().timeIntervalSince(date))
Copy the code
Running results:
13.3783949613571 13.6793909668922
B. Sort 1_000_000 objects
let date = Date()
students.sort { (stu1, stu2) -> Bool in
return stu1.name > stu2.name
}
print(Date().timeIntervalSince(date))
Copy the code
Running results:
6.70881998538971 6.60394102334976
Conclusion: The sorting speed of structures is slow in sorting with a large amount of data, because structures are value types and require a lot of assignment operations when sorting. Objects only need to exchange addresses.
conclusion
Enumerations, structs, classes have in common:
- Define properties and methods;
- Subscript syntax access values;
- Initializer;
- Support extension to add functions;
- Can follow the protocol;
Differences between structures and classes:
- Classes can inherit, but structs cannot;
- Classes can check and interpret the type of a class instance at run time;
- Deinitializers allow an instance of a class to release any resource allocations;
- Classes have reference counting, allowing objects to be referenced more than once;
Class-specific functionality:
- Inheritance;
- Allows type conversions;
- Destructor releases resources;
- Reference counting;
How to use:
When you use the Cocoa framework, a lot of the apis are used by subclasses of NSObject, so you have to use the reference class. In other cases, there are several guidelines:
1. When to use value types:
- When the == operator is used to compare instance data
- When you want copies of that instance to remain independent
- Data will be used by multiple threads
2. When to use reference types (class) :
- When the == operator is used to compare instance identities
- You want to create a shared, mutable object at some point
3. Differences in efficiency between classes and structures:
Structure creation speed, smaller memory footprint, if you need to use complex operations, this time, you need to consider the disadvantages of both.
Above post since: https://www.jianshu.com/p/51f99a352838 and http://www.cocoachina.com/swift/20161221/18377.html