Немного О Рантайме Swift Или Куда Пропал Nsobject

Здравствуйте друзья! Я из тех людей, которым скучно просто дергать за ниточки, торчащие из черного ящика, и которые хотят своими глазами увидеть, как он работает под капотом.

Мы поговорим с вами о времени выполнения, да-да, времени выполнения.

Для наших экспериментов рассмотрим старый добрый дедушку Objective C и революционный, но все еще находящийся в стадии разработки Swift. Нам с вами нужно будет погрузиться практически в самое дно абстракций, которые старательно придумали программисты Apple. Давайте немного разберемся, зачем нужно было разрабатывать новый язык.

В самом начале я слышал много негативных отзывов, особенно от опытных разработчиков Objective C. Если присмотреться к новому языку Swift, то, на мой взгляд, он гораздо более зрелый и серьезный.

Во-первых, он писал на C++, в отличие от C, который является основой Objective C. Я выражаю здесь лишь свои сугубо личные предубеждения, с которыми можно соглашаться и спорить.

На мой взгляд, C++ на данный момент является самым серьезным языком разработки, позволяющим делать что угодно, но более элегантно, чем на C. Думаю, именно поэтому именно он был выбран в качестве основы для написания языка Swift, LLDB и т. д. Я не буду сейчас рассматривать функциональность языка Swift, лишь выделим несколько моментов; все остальное можно прочитать в специализированной документации.

На мой взгляд, он гораздо функциональнее и проще для начинающего программиста, собственно, это и было одной из целей его разработки, снизить входной барьер для новых разработчиков.

Вспомните, как у вас волосы встали дыбом после первого знакомства со скобками Objective C, особенно если до этого вы писали на лаконичном C# или Java. Конечно, были некоторые тонкости, которые программисты придумали, на мой взгляд, просто чтобы выделиться.

Но это все лирика, перейдем к делу.

Для начала давайте посмотрим на базовый класс дедушки Objectice C и сравним его со Swift. Для тех, кто все это знает, можно пока выйти покурить.

Как мы знаем из Objective C, любой класс прямо или косвенно должен наследовать от NSObject или NSProxy. Класс NSObject реализует неформальный протокол NSObject. Мы вернемся к этому позже, когда рассмотрим SwiftObject. Забегая вперед, скажу, что именно этот протокол очень поможет подружить два языка в будущем, в этом сила полиморфизма! Предлагаю сделать это, мы рассмотрим все методы класса NSObject по частям.

И после этого я смею сделать вывод, что не так с этим великим NSObject. Я не буду тебя долго мучить, поехали!

  
  
  
  
  
  
   

@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; .



С первыми двумя методами среднестатистический разработчик Objectice C сталкивается нечасто.

Я не буду на них останавливаться, скажу лишь несколько слов.

Метод нагрузка вызывается, когда класс или категория загружается в среду выполнения, не подчиняется классическим правилам наследования и блокирует работу приложения в момент выполнения.

Метод инициализировать вызывается в отложенном режиме, перед первым использованием класса, не блокирует работу приложения и подчиняется классическим правилам наследования.

Все последующие методы отвечают за создание и инициализацию объекта.

Давайте также немного обсудим их.

Метод allocWithZone отвечает за выделение памяти для нашего объекта.

Внутри он вызывает наш любимый malloc. Оно восходит к бородатым временам, когда память делилась на зоны.

Теперь все объекты создаются в одной зоне, поэтому появился метод alloc, который внутренне вызывает allocWithZone и передает ему зону по умолчанию - НСдефолтМаллокзоне .

Методы Dealloc И завершить вызывается при удалении объекта.

Именно в этих методах все связанные ресурсы очищаются и в конечном итоге освобождаются, а память попадает в пул свободной памяти.

Я отмечаю, что завершить обычно не используется Dealloc вызывается в потоке, в котором произошло последнее освобождение .

Перейдем к следующему пакету методов

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

Ну, наверное, даже тому, кто никогда не писал на объективе, не сложно догадаться, что они для копирования.

С зонами все понятно; это древние методы, которые уже не актуальны.

На самом деле, чтобы что-то у нас скопировали, нам еще нужно реализовать протокол NCopying , а если просто вызвать эти методы, то всё упадёт. Но об этом мы поговорим позже.

А пока перейдем к следующему пакету.



