Dynamic Tips and Tricks of Objective-C

Over the past few years, the emergence of a large number of Objective-C developers. Some are transferred from the dynamic languages ​​over, such as Ruby or Python, some are from the turn of strongly typed languages, such as Java or C #, of course, has a direct entry to Objective-C as a language. That is a large part of developers are not using Objective-C too long. When you touch a new language, it will be more concerned about the basics, such as syntax and characteristics. But usually there are characteristics of some of the more advanced and more powerful features little-known there waiting for you to explore.

This article is a taste to run under the Objective-C (runtime), and explains what makes Objective-C so dynamic, then feel these dynamic technical details. Lets hope this time is how you run a better understanding of Objective-C and Cocoa.

The Runtime

Objective-C is a simple language, 95% C. Just add some keywords and grammar in the language level. Really makes Objective-C so powerful is that it's running. It's small but very powerful. It is the core message distribution.

Messages

If you are from dynamic languages ​​such as Ruby or Python's turn, may know what is the message, you can skip to the next section. Those transferred from other languages ​​over, continue to look at.

Implementation of a method, some language, the compiler will perform some additional optimizations and error checking, because the call relationship is very direct and very clear. But for message distribution, it is not so obvious. Before a message without having to know whether an object is able to process the message. You message to it, it may be processed, may also be transferred to other Object to deal with. A message not necessarily correspond to a method, an object may implement a method to process multiple messages.

In Objective-C, the message is by objc_msgSend()this method and the method similar runtime implemented. This method requires a target, selector, and some parameters. In theory, the compiler message distribution becomes just objc_msgSendperformed. Example, the following two lines of code are equivalent.

1
2
[array insertObject:foo atIndex:5];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

Objects, Classes, MetaClasses

Most object-oriented languages have the concept of classes and objects. Objects produced by Classes. But in Objective-C, classes are themselves objects (Translator's Note: This is with python like), can also process the message, which is why there will be class methods and instance methods. Specifically, Objective-C in the Object is a structure (struct), is the first member isa, point to their own class. This is defined in objc / objc.h in.

1
2
3
typedef struct objc_object {
     Class isa;
} *id;

object of class to save the list of methods, as well as a pointer to the parent class. However, classes are also objects, there will be a isavariable, then where does it point to? Here leads a third type:  metaclasses. A metaclass is directed to class, class is pointed object. It maintains a list of all the implementation methods, and metaclass of the parent class. If you want a clearer understanding of objects, classes and metaclasses is how to work together, you can read this article .

Methods, Selectors and IMPs

We will know the message to target runtime. We also know that a class object holds the list of methods. These messages are then mapped into methods, these methods and how is it performed?

The answer to the first question is simple. class method list is actually a dictionary, key for the selectors, IMPs to value. It is a method implemented in the memory point. It is important that the relationship between the selector and IMP are determined at runtime rather than compile time. In this way we can play a few tricks.

IMP usually pointer to the method, the first parameter is self, type id, the second parameter is _cmd, the SEL type, the remainder being parameters of the method. It is also selfand _cmdplace to be defined. The following demonstrates Method and IMP

1
2
3
- (id)doSomethingWithInt:(int)aInt{}
 
id doSomethingWithInt(id self, SEL _cmd, int aInt){}

Other methods of runtime

Now we know the objects, classes, selectors, IMPs and message distribution, then what in the end do run it? There are two main effects:

  1. Create, modify, introspection classes and objects
  2. Message Distribution

Previously mentioned message distribution, but this is only a small part of the function. All run-time method has a specific prefix. Here are some interesting ways:

class

The method is used to modify the beginning of the class introspection and classes. The method as class_addIvar, class_addMethod, class_addProperty class_addProtocol and allow reconstruction of classes.

class_copyIvarList, class_copyMethodList, class_copyProtocolList and class_copyPropertyList can get all the contents of a class. And class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation and class_getProperty return a single content.

There are also some general method introspection, as class_conformsToProtocol, class_respondsToSelector, class_getSuperclass. Finally, you can use class_createInstance to create an object.

ivar

These methods allow you to get the name, address memory and Objective-C type encoding.

method

These methods are mainly used from the province, such as method_getNamemethod_getImplementationmethod_getReturnTypeand so on. There are also some modifications of methods, including method_setImplementationand method_exchangeImplementationsthat we'll come back.

objc

Once you get the object, you can modify it and do some introspection. You can get / set ivar, use object_copyand object_disposeto copy and free object memory. The most NB is not only to get a class, but you can use object_setClassto change the class of an object. You will be able to see the usage scenario.

property

Property holds a large part of the information. In addition to get the name, you can also use property_getAttributesto find more information about the property, such as the return value, whether atomic, getter / setter name, whether it is dynamic, ivar behind the name used, whether it is weak.

protocol

Protocols bit like classes, however, the method runtime Lite is the same. You can get method, property, protocol list, check whether the realization of the other protocol.

cell

Finally, we have some ways to deal with selectors, such as access to the name, registered a selector and so on.

We now have a rough idea of ​​Objective-C run time, to see what interesting things they can do.

Classes And Selectors From Strings

The basis of comparison is to generate a dynamic characteristic Classes and Selectors by String. Cocoa provides NSClassFromStringand NSSelectorFromStringmethod, is simple to use:

1
Class stringclass = NSClassFromString(@"NSString");

So we get a string class. Next:

1
NSString *myString = [stringclass stringWithString:@"Hello World"];

Why do you do that? Class not directly use more convenient? In general cases, but in some scenarios this approach would be useful. First, you can know whether there is a class, NSClassFromString returns nil, the class does not exist if the runtime. Example, can check NSClassFromString(@"NSRegularExpression")is nil determines whether iOS4.0 +.

Another scenario is the use of a different class or return depending on the input method. For example, you parse some data, each data item has a string to be parsed and its own type (String, Number, Array). In a method where you can get these, you can also use multiple methods. One way is to get type, and then use the method to call if matching. The other is to generate a selector according to type, and then call it. The following are two ways:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)parseObject:(id)object {
     for (id data in object) {
         if ([[data type] isEqualToString:@"String"]) {
             [self parseString:[data value]];
         } else if ([[data type] isEqualToString:@"Number"]) {
             [self parseNumber:[data value]];
         } else if ([[data type] isEqualToString:@"Array"]) {
             [self parseArray:[data value]];
         }
     }
}
- (void)parseObjectDynamic:(id)object {
     for (id data in object) {
         [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]];
     }
}
- (void)parseString:(NSString *)aString {}
- (void)parseNumber:(NSString *)aNumber {}
- (void)parseArray:(NSString *)aArray {}

Can a see, you can put seven lines of code with if turned into 1 line. In future, if a new type of method can be achieved by simply adding, rather than go to add a new else if.

Method Swizzling

Before we said before, the method consists of two parts. Selector id corresponding to a process; Imp is implementation of the method. The convenience of such a separate corresponding relation between the IMP and the selector may be changed. For example there may be a plurality of selectors IMP to it.

Method Swizzling implement the two methods can be exchanged. Perhaps you might ask, "Under what circumstances would it need this?." Let's Objective-C, the way to look at the two extensions of the class. The first is subclassing. You can override a method, call the parent implementation, which also means that you have to use an instance of this subclass, but if inherited a Cocoa class, while Cocoa returned to the original class (such as NSArray). In this case, you'll want to add a method to the NSArray, that is, the use of Category. In 99% of cases, this is OK, but if you override a method, there is no chance to call the original true.

Method Swizzling can handle this problem. You can not rewrite an inherited method, but also can call the original implementation. The usual practice is to add a method in the category (of course, can also be a whole new class). It can method_exchangeImplementationsbe exchanged runtime implementation of this method. View a demo, this demo demonstrates how to override the addObject:object methods to each new record added.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import  <objc/runtime.h>
 
@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end
 
@implementation NSMutableArray (LoggingAddObject)
 
+ (void)load {
     Method addobject = class_getInstanceMethod(self, @selector(addObject:));
     Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));
     method_exchangeImplementations(addObject, logAddObject);
}
 
- (void)logAddObject:(id)aobject {
     [self logAddObject:aObject];
     NSLog(@"Added object %@ to array %@", aObject, self);
}
 
@end

我们把方法交换放到了load中,这个方法只会被调用一次,而且是运行时载入。如果指向临时用一下,可以放到别的地方。注意到一个很明显的递归调用logAddObject:。这也是Method Swizzling容易把我们搞混的地方,因为我们已经交换了方法的实现,所以其实调用的是addObject:

动态继承、交换

我们可以在运行时创建新的class,这个特性用得不多,但其实它还是很强大的。你能通过它创建新的子类,并添加新的方法。

但这样的一个子类有什么用呢?别忘了Objective-C的一个关键点:object内部有一个叫做isa的变量指向它的class。这个变量可以被改变,而不需要重新创建。然后就可以添加新的ivar和方法了。可以通过以下命令来修改一个object的class

1
object_setClass(myObject, [MySubclass class]);

这可以用在Key Value Observing。当你开始observing an object时,Cocoa会创建这个object的class的subclass,然后将这个object的isa指向新创建的subclass。点击这里查看更详细的解释。

动态方法处理

目前为止,我们讨论了方法交换,以及已有方法的处理。那么当你发送了一个object无法处理的消息时会发生什么呢?很明显,”it breaks”。大多数情况下确实如此,但Cocoa和runtime也提供了一些应对方法。

首先是动态方法处理。通常来说,处理一个方法,运行时寻找匹配的selector然后执行之。有时,你只想在运行时才创建某个方法,比如有些信息只有在运行时才能得到。要实现这个效果,你需要重写+resolveInstanceMethod:和/或 +resolveClassMethod:。如果确实增加了一个方法,记得返回YES。

1
2
3
4
5
6
7
+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
     if (aSelector == @selector(myDynamicMethod)) {
         class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:");
         return YES;
     }
     return [super resolveInstanceMethod:aSelector];
}

那Cocoa在什么场景下会使用这些方法呢?Core Data用得很多。NSManagedObjects有许多在运行时添加的属性用来处理get/set属性和关系。那如果Model在运行时被改变了呢?

消息转发

如果 resolve method 返回NO,运行时就进入下一步骤:消息转发。有两种常见用例。1) 将消息转发到另一个可以处理该消息的object。2) 将多个消息转发到同一个方法。

消息转发分两步。首先,运行时调用-forwardingTargetForSelector:,如果只是想把消息发送到另一个object,那么就使用这个方法,因为更高效。如果想要修改消息,那么就要使用-forwardInvocation:,运行时将消息打包成NSInvocation,然后返回给你处理。处理完之后,调用invokeWithTarget:

Cocoa有几处地方用到了消息转发,主要的两个地方是代理(Proxies)和响应链(Responder Chain)。NSProxy是一个轻量级的class,它的作用就是转发消息到另一个object。如果想要惰性加载object的某个属性会很有用。NSUndoManager也有用到,不过是截取消息,之后再执行,而不是转发到其他的地方。

响应链是关于Cocoa如何处理与发送事件与行为到对应的对象。比如说,使用Cmd+C执行了copy命令,会发送-copy:到响应链。首先是First Responder,通常是当前的UI。如果没有处理该消息,则转发到下一个-nextResponder。这么一直下去直到找到能够处理该消息的object,或者没有找到,报错。

使用Block作为Method IMP

iOS 4.3 brings a lot of new runtime method. In addition to strengthening the properties and protocols, but also to bring a new method starts with a group of imp. It is usually a pointer to a method of implementation, the first two parameters of object (self) and the selector (_cmd). iOS 4.0 and Mac OS X 10.6 brings the block, imp_implementationWithBlock() let us use the block as the IMP, the following code snippet shows how to add a new method using block.

1
2
3
4
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
     NSLog(@"Hello %@", string);
});
class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");

If you want to know how this is done, you can view this article

We can see, Objective-C surface looks very simple, but still very flexible and can bring a lot of possibilities. The advantages of dynamic languages ​​that do a lot of very smart thing, without extension of the language itself. Such as Key Value Observing, it offers elegant API can seamlessly with existing code, without the need to add the language level features.

I hope this article makes you a better understanding of Objective-C, when developing app can open ideas, consider more possibilities.

Reproduced in: https: //www.cnblogs.com/zhengJason/p/3690351.html

Guess you like

Origin blog.csdn.net/weixin_34080903/article/details/93462276