Objective-C as the first programming language
- From the sandbox
- Tutorial
This post was conceived after some clear questions were not found clear answers. I do not pretend to be a cool programmer. No, still ahead, but the hatching period has already passed. This article is from the series "Do not know how to do it yourself - teach another." I mean, in order to understand something better, you need to explain this to someone. The moped is not mine, this phrase met me earlier in publications on Habré. Some things are very difficult to understand. And people who understand usually cannot explain to a beginner. Maybe it is waiting for me too. It is like a conversation between an adult and a child. Almost a generational conflict. Until my level has grown into a professional, I need to state my current vision.
In the article, the terms that are used to explain various concepts are quoted. The article is written for people who have already learned to distinguish between loops and arrays and understand what a function and method are.
If you have never programmed, then the book of Kernigan and Ritchie (in any edition), you obviously will not be able to. In nature, there is no such willpower that could force you to read the book to the end, while solving all of the above problems.
Очень рекоммендую BecomeAnXcoder. Там всё написано достаточно понятно для людей, которые о программировании знают крайне мало. Информация немного устарела, но для старта сойдёт. Может она Вам уже попадалась на глаза или вы даже скачали её, но отложили в каталог с литературой (ну такой каталог есть у каждого, он занимает десятки гигабайт и всё это мы собираемся прочитать как только появится свободное время, а каталог всё растёт...).
Понятно, что оптимальный вариант — MAC. На худой конец — Хакинтош или виртуальная машина. И даже если процессор не позволяет запустить ничего из вышеизложенного — начать можно прямо в блокноте с последующей компиляцией в коммандной строке.
In many forums, you can find a popular question posed by people who are apparently in a heavily armored tank: "Does the Objective-C programmer need to learn the actual C?" Everywhere they write the same thing. Those who are more bearded and started with structural programming - they say that you just need to become, at least, MiddleDeveloper in C, and only then look for the way in the fierce wilds of OOP, which is Objective-C. Those who did not know C, but had already somehow reached the MiddleDeveloper status, object: they say, learn how the buttons on the iPhone are drawn and how to assign a function to them, and the materiel will then be tightened, as necessary. I categorically declare that to learn how functions are assigned to buttons, there is no programming! I do not urge you to spend half a year understanding pure S. But a couple of weeks is worth a month. This will give some background to your ambitions. As you learn Objective-C, such code structures will necessarily appear that nothing will be clear. Therefore, I propose without any fanaticism, but to familiarize yourself with the book of Krupnik A.B. "Learning C". It is 3 times larger than the previous one in volume, but it is read easily and naturally. The book again, at a fairly affordable level, explains the basic basics of programming. In it, you can learn how to distinguish an array from a loop and what pointers are. at a fairly affordable level, the basic basics of programming are explained. In it, you can learn how to distinguish an array from a loop and what pointers are. at a fairly affordable level, the basic basics of programming are explained. In it, you can learn how to distinguish an array from a loop and what pointers are.
Recently, an article flashed on Habré, where the author cited a bunch of literature, which he advised to read and go to work as a junior for bread. If you just read all the literature cited in that article, then you won’t learn anything, and if you can redefine and remember all the literature again, then you can become a solid developer, not a junior.
What is a method? This is a set of commands / instructions or one command / instruction that can be applied to an object and invokes the necessary processes in it. If this is not entirely clear, then read on and understand. It’s actually hard to understand what a class method and an instance method are. Everyone says: “with the“ + ”sign is the class method, but with the“ - “sign is the instance method. Is that clear? ”And in response they hear:“ Yes, the class method and the instance method. ” At first, I personally only understood that they were different, but what and when to apply remained unclear. Wikipedia stubbornly repeats the same thing that we heard and read.
In the vast majority of cases, you will work with instance methods.
So, the instance method. Imagine that we have a program in which we will use 2 classes: Primus and Kitchen.
Any class (except abstract) has methods and / or functions. A class can have several methods with a “+” sign in front of a method name (a class method) and several methods with a “-” sign in front of a method name (an instance method). These are the methods that we use with a “-” sign in relation to objects — instances of the class and the class itself. And methods with a “+” sign can only be applied to the class itself, where this method was declared.
It sounds proud, but incomprehensible. Who remembers from the school course: in geometry, a dot is a parent for a circle, a straight line, and other shapes, because each shape consists of many points.
A class for Objective-C, on some resources interpreted as an object and vice versa. Actually this is a little on like that. We can say that an object is an instance of a class. As a concrete instance of the primus in your kitchen, this is a Primus type specimen: with twisters, a burner, etc. This is not just a primus - it is your primus. A class is an example to follow. A reference sample of something that can also perform some functions thanks to class methods (with a “+” sign). Here you can read a little more detailed.
There are also “protocols” and “abstract classes”. Everything is more interesting here, but the threshold of understanding is higher than that of a class and an object. The difference is that a “protocol” cannot have a subclass or a child object inherit from it, it needs to be matched. As a requirement when applying for a job. The application of protocols in real life I will touch on in this article. An abstract class is an entity that is, as it were, a class, but not a very concrete class, and therefore abstract. For example, again, your home primus is an instance of the Primus class, and not some abstract heater class. You cannot have a heater at home without a clan and tribe (it can also be a fireplace and a battery). In this way, the class "Primus" - may be the heir to the abstract class "Heating device". And already your primus is an instance of a specific definition of a primus. And for his attributes - properties, methods, protocols, when using it, he turns to his class and checks what he can do, what he cannot and what he can have in general.
Imagine that there is a certain class “Gas burner” - the heir to the abstract class “Heating device”. The class “Gas burner” will be the parent for the “Primus” and “Gas stove”. In order to get closer to programming, you need to abstract a little.
Objective-C has a parent class, NSObject. In a programming language, it is like a point in geometry. This is an ideal class: there is nothing superfluous in it, it does nothing concrete, but you can create anything from it. Such is the brick of the universe. If you hold down the Command button in Xcode and click on NSObject, then the contents of the NSObject.h file will open in front of you. It contains about 200 lines of description of various methods. There are both methods with a "+" sign in front of the name, as well as methods with a "-" sign. The essence of the methods with a “+” sign is that they can only be applied directly to NSObject itself. This method is, for example,
and you can apply it only like this:
Any other object in Objective-C is, by definition, a descendant of NSObject. It implements additional methods that give it personality traits. Let's say this is an NSResponder. This descendant, NSResponder, can also have children. For example, UIView. Further along the inheritance tree, you can take UIScrollView as an example. Then - UITableView. In each case, the descendants are overgrown with additional functionality. In addition to the fact that they know how to do something their own, individual, they can do everything that their ancestor can do. This is inheritance .
If you do not want your child class to exactly copy the behavior of the parent class, then you can redo any method of the parent class to your liking. This is polymorphism .
In your programs, you will take the class you need and add the necessary methods to it - to expand its functionality. You can also take the class you created and make it a descendant, again expanding the functionality and at the same time creating a unique tool to solve the desired problem. The described mechanism is called - creating subclasses of "subclass".
I will not go into the broad descriptions of methods with the "+" sign. It will be clearer to show that the application of the method
to any class or subclass allocates the necessary amount of memory in the RAM of the computer for it.
Method application
initializes the object, allocates a place for it in RAM, and actually after that the object begins to exist and you can work with it.
These 2 methods (“alloc” and “init”) have just created an object. And this object is an instance of an object or an instance of the NSObject class. If you apply these methods separately
then the object will also be created. But there is a non-zero probability that you will create the wrong object to which the memory is allocated. Let's say a method call
allocated for him such an address in memory
This happens because during initialization, the runtime environment sets all objects to “nil” to avoid referencing a place in memory filled with “garbage”. Or links to another specific object, which in fact is also “garbage”, because it has completed its life cycle and is no longer “stored” in memory, but simply “located”. In order to understand what “garbage" is, create a simple program in which you declare
without assigning a value to it, and then output its value to the console
You will be surprised at what your “i” equals.
An instance of the isa object will also be created, which will be discussed later. This is the only non-zero pointer when creating your object. Suppose this is a thread to a parent object that will spawn its instance and give life to our object.
That is, theoretically, the object already exists after applying the "alloc" method. But he cannot pass the test
Such a piece of code can be seen in any template object in the init method or its derivatives.
In this case, the object seems to be there, but in principle it is equal to "nil". An uninitialized object is like a machine that you are only going to buy on credit. It seems to be, but it is not yours. And if, without departing from the cash register, use the init method
then “Bingo!”, and the object belongs to you, you can do whatever you want with it. But if you apply the method to it
then another object may be initialized. It seems like you paid a loan for a car, but it turned out that you got the wrong car and they give you another instead of one car. They at first glance are not distinguishable from each other. But they have different “VIN” numbers, which means that when checking at the “GAI” post you will be informed that the car is not yours. A further explanation of this phenomenon will be given below.
As you already understood, the “+ alloc” method is a class method, and the “-init” method is an instance method.
I will give an example with real things.
The full implementation of the method usually includes 2 files with the extensions * .h and * .m. There are other numbers, depending on what kind of class it is and what goals it pursues.
Your program has a Primus class. The following methods are declared in the file "Primus.h":
The program also has the “Kitchen” class, where the methods are declared in the “Kitchen.h” file:
In the files “Primus.m” and “Kitchen.m” all declared methods must be implemented, that is, described: how and what happens in each method.
If you intend to create an instance of an object of another class in any class, then in the header you will need to import the * .h file of the created object, for example, in the header of the Kitchen.h file we will enter
That is, it will be possible to create the Primus class inside the Kitchen class.
Since we imported the Primus.h header file into the “Kitchen.h” file, in the “Kitchen” class we became available with the methods of the “Primus” class instance that are declared in the Primus.h file. They can be applied either in relation to a class, or in relation to an instance of the Primus class.
In the Primus.m file, you must implement all the methods that were declared in the Primus.h file, but you can also implement any method that is not declared in the Primus.h header file. This method will be available only from within the class and cannot be called in another class in the program. You can also create variables inside the Primus.m file - they will also not be available for external use. They simply do not know about them. Therefore, a file of type * .h is called the class interface. Only what is explicitly indicated in it is accessible externally to the class in which it is written
First, create an instance of the Primus class inside the Kitchen class
As you can see, we are sending the c + method not to an instance of the myPrimus class, but directly to the Primus class. An instance of the Primus class called myPrimus has been created. Further, the methods from "Primus.h" with the sign "-" will be applied to the class instance - "myPrimus". And if we want to create a new instance of the Primus class, we can again use the + hotAsHell method. There is also a method in the Primus class
Usually, all methods whose name begins with “init” do the same as the “init” method of the NSObject class, but with advanced features. And in this case, the use of the “init” method from this angle
will create a new object of the Primus class with unique characteristics.
In the example just described, a method with arguments was applied. In this particular case, it is used to create an instance of the Primus class with clearly defined characteristics. The name of the characteristics shows that this instance of the Primus class will work on gas of the Propan type and at a temperature of 750 degrees.
Note that the names of the methods in Objective-C are very meaningful. At times, you will be amazed at their 10-word names, but this contributes to a better understanding of what is written. Roughly speaking, the code is self-documenting. Which of course does not preclude the need for commenting. But if you make it a rule to create your own methods, the name of which will carry a semantic load and relate to the creation of variables in the same way, then the number of comments in the code can be significantly reduced.
Unlike some other programming languages in Objective-C, methods are not called, but sent to objects. Thus, this language refers to the "message oriented language", in Russian it does not sound so strict, so I do not quote the translation.
Also note that all methods that, after sending to the object create (return) a new object, have in brackets after "+" or "-" either the specific name of the object whose instance they will return, or "id". That is, by what object is indicated in brackets immediately after the “+” or “-” sign, we can judge what we will get after sending this method. What is an id? This is a type of object that can take the form (type) of any other object. Let's say this is a “weak” type of object. Not in the sense that something is “weak” to him. In general terms, this is a weakly typed object. The presence of such objects in the PL makes it weakly typed.
You can also find the “void” type in these same brackets.
Everyone says: "this is when the method returns nothing." But why is it needed? Applying a method of this type nevertheless produces manipulations with objects, structures, and numbers. Also, objects and scalar quantities that fall into it as arguments can change or disappear altogether. It all depends on what is implemented within each particular method. The fact is that "void" doesn’t return anything ... new. As was said, the arguments that fall into it can change themselves, or affect other objects that are manipulated in the “void” method. He can even create new objects within himself, but nothing comes back outright. Usually a method that returns something has the keyword “return” at the very end. And the method like "void", "return" at the end does not have. He can have it inside his own cycles or conditions in order to be able to interrupt them in the right place. But not at the end. The method does not create anything fundamentally new, it simply, working with a pointer to some object (number, array, cell, etc.), changes the data at this address or uses it to calculate other data that it assigns to other objects.
This word is often used anywhere. The fact of its application in various places introduces confusion into the harmonious pantries of your knowledge.
Let's return to the classes “Primus” and “Kitchen”. Take a concrete method of the Primus class and consider how its implementation in the Primus.m file will look. For example, this is a method.
can be implemented this way:
In curly brackets, the method is implemented or "implied". In this case, the implementation of the method reads as follows: if not myPrimus, then run the command
which in turn reads: initialize itself to gas: gas temperature: 750. That is, the method is sent to itself, and not to some other object. In this case, “yourself” is for the Primus class, since the method is inside the Primus class. Its declaration is in the file Primus.h and the implementation in the file Primus.m.
You may also notice that the method returns nothing “void”. That is, a pun comes out: if there is no primus, then we create it, but it does not go outside the method. Why create it then? Suppose its functionality is implemented as follows:
And here it’s already clear that somewhere inside the method a primus repair wizard appears.
Why was the exclamation mark "!" in brackets after "if"? In programming, this is a sign of negation. That is, "! =" Sounds like "not equal." This method of condition is convenient for its brevity. In this example, a different condition could be used that would carry the same function.
This condition is 2 times longer and you need to read the entire line in order to understand it. This condition is small, but if you need to write something like this
then this structure
will be in a more advantageous position, both in brevity and in physical size. The previous construction - it may simply not fit into one line.
Let's consider how the implementation of this method would look like in the framework of the "Kitchen" class. In the file “Kitchen.m” the same method will be implemented like this:
It can be seen that here you need to specify which object will be created, how its instance will be called, and then send the method directly to the parent class.
That is, the tricky word “self” is used inside the class, as if you had already created an instance of it.
and refer to the class instance. The only trick is that you did not create anything, and do not access the instance, but directly to the class. For clarity, I created an object
which can be accessed with the method. But it was possible not to create it, but to implement a more complex structure that would perform the same functions
In order to understand whether it is possible to write “self” or not, just imagine what method you want to send to which object and start typing the word “self”, but not completely, for example, “sel”. Xcode will offer several options, among which will be actually "self". To his left will be a description of what class it is. If this is exactly the class to which you wanted to send the desired method (the class within which you are currently performing the action), then use "self". If Xcode indicates that “self” is not the object you need, then use the name of the class or instance of the one you need.
It is also evident that the arguments were involved here. Each argument has a type, which is also described in parentheses, but after the colon. After the brackets with the type of the argument comes the name of the argument. If there is a "*" sign in parentheses after the argument type, then this is an object. If there is no “*” sign, then this is a scalar type, for example, “NSInteger”, which is essentially a simple “int”. Also, "NSInteger" may be marked with "*" for the argument passed. In this case, we will not redo the variable itself, but a pointer to it. The topic of Indexes is beyond the scope of this article. The name of the argument in this method
will sound like "gas". Did you notice that the normal gas name was not used in the implementation? For example, "Propan". The fact is that in the argument "gas" just the name of the gas is passed. Where from? We examined how the method is implemented.
The method was involved in it.
There were 2 arguments “gas” and “t” in this method. In sending the method to the object, we indicated the temperature, and the type of gas was simply duplicated from the method
That is, this method is also sent somewhere. Let's take a closer look at the method
Its implementation may look like this
Note the value of the argument "propanButan" sent. It is it that is transmitted throughout the class, from method to method.
It is also visible here that the method is sent to the object under certain conditions.
You need to know that not one of your methods alone can work. He needs a “kick” outside. Pressing a button, the occurrence of an event, or it should be launched by the protocol method, which the runtime runs at the right moment. This “hotAsHell” method is sent only after an explicit call, this is not a system method, it must also be registered somewhere or assigned to something.
Now consider the method
It consists of a return type, argument types, arguments, and a selector. The selector is what remains of the method when the type of the returned object, the type of arguments, and arguments are taken away from it.
with colons. If there are no arguments, then there are no colons either, as is the case with "hotAsHell". Often you will have to deal with the need to use a selector. Now you know what to use.
A little higher was used the concept of "super".
Consider the hotAsHell method implemented above. It has such a design
So it is customary to initialize the child class - through the initialization of the parent.
Suppose that the Primus class is a direct descendant of the NSObject class. It is the class that stands in hierarchy immediately above the class with which we work has the right to be called "super". Command
calls the init method of an NSObject instance. That is, an instance of the progenitor of all objects is automatically created - NSObject, with default (purely NSObject's) parameters. Sending the "init" method to NSObject'y returns "self" from NSObject. That is, directly an instance of NSObject itself. And this instance is assigned to our Primus object. Indeed, “self” in this case is precisely “Primus”. And at this stage we get an instance of the Primus object, which is no different from NSObject. Our package method gives individual features to it.
as well as other methods implemented within the framework of this class.
Design
it’s just reinsurance in case something goes wrong and the default “Primus” is not created. those. The init method of the parent class will fail. In this case, they usually do
where they are trying to correct the situation, but, unfortunately, this is not the case and the "else" will not help us.
And here I want to remind you of the uninitialized object from the beginning of the article. It is during the initialization of an instance of an object by its superclass that a place in memory is assigned to it and a pointer is transferred to that place in memory. And when we check
it is at this very moment that "self" can receive an unexpected address in memory. That is, the resulting pointer will no longer be "self". The superclass will not return us something out of the ordinary. The instance of the object will be identical to the one we are going to work with. But the instance of this particular object will not be the instance that we need. This is such a rare mistake that you may not observe it for a very long time in your applications. Just at one point, your application may start to generate exotic errors. As a result, you have to spend time finding their cause. The allocated memory for your object was not used. A random pointer could be initialized, having no connection in the object to which the memory was allocated using the "alloc" method. Then "self" does not pass the test
In the initialization method under consideration. And all because at some point in the program, when this object was called, the initialization was not performed properly.
The last thing we need to do to complete the initialization
Since this method is not "void", it expects us that in the end we will tell it what needs to be returned. In this case, an object of type “Primus” will be returned, because “self” in the framework of this class is precisely “Primus”. Also, the type of the returned object tells us what exactly is expected to be "Primus".
In these methods
the type of returned objects is “Kitchen”, “UkrainianBorsch” and “MasterPoRemontu”.
Thus, in the “hotAsHell” method, we say that at the end of the execution of this method we want to get an object of type “Primus” with the given parameters.
We describe such an implementation of the method
This means that when calling this method
literally such parameters are set
Parameters are set by the variables that are declared in the * .h file.
Note the construction of the word "setGas" or "setTemperature". If there is a variable, for example “variable”, then you can set the desired value for it through the “set” prefix:
the first letter of the variable becomes capitalized. Thus, we found out that the variables "gas" and "temperature" were declared in the file Primus.h. But the very existence of variables does not allow us to assign them using the “set” prefix.
To obtain such an opportunity, we need to declare properties for these variables. Let's admit such:
and in the implementation file you will need to register
and only after that it will be possible to assign values to them through "setGas" and "setTemperature". These prefixes are called setters.
At any convenient moment, you can ask the Primus object to show you these values, referring to its instance
or
for example for a team
These methods are called getters. In the case of accessing the values of the variable name, nothing needs to be added.
Note that the property of an object can be accessed as through a construct
and through
They perform similar actions.
There is one trick when accessing object variables. This can be done not only through properties, but also through access to them on a key-value "key-value coding" basis. That is, you can refer to the variable
So
We applied for the value that is in the “Primus” object in the “gas” keyword.
And such a design
in the context of “key-value coding” it will look like this
Using key-value coding instead of properties is a tedious task. But it is necessary to have an idea about it, so in the case when you cannot do without it, take advantage.
Also, it will not be amiss to point out the fact that recently, variables in Objective-C programming are no longer used. Instead, they use properties. Properties can be either externally accessible or encapsulated. Naturally, for use inside methods, variables can and should be used. But declaring variables in the ivar block, which is in the class interface, is already an anachronism.
Even if this has not happened yet, it is likely that you will soon find the word “runtime” in the documentation and, within the same article, the word “isa”.
The concept of "runtime" can be described as "the environment in which your code is translated to a lower level code." "Runtime" is written directly using C and Assembler. This is not yet a translation into machine code, but a conversion of your code to the C and Assembler languages. Your method
in "runtime" in C, it looks something like
This should be enough for a novice Cocoa programmer to understand: it’s better not to go further into the jungle. As soon as you cross the threshold of entry into the club, you yourself can find the information you need.
This is a variable that is declared directly in NSObject. Its only variable. When we called the method
then not only an instance of the NSobject class was created, but also an instance of this isa variable, which refers to NSObject. That is, it specifically tells runtime that it belongs to an NSObject. This means that you need to work with an instance of your newly created object just like with NSObject. A pointer to the parent object that we need to inherit is written to isa. Let's say your object is a descendant of NSArray or UITableView or CFDictionaryRef or any other object. In this case, “isa” points to an NSArray or UITableView or CFDictionaryRef or any other object, respectively. So creating an instance of any object also creates a class variable - "isa", which refers directly to the parent class, as a result of which "runtime" knows what will be done with each instance.
This information at the training stage is not needed for anything specific, but, in principle, for a more comprehensive understanding of the Objective-C programming philosophy.
In the process of reading books and various documentation, the concept of “singleton” has come to your eyes more than once. As one popular Internet meme says: “You can’t immediately grasp and understand what a singleton is.
Imagine that you need to create an object that, every time it is called at any point in the application, returns the same instance. In fact, in the process of creating applications, you really need to create one. So why can’t you create an object several times and assign the same data to it through the “initWithSomething:” method or using setters? It's all about working with memory and speed, and indeed, with less time spent writing code. There is always little memory, and even when they put 2GB of RAM on the iPhone6, it will again be small. Creating one instance of the object, and then accessing it, saves device resources and speeds up the application. But everyone wants his application to be as fast as “Bugatti Veyron” and usable as the word “horseradish”.
Let's say that our “Primus” may well be a singleton. Then his method
during implementation it will look like this
Consider what this means later. Let’s find out why “Primus” is a singleton.
Suppose this is not quite the usual "Primus", but rare. It has an engraving by an unknown master, it has an extremely low gas consumption, and also it has a special box in which it fits perfectly. Now ask yourself the question: “Do you need another primus?”. Of course not! But when sending the method
one “Primus” will be created, and when sending the method
a completely different „Primus“ will be created. Without engraving and boxes.
Now back to what is written in the implementation of the “hotAsHell” method.
First you need to create an instance of the object with the “static” property in order to block access to the object from the outside. Then assign him “nil” so that he does not take a random address in memory. Design
creates a predicate (condition) - "predicate" which also will not be visible from the outside. The condition is that automatic or dynamic calling of the block behind the predicate is prohibited. And here is the line
already takes all the necessary steps to create a unique instance of the object. Specifically, “dispatch_once” means that the expression in brackets after it is guaranteed to be launched only once during the entire life cycle of the application. Also "dispatch_once" guarantees thread safety. That is, when the application is launched in multithreaded mode, this function will not be called simultaneously in several threads, and it certainly won’t create another “unique“ Primus “” for us. There is also a block
This is kind of a small function or method. There are also large blocks. In this block, the parent object is initialized, after which its value is assigned to the descendant of myPrimus.
Together the whole line
means: once during the entire life cycle of the application, an instance of the Primus object called “myPrimus” with the properties of the parent object will be initialized and it will not be possible to access this block in another way. But no one will know that “myPrimus” was created, because this instance of the object is not visible from the outside. Everything happens in the background and only once. And it happens thanks to GCD (Grand Central Dispatch). The story about which is a separate topic.
And of course at the end we return the created singleton
We have Singleton - “Primus”, now we can add properties to it: box - “Box”, engraving - “Etching”, efficiency - “Performance”. And if you declare properties for them, then you can change these variables from the outside. Sheathe the box, clean the engraving, chip our “Primus” to increase efficiency. But it will remain our good old Primus. Access from the outside to the Primus object will be for those classes in the header of which it is declared. But now, if you do so
and then working with "myPrimus" as a singleton, then nothing will work. All calls to singleton variables must occur in the following manner.
So you can create an instance of “someBox” of a completely different class of boxes and assign the value that our singleton’s box has, or vice versa
change the box of our "Primus".
Singleton can be used when you need to call NSLog with a description of the properties of a singleton, but the singleton itself in this class is not in principle. In this case, you just need to declare it in the header of the file and call it once where necessary. Singleton can and is even recommended to be used as a global variable. More precisely, its properties will be global variables.
After mastering the basic principles, it will be possible to begin to solve the problems proposed in the textbooks. And if you can figure out writing your methods in your classes and sending a method to an object, then in the future, you will need to use delegates, protocols, and other MVCs. You will need to use Apple documentation and apply thousands of different methods, carefully created and described by Cupertin programmers.
And here it becomes incomprehensible in principle how and what works. If the method you created creates an array inside yourself, then inserts objects into it, then it starts a loop and does something in it, and then this method launches your other method, everything seems to be clear. But here you open the documentation for the object you are interested in, and there are 20 methods in it that can do very interesting things. In addition, there are ancestor objects whose methods this object can also accept. In addition, at the beginning of the article I wrote about the protocols that an object can correspond to. In total, there may be hundreds of methods. Which one should I apply? Counter question: "What exactly do you need a method for?" A correctly asked question is already half the answer. When you understand what you want to do, you can find the method you are interested in in the class description and apply it to the destination. If you want this instance of the object to do something inherent only to the class from which it came from, then in the documentation for this class you need to carefully look for a method that performs the operations you need. It is necessary to send this method to an object so
That is, in the documentation you learned that this method takes the specified “Potato” argument and does something with it that ultimately leads you to the goal if you apply the method to the “myPrimus” object. No need to implement this method, it is implemented for you for direct application. Exceptions are cases when you need to take a ready-made class from a framework and subclass it so that when sending standard methods to its instance, non-standard actions occur.
There are also protocol methods. As I indicated at the beginning of the article, they cannot have descendants. They are just a collection of methods. In order to apply the protocol method within itself, the object must comply with this protocol. This is indicated in the * .h file
In this case, the object corresponds to two protocols “UITableViewDataSource” and “UITableViewDelegate” at once.
If you go into the description of these protocols, then there you can find methods that the object can implement. Please note: you do not send protocol methods to your object, but you must specify inside them how your object should react when the program accesses these methods. Some methods in the protocols may be required. And if you decide that your object must comply with the protocol, then the required methods must be implemented first. Inside any protocol method, there can be any class method. That is, the protocol method is a container in which any other methods are located, in fact, like any method you implement. It is necessary to implement the necessary functionality within each protocol method based on needs. For example, you need to make sure that your mold does something, under certain conditions. For example, I changed color after becoming active / inactive. We go to the Apple documentation, see which protocols implement the methods you need. Then we look for the protocols that correspond to the parent class of the form. If the protocols that support the functionality you need are not in the standard set of functions, then we add them in <> brackets. In the description of these protocols, we look for methods that are implemented after an event. Let's say
which is automatically executed when the argument "active" takes the value "YES". And it changes color in the part of the screen described in "rect":
Protocol methods specify the operation parameters of the class instance, change the functionality, pass values. For instance:
returns the number of rows in the “section” section for the given tableView.
On this I allow myself to finish. If the article helps the target audience, which I was a few months ago, it means that I think correctly and such articles are needed. Articles are not professionals, but people who understand something at a level that is difficult to step over without a support or springboard. I hope this article is for someone a springboard, or at least a stool. The lack of good teachers who can normally explain something is a fundamental problem of our time. I do not have a teacher education, but the article sets out the concept of programming in a way that I personally would understand.
In the article, the terms that are used to explain various concepts are quoted. The article is written for people who have already learned to distinguish between loops and arrays and understand what a function and method are.
If you have never programmed, then the book of Kernigan and Ritchie (in any edition), you obviously will not be able to. In nature, there is no such willpower that could force you to read the book to the end, while solving all of the above problems.
Очень рекоммендую BecomeAnXcoder. Там всё написано достаточно понятно для людей, которые о программировании знают крайне мало. Информация немного устарела, но для старта сойдёт. Может она Вам уже попадалась на глаза или вы даже скачали её, но отложили в каталог с литературой (ну такой каталог есть у каждого, он занимает десятки гигабайт и всё это мы собираемся прочитать как только появится свободное время, а каталог всё растёт...).
Понятно, что оптимальный вариант — MAC. На худой конец — Хакинтош или виртуальная машина. И даже если процессор не позволяет запустить ничего из вышеизложенного — начать можно прямо в блокноте с последующей компиляцией в коммандной строке.
In many forums, you can find a popular question posed by people who are apparently in a heavily armored tank: "Does the Objective-C programmer need to learn the actual C?" Everywhere they write the same thing. Those who are more bearded and started with structural programming - they say that you just need to become, at least, MiddleDeveloper in C, and only then look for the way in the fierce wilds of OOP, which is Objective-C. Those who did not know C, but had already somehow reached the MiddleDeveloper status, object: they say, learn how the buttons on the iPhone are drawn and how to assign a function to them, and the materiel will then be tightened, as necessary. I categorically declare that to learn how functions are assigned to buttons, there is no programming! I do not urge you to spend half a year understanding pure S. But a couple of weeks is worth a month. This will give some background to your ambitions. As you learn Objective-C, such code structures will necessarily appear that nothing will be clear. Therefore, I propose without any fanaticism, but to familiarize yourself with the book of Krupnik A.B. "Learning C". It is 3 times larger than the previous one in volume, but it is read easily and naturally. The book again, at a fairly affordable level, explains the basic basics of programming. In it, you can learn how to distinguish an array from a loop and what pointers are. at a fairly affordable level, the basic basics of programming are explained. In it, you can learn how to distinguish an array from a loop and what pointers are. at a fairly affordable level, the basic basics of programming are explained. In it, you can learn how to distinguish an array from a loop and what pointers are.
Recently, an article flashed on Habré, where the author cited a bunch of literature, which he advised to read and go to work as a junior for bread. If you just read all the literature cited in that article, then you won’t learn anything, and if you can redefine and remember all the literature again, then you can become a solid developer, not a junior.
Start: class method and instance method
What is a method? This is a set of commands / instructions or one command / instruction that can be applied to an object and invokes the necessary processes in it. If this is not entirely clear, then read on and understand. It’s actually hard to understand what a class method and an instance method are. Everyone says: “with the“ + ”sign is the class method, but with the“ - “sign is the instance method. Is that clear? ”And in response they hear:“ Yes, the class method and the instance method. ” At first, I personally only understood that they were different, but what and when to apply remained unclear. Wikipedia stubbornly repeats the same thing that we heard and read.
In the vast majority of cases, you will work with instance methods.
So, the instance method. Imagine that we have a program in which we will use 2 classes: Primus and Kitchen.
Any class (except abstract) has methods and / or functions. A class can have several methods with a “+” sign in front of a method name (a class method) and several methods with a “-” sign in front of a method name (an instance method). These are the methods that we use with a “-” sign in relation to objects — instances of the class and the class itself. And methods with a “+” sign can only be applied to the class itself, where this method was declared.
What is an instance of a class?
It sounds proud, but incomprehensible. Who remembers from the school course: in geometry, a dot is a parent for a circle, a straight line, and other shapes, because each shape consists of many points.
A class for Objective-C, on some resources interpreted as an object and vice versa. Actually this is a little on like that. We can say that an object is an instance of a class. As a concrete instance of the primus in your kitchen, this is a Primus type specimen: with twisters, a burner, etc. This is not just a primus - it is your primus. A class is an example to follow. A reference sample of something that can also perform some functions thanks to class methods (with a “+” sign). Here you can read a little more detailed.
There are also “protocols” and “abstract classes”. Everything is more interesting here, but the threshold of understanding is higher than that of a class and an object. The difference is that a “protocol” cannot have a subclass or a child object inherit from it, it needs to be matched. As a requirement when applying for a job. The application of protocols in real life I will touch on in this article. An abstract class is an entity that is, as it were, a class, but not a very concrete class, and therefore abstract. For example, again, your home primus is an instance of the Primus class, and not some abstract heater class. You cannot have a heater at home without a clan and tribe (it can also be a fireplace and a battery). In this way, the class "Primus" - may be the heir to the abstract class "Heating device". And already your primus is an instance of a specific definition of a primus. And for his attributes - properties, methods, protocols, when using it, he turns to his class and checks what he can do, what he cannot and what he can have in general.
Imagine that there is a certain class “Gas burner” - the heir to the abstract class “Heating device”. The class “Gas burner” will be the parent for the “Primus” and “Gas stove”. In order to get closer to programming, you need to abstract a little.
Objective-C has a parent class, NSObject. In a programming language, it is like a point in geometry. This is an ideal class: there is nothing superfluous in it, it does nothing concrete, but you can create anything from it. Such is the brick of the universe. If you hold down the Command button in Xcode and click on NSObject, then the contents of the NSObject.h file will open in front of you. It contains about 200 lines of description of various methods. There are both methods with a "+" sign in front of the name, as well as methods with a "-" sign. The essence of the methods with a “+” sign is that they can only be applied directly to NSObject itself. This method is, for example,
+(id) alloc;
and you can apply it only like this:
[NSObject alloc];
Any other object in Objective-C is, by definition, a descendant of NSObject. It implements additional methods that give it personality traits. Let's say this is an NSResponder. This descendant, NSResponder, can also have children. For example, UIView. Further along the inheritance tree, you can take UIScrollView as an example. Then - UITableView. In each case, the descendants are overgrown with additional functionality. In addition to the fact that they know how to do something their own, individual, they can do everything that their ancestor can do. This is inheritance .
If you do not want your child class to exactly copy the behavior of the parent class, then you can redo any method of the parent class to your liking. This is polymorphism .
In your programs, you will take the class you need and add the necessary methods to it - to expand its functionality. You can also take the class you created and make it a descendant, again expanding the functionality and at the same time creating a unique tool to solve the desired problem. The described mechanism is called - creating subclasses of "subclass".
I will not go into the broad descriptions of methods with the "+" sign. It will be clearer to show that the application of the method
+(id) alloc;
to any class or subclass allocates the necessary amount of memory in the RAM of the computer for it.
NSObject *object = [NSObject alloc];
Method application
-(id) init;
initializes the object, allocates a place for it in RAM, and actually after that the object begins to exist and you can work with it.
NSObject *object = [[NSObject alloc] init];
These 2 methods (“alloc” and “init”) have just created an object. And this object is an instance of an object or an instance of the NSObject class. If you apply these methods separately
NSObject *object = [NSObject alloc];
[object init];
then the object will also be created. But there is a non-zero probability that you will create the wrong object to which the memory is allocated. Let's say a method call
NSObject *object = [NSObject alloc];
allocated for him such an address in memory
0x000000000
This happens because during initialization, the runtime environment sets all objects to “nil” to avoid referencing a place in memory filled with “garbage”. Or links to another specific object, which in fact is also “garbage”, because it has completed its life cycle and is no longer “stored” in memory, but simply “located”. In order to understand what “garbage" is, create a simple program in which you declare
int i;
without assigning a value to it, and then output its value to the console
NSLog(@"%i", i);
You will be surprised at what your “i” equals.
An instance of the isa object will also be created, which will be discussed later. This is the only non-zero pointer when creating your object. Suppose this is a thread to a parent object that will spawn its instance and give life to our object.
That is, theoretically, the object already exists after applying the "alloc" method. But he cannot pass the test
if (!self){
...
}
Such a piece of code can be seen in any template object in the init method or its derivatives.
In this case, the object seems to be there, but in principle it is equal to "nil". An uninitialized object is like a machine that you are only going to buy on credit. It seems to be, but it is not yours. And if, without departing from the cash register, use the init method
NSObject *object = [[NSObject alloc] init];
then “Bingo!”, and the object belongs to you, you can do whatever you want with it. But if you apply the method to it
[object init];
then another object may be initialized. It seems like you paid a loan for a car, but it turned out that you got the wrong car and they give you another instead of one car. They at first glance are not distinguishable from each other. But they have different “VIN” numbers, which means that when checking at the “GAI” post you will be informed that the car is not yours. A further explanation of this phenomenon will be given below.
As you already understood, the “+ alloc” method is a class method, and the “-init” method is an instance method.
I will give an example with real things.
The full implementation of the method usually includes 2 files with the extensions * .h and * .m. There are other numbers, depending on what kind of class it is and what goals it pursues.
Your program has a Primus class. The following methods are declared in the file "Primus.h":
Primus. h
//метод класса, можно применить только к классу "Primus"
+(Primus *) hotAsHell;
//метод экземпляра, инициализирует экземпляр класса "Primus"
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
//метод экземпляра, задаёт параметры пламени
-(void) doFireWithGas:(Gas *)gas;
//метод экземпляра, но его применение к текущему классу, вызывает экземпляр совсем другого объекта
-(MasterPoRemontu *) masterPoRemontuWithServiceItems:(NSArray *)i
serviceSkills:(NSArray *)s;
The program also has the “Kitchen” class, where the methods are declared in the “Kitchen.h” file:
Kitchen.h
//метод класса, создаёт экземпляр объекта "Kitchen" и применяется непосредственно к объекту "Kitchen"
+(Kitchen *) kitchenFurniture:(NSArray *)furniture
otherDishes:(NSArray *)dish;
//метод экземпляра, применение его к кухне вызывает экземпляр другого объекта
-(UkrainianBorsch *) borschWithIngredients:(NSarray)ingredients
casserolAndPan:(NSArray)pan;
In the files “Primus.m” and “Kitchen.m” all declared methods must be implemented, that is, described: how and what happens in each method.
If you intend to create an instance of an object of another class in any class, then in the header you will need to import the * .h file of the created object, for example, in the header of the Kitchen.h file we will enter
#import "Primus.h"
That is, it will be possible to create the Primus class inside the Kitchen class.
Since we imported the Primus.h header file into the “Kitchen.h” file, in the “Kitchen” class we became available with the methods of the “Primus” class instance that are declared in the Primus.h file. They can be applied either in relation to a class, or in relation to an instance of the Primus class.
In the Primus.m file, you must implement all the methods that were declared in the Primus.h file, but you can also implement any method that is not declared in the Primus.h header file. This method will be available only from within the class and cannot be called in another class in the program. You can also create variables inside the Primus.m file - they will also not be available for external use. They simply do not know about them. Therefore, a file of type * .h is called the class interface. Only what is explicitly indicated in it is accessible externally to the class in which it is written
#import "Primus.h" . The inaccessibility of methods and variables for other parts of the program is called - encapsulation . First, create an instance of the Primus class inside the Kitchen class
Primus *myPrimus = [Primus hotAsHell];
As you can see, we are sending the c + method not to an instance of the myPrimus class, but directly to the Primus class. An instance of the Primus class called myPrimus has been created. Further, the methods from "Primus.h" with the sign "-" will be applied to the class instance - "myPrimus". And if we want to create a new instance of the Primus class, we can again use the + hotAsHell method. There is also a method in the Primus class
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
Usually, all methods whose name begins with “init” do the same as the “init” method of the NSObject class, but with advanced features. And in this case, the use of the “init” method from this angle
Primus *myPrimus = [[Primus alloc] initWithGas:Propan temperature:750];
will create a new object of the Primus class with unique characteristics.
In the example just described, a method with arguments was applied. In this particular case, it is used to create an instance of the Primus class with clearly defined characteristics. The name of the characteristics shows that this instance of the Primus class will work on gas of the Propan type and at a temperature of 750 degrees.
Note that the names of the methods in Objective-C are very meaningful. At times, you will be amazed at their 10-word names, but this contributes to a better understanding of what is written. Roughly speaking, the code is self-documenting. Which of course does not preclude the need for commenting. But if you make it a rule to create your own methods, the name of which will carry a semantic load and relate to the creation of variables in the same way, then the number of comments in the code can be significantly reduced.
Unlike some other programming languages in Objective-C, methods are not called, but sent to objects. Thus, this language refers to the "message oriented language", in Russian it does not sound so strict, so I do not quote the translation.
Also note that all methods that, after sending to the object create (return) a new object, have in brackets after "+" or "-" either the specific name of the object whose instance they will return, or "id". That is, by what object is indicated in brackets immediately after the “+” or “-” sign, we can judge what we will get after sending this method. What is an id? This is a type of object that can take the form (type) of any other object. Let's say this is a “weak” type of object. Not in the sense that something is “weak” to him. In general terms, this is a weakly typed object. The presence of such objects in the PL makes it weakly typed.
You can also find the “void” type in these same brackets.
What is a void?
Everyone says: "this is when the method returns nothing." But why is it needed? Applying a method of this type nevertheless produces manipulations with objects, structures, and numbers. Also, objects and scalar quantities that fall into it as arguments can change or disappear altogether. It all depends on what is implemented within each particular method. The fact is that "void" doesn’t return anything ... new. As was said, the arguments that fall into it can change themselves, or affect other objects that are manipulated in the “void” method. He can even create new objects within himself, but nothing comes back outright. Usually a method that returns something has the keyword “return” at the very end. And the method like "void", "return" at the end does not have. He can have it inside his own cycles or conditions in order to be able to interrupt them in the right place. But not at the end. The method does not create anything fundamentally new, it simply, working with a pointer to some object (number, array, cell, etc.), changes the data at this address or uses it to calculate other data that it assigns to other objects.
What is self?
This word is often used anywhere. The fact of its application in various places introduces confusion into the harmonious pantries of your knowledge.
Let's return to the classes “Primus” and “Kitchen”. Take a concrete method of the Primus class and consider how its implementation in the Primus.m file will look. For example, this is a method.
-(void) doFireWithGas:(Gas *)gas;
can be implemented this way:
-(void) doFireWithGas:(Gas *)gas {
id myPrimus;
if (!self){
myPrimus = [self initWithGas:gas temperature:750];
}
...
...
...
}
In curly brackets, the method is implemented or "implied". In this case, the implementation of the method reads as follows: if not myPrimus, then run the command
[self initWithGas:propan temperature:750];
which in turn reads: initialize itself to gas: gas temperature: 750. That is, the method is sent to itself, and not to some other object. In this case, “yourself” is for the Primus class, since the method is inside the Primus class. Its declaration is in the file Primus.h and the implementation in the file Primus.m.
You may also notice that the method returns nothing “void”. That is, a pun comes out: if there is no primus, then we create it, but it does not go outside the method. Why create it then? Suppose its functionality is implemented as follows:
id myPrimus;
if (!self){
myPrimus = [self initWithGas:gas temperature:750];
}
MasterPoRemonty *master = [myPrimus masterPoRemontuWithServiceItems:someItemsArray
serviceSkills:someSkillsAray];
}
And here it’s already clear that somewhere inside the method a primus repair wizard appears.
Why was the exclamation mark "!" in brackets after "if"? In programming, this is a sign of negation. That is, "! =" Sounds like "not equal." This method of condition is convenient for its brevity. In this example, a different condition could be used that would carry the same function.
if (myPrimus == nil){}
This condition is 2 times longer and you need to read the entire line in order to understand it. This condition is small, but if you need to write something like this
if ((myPrimus == nil || yourPrimus == nil) && (mamaPrimus == nil || papaPrimus == nil)){}
then this structure
if ((!myPrimus || !yourPrimus) && (!mamaPrimus || !papaPrimus)){}
will be in a more advantageous position, both in brevity and in physical size. The previous construction - it may simply not fit into one line.
Let's consider how the implementation of this method would look like in the framework of the "Kitchen" class. In the file “Kitchen.m” the same method will be implemented like this:
-(void) doFireWithGas:(Gas *)gas {
Primus *myPrimus;
if (!myPrimus){
Primus *myPrimus = [Primus initWithGas:gas temperature:750];
}
MasterPoRemonty *master = [myPrimus masterPoRemontuWithServiceItems:someItemsArray
serviceSkills:someSkillsAray];
...
...
}
It can be seen that here you need to specify which object will be created, how its instance will be called, and then send the method directly to the parent class.
That is, the tricky word “self” is used inside the class, as if you had already created an instance of it.
Primus *myPrimus;
and refer to the class instance. The only trick is that you did not create anything, and do not access the instance, but directly to the class. For clarity, I created an object
id myPrimus;
which can be accessed with the method. But it was possible not to create it, but to implement a more complex structure that would perform the same functions
if (!self){
MasterPoRemonty *master = [[self initWithGas:gas temperature:750] masterPoRemontuWithServiceItems:someItemsArray
serviceSkills:someSkillsAray];
}
In order to understand whether it is possible to write “self” or not, just imagine what method you want to send to which object and start typing the word “self”, but not completely, for example, “sel”. Xcode will offer several options, among which will be actually "self". To his left will be a description of what class it is. If this is exactly the class to which you wanted to send the desired method (the class within which you are currently performing the action), then use "self". If Xcode indicates that “self” is not the object you need, then use the name of the class or instance of the one you need.
It is also evident that the arguments were involved here. Each argument has a type, which is also described in parentheses, but after the colon. After the brackets with the type of the argument comes the name of the argument. If there is a "*" sign in parentheses after the argument type, then this is an object. If there is no “*” sign, then this is a scalar type, for example, “NSInteger”, which is essentially a simple “int”. Also, "NSInteger" may be marked with "*" for the argument passed. In this case, we will not redo the variable itself, but a pointer to it. The topic of Indexes is beyond the scope of this article. The name of the argument in this method
-(void) doFireWithGas:(Gas *)gas;
will sound like "gas". Did you notice that the normal gas name was not used in the implementation? For example, "Propan". The fact is that in the argument "gas" just the name of the gas is passed. Where from? We examined how the method is implemented.
-(void) doFireWithGas:(Gas *)gas;
The method was involved in it.
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
There were 2 arguments “gas” and “t” in this method. In sending the method to the object, we indicated the temperature, and the type of gas was simply duplicated from the method
-(void) doFireWithGas:(Gas *)gas;
That is, this method is also sent somewhere. Let's take a closer look at the method
(Primus *) hotAsHell;
Its implementation may look like this
+(Primus *) hotAsHell{
self = [super init];
if (self){
[self doFireWithGas:propanButan];
}
return self;
}
Note the value of the argument "propanButan" sent. It is it that is transmitted throughout the class, from method to method.
It is also visible here that the method is sent to the object under certain conditions.
You need to know that not one of your methods alone can work. He needs a “kick” outside. Pressing a button, the occurrence of an event, or it should be launched by the protocol method, which the runtime runs at the right moment. This “hotAsHell” method is sent only after an explicit call, this is not a system method, it must also be registered somewhere or assigned to something.
Now consider the method
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
It consists of a return type, argument types, arguments, and a selector. The selector is what remains of the method when the type of the returned object, the type of arguments, and arguments are taken away from it.
initWithGas:temperature:
with colons. If there are no arguments, then there are no colons either, as is the case with "hotAsHell". Often you will have to deal with the need to use a selector. Now you know what to use.
A little higher was used the concept of "super".
What is a super?
Consider the hotAsHell method implemented above. It has such a design
self = [super init];
So it is customary to initialize the child class - through the initialization of the parent.
Suppose that the Primus class is a direct descendant of the NSObject class. It is the class that stands in hierarchy immediately above the class with which we work has the right to be called "super". Command
[super init];
calls the init method of an NSObject instance. That is, an instance of the progenitor of all objects is automatically created - NSObject, with default (purely NSObject's) parameters. Sending the "init" method to NSObject'y returns "self" from NSObject. That is, directly an instance of NSObject itself. And this instance is assigned to our Primus object. Indeed, “self” in this case is precisely “Primus”. And at this stage we get an instance of the Primus object, which is no different from NSObject. Our package method gives individual features to it.
[self doFireWithGas:propanButan];
as well as other methods implemented within the framework of this class.
Design
if (self) {...}
it’s just reinsurance in case something goes wrong and the default “Primus” is not created. those. The init method of the parent class will fail. In this case, they usually do
...
else {...}
where they are trying to correct the situation, but, unfortunately, this is not the case and the "else" will not help us.
And here I want to remind you of the uninitialized object from the beginning of the article. It is during the initialization of an instance of an object by its superclass that a place in memory is assigned to it and a pointer is transferred to that place in memory. And when we check
if (self = [super init])
it is at this very moment that "self" can receive an unexpected address in memory. That is, the resulting pointer will no longer be "self". The superclass will not return us something out of the ordinary. The instance of the object will be identical to the one we are going to work with. But the instance of this particular object will not be the instance that we need. This is such a rare mistake that you may not observe it for a very long time in your applications. Just at one point, your application may start to generate exotic errors. As a result, you have to spend time finding their cause. The allocated memory for your object was not used. A random pointer could be initialized, having no connection in the object to which the memory was allocated using the "alloc" method. Then "self" does not pass the test
if (self) {...}
In the initialization method under consideration. And all because at some point in the program, when this object was called, the initialization was not performed properly.
The last thing we need to do to complete the initialization
return self;
Since this method is not "void", it expects us that in the end we will tell it what needs to be returned. In this case, an object of type “Primus” will be returned, because “self” in the framework of this class is precisely “Primus”. Also, the type of the returned object tells us what exactly is expected to be "Primus".
In these methods
+(Kitchen *) kitchenFurniture:(NSArray *)furniture
otherDishes:(NSArray *)dish;
-(UkrainianBorsch *) borschWithIngredients:(NSarray)ingredients
casserolAndPan:(NSArray)pan;
-(MasterPoRemontu *) serviceItems:(NSArray *)i
serviceSkills:(NSArray *)s;
the type of returned objects is “Kitchen”, “UkrainianBorsch” and “MasterPoRemontu”.
Thus, in the “hotAsHell” method, we say that at the end of the execution of this method we want to get an object of type “Primus” with the given parameters.
What exactly are the given parameters?
We describe such an implementation of the method
- (id) initWithGas:(Gas *)gas temperature:(NSInteger)t {
...
[self setGas:gas];
[self setTemperature:t];
...
}
This means that when calling this method
Primus *myPrimus = [[Primus alloc] initWithGas:metan temperature:500];
literally such parameters are set
[self setGas:metan];
[self setTemperature:500];
Parameters are set by the variables that are declared in the * .h file.
Note the construction of the word "setGas" or "setTemperature". If there is a variable, for example “variable”, then you can set the desired value for it through the “set” prefix:
setVariable
the first letter of the variable becomes capitalized. Thus, we found out that the variables "gas" and "temperature" were declared in the file Primus.h. But the very existence of variables does not allow us to assign them using the “set” prefix.
To obtain such an opportunity, we need to declare properties for these variables. Let's admit such:
@property (nonatomic, strong) Gas *gas;
@property (nonatomic) NSInteger temperature;
and in the implementation file you will need to register
@synthesize gas, temperature;
and only after that it will be possible to assign values to them through "setGas" and "setTemperature". These prefixes are called setters.
At any convenient moment, you can ask the Primus object to show you these values, referring to its instance
[myPrimus gas];
[myPrimus temperature];
or
myPrimus.gas;
myPrimus.temperature;
for example for a team
NSLog(@"Примус газовый работает на газе марки %@, рабочая температура %i", [myPrimus gas], myPrimus.temperature);
These methods are called getters. In the case of accessing the values of the variable name, nothing needs to be added.
Note that the property of an object can be accessed as through a construct
[myPrimus gas];
and through
myPrimus.gas;
They perform similar actions.
There is one trick when accessing object variables. This can be done not only through properties, but also through access to them on a key-value "key-value coding" basis. That is, you can refer to the variable
myPrimus.gas;
So
[myPrimus valueForKey:@"gas"];
We applied for the value that is in the “Primus” object in the “gas” keyword.
And such a design
[myPrimus setGas:metan];
in the context of “key-value coding” it will look like this
[myPrimus setValue:@metan forKey:@"gas"];
Using key-value coding instead of properties is a tedious task. But it is necessary to have an idea about it, so in the case when you cannot do without it, take advantage.
Also, it will not be amiss to point out the fact that recently, variables in Objective-C programming are no longer used. Instead, they use properties. Properties can be either externally accessible or encapsulated. Naturally, for use inside methods, variables can and should be used. But declaring variables in the ivar block, which is in the class interface, is already an anachronism.
runtime
Even if this has not happened yet, it is likely that you will soon find the word “runtime” in the documentation and, within the same article, the word “isa”.
The concept of "runtime" can be described as "the environment in which your code is translated to a lower level code." "Runtime" is written directly using C and Assembler. This is not yet a translation into machine code, but a conversion of your code to the C and Assembler languages. Your method
[myPrimus initWithGas:gelium temperature:nil];
in "runtime" in C, it looks something like
objc_msgSend(myPrimus,@selector(initWithGas:temperature:),gelium,nil);
This should be enough for a novice Cocoa programmer to understand: it’s better not to go further into the jungle. As soon as you cross the threshold of entry into the club, you yourself can find the information you need.
In the meantime, we are interested in what is isa is.
This is a variable that is declared directly in NSObject. Its only variable. When we called the method
[super init];
then not only an instance of the NSobject class was created, but also an instance of this isa variable, which refers to NSObject. That is, it specifically tells runtime that it belongs to an NSObject. This means that you need to work with an instance of your newly created object just like with NSObject. A pointer to the parent object that we need to inherit is written to isa. Let's say your object is a descendant of NSArray or UITableView or CFDictionaryRef or any other object. In this case, “isa” points to an NSArray or UITableView or CFDictionaryRef or any other object, respectively. So creating an instance of any object also creates a class variable - "isa", which refers directly to the parent class, as a result of which "runtime" knows what will be done with each instance.
This information at the training stage is not needed for anything specific, but, in principle, for a more comprehensive understanding of the Objective-C programming philosophy.
In the process of reading books and various documentation, the concept of “singleton” has come to your eyes more than once. As one popular Internet meme says: “You can’t immediately grasp and understand what a singleton is.
What is singleton?
Imagine that you need to create an object that, every time it is called at any point in the application, returns the same instance. In fact, in the process of creating applications, you really need to create one. So why can’t you create an object several times and assign the same data to it through the “initWithSomething:” method or using setters? It's all about working with memory and speed, and indeed, with less time spent writing code. There is always little memory, and even when they put 2GB of RAM on the iPhone6, it will again be small. Creating one instance of the object, and then accessing it, saves device resources and speeds up the application. But everyone wants his application to be as fast as “Bugatti Veyron” and usable as the word “horseradish”.
Let's say that our “Primus” may well be a singleton. Then his method
+(Primus *) hotAsHell;
during implementation it will look like this
+(Primus*) hotAsHell{
static Primus *myPrimus = nil;
static dispatch_once_t predicate;
dispatch_once (&predicate, ^{myPrimus = [[super alloc] init];});
return myPrimus;
}
Consider what this means later. Let’s find out why “Primus” is a singleton.
Suppose this is not quite the usual "Primus", but rare. It has an engraving by an unknown master, it has an extremely low gas consumption, and also it has a special box in which it fits perfectly. Now ask yourself the question: “Do you need another primus?”. Of course not! But when sending the method
Primus *myPrimus = [[Primus alloc] initWitGas:metan temperature:500];
one “Primus” will be created, and when sending the method
Primus *myPrimus = [[Primus alloc] initWitGas:propan temperature:600];
a completely different „Primus“ will be created. Without engraving and boxes.
Now back to what is written in the implementation of the “hotAsHell” method.
First you need to create an instance of the object with the “static” property in order to block access to the object from the outside. Then assign him “nil” so that he does not take a random address in memory. Design
static dispatch_once_t predicate;
creates a predicate (condition) - "predicate" which also will not be visible from the outside. The condition is that automatic or dynamic calling of the block behind the predicate is prohibited. And here is the line
dispatch_once (&predicate, ^{myPrimus = [[super alloc] init];});
already takes all the necessary steps to create a unique instance of the object. Specifically, “dispatch_once” means that the expression in brackets after it is guaranteed to be launched only once during the entire life cycle of the application. Also "dispatch_once" guarantees thread safety. That is, when the application is launched in multithreaded mode, this function will not be called simultaneously in several threads, and it certainly won’t create another “unique“ Primus “” for us. There is also a block
^{myPrimus = [[super alloc] init];}
This is kind of a small function or method. There are also large blocks. In this block, the parent object is initialized, after which its value is assigned to the descendant of myPrimus.
Together the whole line
dispatch_once (&predicate, ^{myPrimus = [[super alloc] init];});
means: once during the entire life cycle of the application, an instance of the Primus object called “myPrimus” with the properties of the parent object will be initialized and it will not be possible to access this block in another way. But no one will know that “myPrimus” was created, because this instance of the object is not visible from the outside. Everything happens in the background and only once. And it happens thanks to GCD (Grand Central Dispatch). The story about which is a separate topic.
And of course at the end we return the created singleton
return myPrimus;
We have Singleton - “Primus”, now we can add properties to it: box - “Box”, engraving - “Etching”, efficiency - “Performance”. And if you declare properties for them, then you can change these variables from the outside. Sheathe the box, clean the engraving, chip our “Primus” to increase efficiency. But it will remain our good old Primus. Access from the outside to the Primus object will be for those classes in the header of which it is declared. But now, if you do so
Primus *myPrimus = [Primus hotAsHell];
and then working with "myPrimus" as a singleton, then nothing will work. All calls to singleton variables must occur in the following manner.
Box *someBox = [Primus hotAsHell].Box;
So you can create an instance of “someBox” of a completely different class of boxes and assign the value that our singleton’s box has, or vice versa
[Primus hotAsHell].Box = someBox;
change the box of our "Primus".
Singleton can be used when you need to call NSLog with a description of the properties of a singleton, but the singleton itself in this class is not in principle. In this case, you just need to declare it in the header of the file and call it once where necessary. Singleton can and is even recommended to be used as a global variable. More precisely, its properties will be global variables.
After mastering the basic principles, it will be possible to begin to solve the problems proposed in the textbooks. And if you can figure out writing your methods in your classes and sending a method to an object, then in the future, you will need to use delegates, protocols, and other MVCs. You will need to use Apple documentation and apply thousands of different methods, carefully created and described by Cupertin programmers.
And here it becomes incomprehensible in principle how and what works. If the method you created creates an array inside yourself, then inserts objects into it, then it starts a loop and does something in it, and then this method launches your other method, everything seems to be clear. But here you open the documentation for the object you are interested in, and there are 20 methods in it that can do very interesting things. In addition, there are ancestor objects whose methods this object can also accept. In addition, at the beginning of the article I wrote about the protocols that an object can correspond to. In total, there may be hundreds of methods. Which one should I apply? Counter question: "What exactly do you need a method for?" A correctly asked question is already half the answer. When you understand what you want to do, you can find the method you are interested in in the class description and apply it to the destination. If you want this instance of the object to do something inherent only to the class from which it came from, then in the documentation for this class you need to carefully look for a method that performs the operations you need. It is necessary to send this method to an object so
[myPrimus doSomethingWith:Potato];
That is, in the documentation you learned that this method takes the specified “Potato” argument and does something with it that ultimately leads you to the goal if you apply the method to the “myPrimus” object. No need to implement this method, it is implemented for you for direct application. Exceptions are cases when you need to take a ready-made class from a framework and subclass it so that when sending standard methods to its instance, non-standard actions occur.
Protocols
There are also protocol methods. As I indicated at the beginning of the article, they cannot have descendants. They are just a collection of methods. In order to apply the protocol method within itself, the object must comply with this protocol. This is indicated in the * .h file
@interface Primus : UIViewController In this case, the object corresponds to two protocols “UITableViewDataSource” and “UITableViewDelegate” at once.
И чем отличаются методы классов и протоколов?
If you go into the description of these protocols, then there you can find methods that the object can implement. Please note: you do not send protocol methods to your object, but you must specify inside them how your object should react when the program accesses these methods. Some methods in the protocols may be required. And if you decide that your object must comply with the protocol, then the required methods must be implemented first. Inside any protocol method, there can be any class method. That is, the protocol method is a container in which any other methods are located, in fact, like any method you implement. It is necessary to implement the necessary functionality within each protocol method based on needs. For example, you need to make sure that your mold does something, under certain conditions. For example, I changed color after becoming active / inactive. We go to the Apple documentation, see which protocols implement the methods you need. Then we look for the protocols that correspond to the parent class of the form. If the protocols that support the functionality you need are not in the standard set of functions, then we add them in <> brackets. In the description of these protocols, we look for methods that are implemented after an event. Let's say
-(UIColor *) areaDidChangeColor:(CGRect)rect isActive:(BOOL)active;
which is automatically executed when the argument "active" takes the value "YES". And it changes color in the part of the screen described in "rect":
{
if (isActive && myForm == rect){
myColor = [UIColor redColor];
}
return myColor;
}
Protocol methods specify the operation parameters of the class instance, change the functionality, pass values. For instance:
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
returns the number of rows in the “section” section for the given tableView.
On this I allow myself to finish. If the article helps the target audience, which I was a few months ago, it means that I think correctly and such articles are needed. Articles are not professionals, but people who understand something at a level that is difficult to step over without a support or springboard. I hope this article is for someone a springboard, or at least a stool. The lack of good teachers who can normally explain something is a fundamental problem of our time. I do not have a teacher education, but the article sets out the concept of programming in a way that I personally would understand.