📜 ⬆️ ⬇️

A little about Swift runtime or where NSObject disappeared

Hello friends! I am one of those who are bored just to pull the strings sticking out of the black box, I want to see everything with my own eyes, how it works under the hood. We will talk to you about runtime, yes yes runtime. For our experiences, let's consider the good old grandfather of Objective C and the revolutionary, but still under development, Swift. We need to dive with you almost at the very bottom of the abstractions that Apple programmers have thoughtfully invented. Let's see a little about why it was even necessary to develop a new language. I heard a lot of negative reviews at the very beginning, especially from the already well-experienced Objective C developers. If you look closely at the new Swift language, it is in my opinion more significant and more serious. First, he wrote in C ++, in contrast to C, which is the basis of Objective C. I here express only my purely personal prejudices, which you can agree with, and you can argue.

In my opinion C ++ at the moment, the most serious development language that allows you to do anything, but more elegant than in C. I think therefore it was he who was chosen as the basis for writing Swift, LLDB, and so on. I will not review the functionality of the Swift language now, just to highlight a few points, everything else can be read in the specialized documentation. In my opinion, it is much more functional and easier for a novice programmer, in fact, this was one of the goals of its development, to lower the threshold of entry for new developers. Remember how your hair got up after the first acquaintance with Objective C brackets, especially if you had previously written in concise C # or Java. Of course, it was not done without the cunning that programmers invented in my opinion just to stand out. But this is all the lyrics, let's get down to business. First, let's analyze the base class of grandfather Objectice C because we compare it with Swift.

For those who know all this, you can still go out for a smoke. As we know in Objective-C, any class, either directly or indirectly, must inherit from NSObject or NSProxy. The NSObject class implements the informal protocol NSObject. We will return to it when we consider the SwiftObject. Looking ahead, I’ll say that it is this protocol that will greatly help to make two languages ​​friends, here it is the power of polymorphism! I propose to do so, we will look at all the methods of the NSObject class in pieces. And after that, I dare to conclude what is wrong with this great NSObject. I will not torment you for a long time!

@interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; } + (void)load; + (void)initialize; - (instancetype)init #if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER NS_DESIGNATED_INITIALIZER #endif ; + (instancetype)new OBJC_SWIFT_UNAVAILABLE("use object initializers instead"); + (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead"); + (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead"); - (void)dealloc OBJC_SWIFT_UNAVAILABLE("use 'deinit' to define a de-initializer"); - (void)finalize; .... 

The first two methods do not often come across an ordinary developer Objectice C. I will not dwell on them, I will just say a few words. The load method is called when the class or category is loaded into the runtime, does not obey the classical inheritance rules, and blocks the application at the time of execution. The initialize method is called in deferred mode, before the first use of the class, does not block the operation of the application, obeys the classical inheritance rules.

All subsequent methods are responsible for creating and initializing the object. Also discuss them a little. The allocWithZone method is responsible for allocating memory for our object. Inside, he calls our favorite malloc. He is still from bearded times when the memory was divided into zones. Now all objects are created in one zone, because the alloc method has appeared, which inside itself calls allocWithZone and passes it the default zone - NSDefaultMallocZone .
')
The dealloc and finalize methods are called when the object is deleted. It is in these methods that all the related resources are cleared and finally free and the memory goes to the free memory pool. I note that finalize is not normally used and dealloc is called on the thread where the last release occurred .

Go to the next pack of methods.

 - (id)copy; - (id)mutableCopy; + (id)copyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE; + (id)mutableCopyWithZone:(struct _NSZone *)zone OBJC_ARC_UNAVAILABLE; 

Well, probably even to someone who never wrote to obzhaktiv si it is not difficult to guess what they are for copying. Everything is clear with zones, these are ancient methods that are no longer relevant. In fact, in order to copy something from us, we still need to implement the NSCopying protocol, and if you just call these methods, everything will fall. But we will discuss this later. In the meantime, move on to the next pack.

 + (BOOL)instancesRespondToSelector:(SEL)aSelector; + (BOOL)conformsToProtocol:(Protocol *)protocol; - (IMP)methodForSelector:(SEL)aSelector; + (IMP)instanceMethodForSelector:(SEL)aSelector; - (void)doesNotRecognizeSelector:(SEL)aSelector; + (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); - (BOOL)allowsWeakReference UNAVAILABLE_ATTRIBUTE; - (BOOL)retainWeakReference UNAVAILABLE_ATTRIBUTE; + (BOOL)isSubclassOfClass:(Class)aClass; + (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); + (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); + (Class)superclass; + (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead"); 

Here it is introspection, in person. The key to secure code in obzhivek si. These methods allow us to evaluate an object or class, which protocols it implements, to which selectors it can react, and so on. Go ahead

 - (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); - (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""); - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); 

The first two methods will be called up to the moment when everything crashes down on you if you do not implement anything in them. This happens when our object does not understand what you want from it and call a method that it does not have. If you do this meaningfully, then be kind enough to determine one of these methods and decide what to do with the call to the selector, where to send it so to speak. This is actually called a message forwarding mechanism. About this, too, can be read, a bunch of articles, and there are three methods. I will not dwell on this, otherwise we will lose the main essence of this article.

Let's look at the latest methods.

 + (NSUInteger)hash; + (NSString *)description; + (NSString *)debugDescription; 

I think the hash method is known to everyone, it is used to compare objects, to distribute objects among nests in collections, and so on. The description and debugDescription methods must be redefined if you want to see in the logs not just the addresses where your object is located, but some meaningful information for it.

Fuh, that I already got tired to describe these methods. Let us now take a closer look at this class, here it stands at the head of all classes in obzhivek si, basically. What's wrong with him? What I do not like him? Yes, everything is wrong with him!

How it says our principles of OOP: inheritance, encapsulation, polymorphism! But there for some reason it does not say that inherit everything from one class, may the Java and C # developers forgive me. I think programmers with experience building architectures, came up against various pitfalls of architectural solutions that initially seemed very thoughtful. I would say that inheritance is like a time bomb, which will ultimately reduce all your thoughtful architecture to zero or will already tie your hands for development. In many books on architecture it is written that choose a composition instead of inheritance. This is a more flexible approach, but inheritance is also a very powerful thing that you need to know how to handle. Namely, it is correct to separate abstractions and to lay for further expansion. Looking ahead, I’ll say that Swift lost this base class that is accessible to the programmer.

In fact, it is, and it is called SwiftObject from which all classes that need to interact with Objective C, written in swift, are inherited. We will talk more about it. Many will probably say: what is this guy carrying? Inheritance is a cool thing, reusing a code that is bad in it. I think this topic I will put in a separate article, while we talk about something else. For example, why do I need the copy method if I don’t want to copy anything? However, I can call it, and by itself everything will fall if I do not implement the NSCopying puncture. Let's talk more about inheritance. There is an init method that I should call if I want to initialize an object, but there is a dealloc method that is called by myself! For this is the method of the life cycle of an object and you do not need to call it with your hands, never! But no one bothers me to do this, isn't it great? Yes, that's not great at all. It turns out that the NSObject class itself allows us to do what we don’t need to do, or to know what we don’t need to know about. I can develop the topic further and further, but I think it is already clear that NSObject is an ugliness that should not be and therefore it is missing in the Swift language for the programmer. Formally, of course, the base class remained only for the IOS platform, but in order to make friends with each other, these two languages: the old Objective C and the ambitious Swift. Let's probably look at him with one eye.

 @implementation SwiftObject + (void)initialize {} + (instancetype)allocWithZone:(struct _NSZone *)zone { assert(zone == nullptr); return _allocHelper(self); } + (instancetype)alloc { // we do not support "placement new" or zones, // so there is no need to call allocWithZone return _allocHelper(self); } + (Class)class { return self; } - (Class)class { return (Class) _swift_getClassOfAllocated(self); } + (Class)superclass { return (Class) _swift_getSuperclass((const ClassMetadata*) self); } - (Class)superclass { return (Class) _swift_getSuperclass(_swift_getClassOfAllocated(self)); } + (BOOL)isMemberOfClass:(Class)cls { return cls == (Class) _swift_getClassOfAllocated(self); } - (BOOL)isMemberOfClass:(Class)cls { return cls == (Class) _swift_getClassOfAllocated(self); } - (instancetype)self { return self; } - (BOOL)isProxy { return NO; } - (struct _NSZone *)zone { auto zone = malloc_zone_from_ptr(self); return (struct _NSZone *)(zone ? zone : malloc_default_zone()); } - (void)doesNotRecognizeSelector: (SEL) sel { Class cls = (Class) _swift_getClassOfAllocated(self); fatalError(/* flags = */ 0, "Unrecognized selector %c[%s %s]\n", class_isMetaClass(cls) ? '+' : '-', class_getName(cls), sel_getName(sel)); } - (id)retain { auto SELF = reinterpret_cast<HeapObject *>(self); swift_retain(SELF); return self; } - (void)release { auto SELF = reinterpret_cast<HeapObject *>(self); swift_release(SELF); } - (id)autorelease { return _objc_rootAutorelease(self); } - (NSUInteger)retainCount { return swift::swift_retainCount(reinterpret_cast<HeapObject *>(self)); } - (BOOL)_isDeallocating { return swift_isDeallocating(reinterpret_cast<HeapObject *>(self)); } - (BOOL)_tryRetain { return swift_tryRetain(reinterpret_cast<HeapObject*>(self)) != nullptr; } - (BOOL)allowsWeakReference { return !swift_isDeallocating(reinterpret_cast<HeapObject *>(self)); } - (BOOL)retainWeakReference { return swift_tryRetain(reinterpret_cast<HeapObject*>(self)) != nullptr; } // Retaining the class object itself is a no-op. + (id)retain { return self; } + (void)release { /* empty */ } + (id)autorelease { return self; } + (NSUInteger)retainCount { return ULONG_MAX; } + (BOOL)_isDeallocating { return NO; } + (BOOL)_tryRetain { return YES; } + (BOOL)allowsWeakReference { return YES; } + (BOOL)retainWeakReference { return YES; } - (void)dealloc { swift_rootObjCDealloc(reinterpret_cast<HeapObject *>(self)); } - (BOOL)isKindOfClass:(Class)someClass { for (auto isa = _swift_getClassOfAllocated(self); isa != nullptr; isa = _swift_getSuperclass(isa)) if (isa == (const ClassMetadata*) someClass) return YES; return NO; } + (BOOL)isSubclassOfClass:(Class)someClass { for (auto isa = (const ClassMetadata*) self; isa != nullptr; isa = _swift_getSuperclass(isa)) if (isa == (const ClassMetadata*) someClass) return YES; return NO; } + (BOOL)respondsToSelector:(SEL)sel { if (!sel) return NO; return class_respondsToSelector((Class) _swift_getClassOfAllocated(self), sel); } - (BOOL)respondsToSelector:(SEL)sel { if (!sel) return NO; return class_respondsToSelector((Class) _swift_getClassOfAllocated(self), sel); } + (BOOL)instancesRespondToSelector:(SEL)sel { if (!sel) return NO; return class_respondsToSelector(self, sel); } - (BOOL)conformsToProtocol:(Protocol*)proto { if (!proto) return NO; auto selfClass = (Class) _swift_getClassOfAllocated(self); // Walk the superclass chain. while (selfClass) { if (class_conformsToProtocol(selfClass, proto)) return YES; selfClass = class_getSuperclass(selfClass); } return NO; } + (BOOL)conformsToProtocol:(Protocol*)proto { if (!proto) return NO; // Walk the superclass chain. Class selfClass = self; while (selfClass) { if (class_conformsToProtocol(selfClass, proto)) return YES; selfClass = class_getSuperclass(selfClass); } return NO; } - (NSUInteger)hash { return (NSUInteger)self; } - (BOOL)isEqual:(id)object { return self == object; } - (id)performSelector:(SEL)aSelector { return ((id(*)(id, SEL))objc_msgSend)(self, aSelector); } - (id)performSelector:(SEL)aSelector withObject:(id)object { return ((id(*)(id, SEL, id))objc_msgSend)(self, aSelector, object); } - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2 { return ((id(*)(id, SEL, id, id))objc_msgSend)(self, aSelector, object1, object2); } - (NSString *)description { return _getDescription(self); } - (NSString *)debugDescription { return _getDescription(self); } + (NSString *)description { return _getClassDescription(self); } + (NSString *)debugDescription { return _getClassDescription(self); } - (NSString *)_copyDescription { // The NSObject version of this pushes an autoreleasepool in case -description // autoreleases, but we're OK with leaking things if we're at the top level // of the main thread with no autorelease pool. return [[self description] retain]; } - (CFTypeID)_cfTypeID { // Adopt the same CFTypeID as NSObject. static CFTypeID result; static dispatch_once_t predicate; dispatch_once_f(&predicate, &result, [](void *resultAddr) { id obj = [[NSObject alloc] init]; *(CFTypeID*)resultAddr = [obj _cfTypeID]; [obj release]; }); return result; } // Foundation collections expect these to be implemented. - (BOOL)isNSArray__ { return NO; } - (BOOL)isNSDictionary__ { return NO; } - (BOOL)isNSSet__ { return NO; } - (BOOL)isNSOrderedSet__ { return NO; } - (BOOL)isNSNumber__ { return NO; } - (BOOL)isNSData__ { return NO; } - (BOOL)isNSDate__ { return NO; } - (BOOL)isNSString__ { return NO; } - (BOOL)isNSValue__ { return NO; } @end 

That damm! What we see, and we see that the SwiftObject class implements the informal protocol NSObject, but the implementation of the methods is completely different. Now we know the enemy in person, all Swift classes that are not explicitly inherited from NSObject are now implicitly inherited from the SwiftObject class. Immediately make an amendment that this is the case only for the platform in which interaction with Objectice C is necessary. On non-Objective-C platforms (Linux for example), this is not necessary because it is not necessary.

How we learned this is another story. I think you can also tell a little. As you know, Swift language is in the public domain on the Apple repository. No one bothers to download it and build it yourself from the source, which we actually did. But we went a little further. Well-known Xcode, starting with version 8, allows you to slip your toolchain. Feel yes, what does that mean? This means that you can compile Swift with debug information and embed it in Xcode.

image

My colleague did just that, which allowed us to debug the Swift source code directly from Xcode.

image

We are a little distracted, we will continue our reasoning. It is already obvious that the metadata generated in Objective C and generated in Swift are of a different nature. Any programmer who has written for a long time on Objectice C and at least a little picking rantaym knows this structure

 struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; 

We all know that all objects end up in varying degrees in the form of this structure. Our favorite NSObject with you is an abstraction that saves the programmer from direct interaction with this structure. You can read more about it, there are a lot of articles written during the existence of the language, even in Russian. Let's go back to our swift. Now a special Metadata class has appeared for storing metadata, which is quite voluminous and provides the basis for all metadata in Swift. A more detailed description of its structure put in a separate article. Another point, despite the fact that all Swift objects are able to structure their metadata, they still generate Objective C metadata for compatibility. That is, each Swift object has two sets of metadata.

Let's summarize a bit. We figured out that NSObject is ugly, and there is no place for it in a new language. Therefore, in Swift, you can create classes without inheriting them from anything, but in fact for compatibility, they still inherit from SwiftObject. Making friends with the SwiftObject class and the NSObject class allowed the informal protocol NSObject. Which allows you to cast the Swift object into id and pass it in Objective C. But it would be nice if it worked there, so each Swift object generates, in addition to its metadata, also Objective C metadata. That's how it is! Thanks to all! Health and good mood!

Source: https://habr.com/ru/post/320684/


All Articles