+ (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");

Вот это самоанализ, лично.

Ключ к защите кода в цели C. Эти методы позволяют нам оценить объект или класс, какие протоколы он реализует, на какие селекторы он может реагировать и т. д. Продолжайте

- (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("");

Первые два метода будут вызваны до того, как все рухнет, если вы ничего в них не реализуете.

Это происходит, когда наш объект не понимает, чего вы от него хотите, и вызывает метод, которого у него нет. Если вы делаете это осмысленно, то будьте любезны определить для себя один из этих методов и решить, что делать с вызовом селектора, куда его так сказать отправлять.

На самом деле это называется механизмом перенаправления сообщений.

Об этом тоже можно почитать, статей много, и способов три.

Я не буду на этом останавливаться, иначе мы потеряем основную мысль данной статьи.

Давайте посмотрим на новейшие методы

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

Думаю, метод хеширования известен всем, он используется для сравнения объектов, для распределения объектов по гнездам в коллекциях и т.п.

Методы описание И описание отладки его нужно переопределить, если вы хотите видеть в логах не просто адреса, где находится ваш объект, а какую-то значимую для него информацию.

Уф, я уже устал описывать эти методы.

Давайте теперь более подробно рассмотрим этот класс, он, по сути, стоит во главе всех классов по объективности.

Что случилось с ним? Почему он мне так не нравится? Да все с ним не так! Как гласят наши принципы ООП: наследование, инкапсуляция, полиморфизм! Но почему-то не сказано, что наследовать всё от одного класса, да простят меня разработчики Java и C#.

Я думаю, что программисты с опытом построения архитектур сталкивались с различными подводными камнями архитектурных решений, которые поначалу казались очень хорошо продуманными.

Я бы сказал, что наследование — это как бомба замедленного действия, которая в конечном итоге сведет всю вашу продуманную архитектуру к нулю или уже свяжет вам руки в разработке.

Во многих книгах по архитектуре говорится, что лучше выбирать композицию, чем наследование.

Это более гибкий подход, но наследование также является очень мощной вещью, с которой вам нужно уметь обращаться.

А именно правильно отделить абстракции и отложить для дальнейшего расширения.

Забегая вперед, скажу, что Swift потерял этот доступный программисту базовый класс.

На самом деле он существует и называется SwiftObject от которого наследуются все классы, которым необходимо взаимодействовать с Objective C, написанным на Swift. О нем мы поговорим позже.

Многие, наверное, скажут: о чем говорит этот парень! Наследование — классная штука, повторное использование кода, что в этом плохого.

Думаю, вынесу эту тему в отдельную статью, пока мы поговорим о чем-нибудь другом.

Например, зачем мне метод копирования, если я не хочу ничего копировать? Однако я могу это назвать, и конечно все завалится, если я не реализую прокол NScoping. Давайте поговорим подробнее о наследовании.

Существует метод в этом который я должен вызвать, если хочу инициализировать объект и есть метод Dealloc , который называется сам собой! Потому что это метод жизненного цикла объекта, и вам не следует вызывать его вручную, никогда! Но никто не мешает мне это сделать, разве это не здорово? Да, это совсем не здорово.

Оказывается, сам класс NSObject позволяет нам делать что-то, что нам не нужно делать, или знать что-то, о чем нам знать не нужно.

Я могу развивать тему дальше и дальше, но думаю, уже понятно, что NSObject — это чудовище, которого не должно быть и поэтому он исчез в языке Swift для программиста.

Формально, конечно, базовый класс остался только для платформы iOS, но так, чтобы можно было подружить эти два языка: старый Objective C и амбициозный Swift. Давайте посмотрим на это одним глазом

@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

Та черт возьми! Что мы видим, и видим, что класс SwiftObject реализует неформальный протокол NSObject, но реализация методов совершенно другая.

Теперь мы знаем врага в лицо, все классы Swift, которые не наследуются явно от NSObject, теперь неявно наследуются от класса SwiftObject. Сразу сделаю поправку, что это происходит только для платформы, в которой необходимо взаимодействие с Objectice C. На платформах, отличных от Objective C (например, Linux), этого не происходит, потому что в этом нет необходимости.

Как мы это узнали, это уже другая история.

Я думаю, мы тоже можем вам кое-что рассказать.

Как вы знаете, язык Swift находится в публичном доступе в репозитории Apple. Никто не мешает вам скачать его и скомпилировать самостоятельно из исходного кода, что мы и сделали.

Но мы пошли немного дальше.

Известный Xcode, начиная с версии 8, позволяет вставлять свой собственный инструментарий.

Вы чувствуете «да», что это значит? Это означает, что вы можете собрать Swift с отладочной информацией и поместить ее в Xcode.

Немного о рантайме Swift или куда пропал NSObject

Мой коллега так и сделал, что позволило нам отлаживать исходники Swift прямо из Xcode.

Немного о рантайме Swift или куда пропал NSObject

Мы немного отвлеклись, продолжим наши рассуждения.

Уже очевидно, что мы можем сделать вывод, что метаданные, сгенерированные в Objective C и сгенерированные в Swift, имеют разную природу.

Эту структуру знает любой программист, давно писавший на Objectice C и хоть немного возившийся с рантаймом.



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;

Все мы знаем, что все объекты в той или иной степени похожи на эту структуру.

Наш любимый NSObject — это абстракция, которая не позволяет программисту напрямую взаимодействовать с этой структурой.

Вы можете прочитать об этом больше; за время существования языка написано очень много статей, даже на русском языке.

Вернемся к нашему Swift. Теперь есть специальный класс для хранения метаданных Метаданные , который довольно обширен и представляет собой основу для всех метаданных в Swift. Более подробное описание его структуры я приведу в отдельной статье.

Еще один момент: несмотря на то, что все объекты Swift имеют собственную структуру метаданных, они все равно генерируют метаданные Objective C для совместимости.

То есть каждый объект Swift имеет два набора метаданных.

Подведем небольшой итог.

Мы выяснили, что NSObject уродлив и ему нет места в новом языке.

Поэтому в Swift можно создавать классы, ни от чего их не наследуя, но фактически для совместимости они всё равно наследуются от SwiftObject. Неофициальный протокол NSObject позволил подружить класс SwiftObject и класс NSObject. Что позволяет вам привести объект Swift в id и передать его в Objective C. Но было бы неплохо, если бы это работало и там, чтобы каждый объект Swift генерировал помимо своих метаданных метаданные Objective C. Что-то вроде этого! Спасибо всем! Здоровья и хорошего настроения! Теги: #разработка iOS #objective-c #runtime #Swift #разработка iOS #Разработка мобильных приложений #objective-c #Swift

Вместе с данным постом часто просматривают: