Большинство iOS-проектов частично или полностью переходят на Swift. Swift — замечательный язык, и за ним будущее разработки под iOS. Но язык нераздельно связан с инструментарием, а в инструментарии Swift есть недостатки.


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


В связи с этим существующим проектам может быть выгоднее продолжать разработку на Objective-C. А Objective-C уже не тот, что был раньше!


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



let и var


В Objective-C больше не нужно явно указывать типы переменных: еще в Xcode 8 появилось расширение языка __auto_type, а до Xcode 8 выведение типов было доступно в Objective-C++ (при помощи ключевого слова auto с появлением C++0X).


Для начала добавим макросы let и var:


#define let __auto_type const
#define var __auto_type

// Было
NSArray<NSString *> *const items = [string componentsSeparatedByString:@","];

void(^const completion)(NSData * _Nullable, NSURLResponse * _Nullable, NSError * _Nullable) = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // ...
};

// Стало
let items = [string componentsSeparatedByString:@","];

let completion = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // ...
};

Если раньше писать const после указателя на Objective-C класс было непозволительной роскошью, то теперь неявное указание const (через let) стало само собой разумеющимся. Особенно заметна разница при сохранении блока в переменную.


Для себя мы выработали правило использовать let и var для объявления всех переменных. Даже когда переменная инициализируется значением nil:


- (nullable JMSomeResult *)doSomething {
    var result = (JMSomeResult *)nil;

    if (...) {
        result = ...;
    }

    return result;
}

Единственное исключение — когда надо гарантировать, что переменной присваивается значение в каждой ветке кода:


NSString *value;

if (...) {
    if (...) {
        value = ...;
    } else {
        value = ...;
    }
} else {
    value = ...;
}

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


И напоследок: чтобы использовать let и var для переменных типа id, нужно отключить предупреждение auto-var-id (добавить -Wno-auto-var-id в "Other Warning Flags" в настройках проекта).


Автовывод типа возвращаемого значения блока


Немногие знают, что компилятор умеет выводить тип возвращаемого значения блока:


let block = ^{
    return @"abc";
};

// `block` имеет тип `NSString *(^const)(void)`

Это очень удобно. Особенно если вы пишете "реактивный" код с использованием ReactiveObjC. Но есть ряд ограничений, при которых нужно явно указывать тип возвращаемого значения.


  1. Если в блоке есть несколько операторов return, возвращающих значения разных типов.


    let block1 = ^NSUInteger(NSUInteger value){
        if (value > 0) {
            return value;
        } else {
            // `NSNotFound` имеет тип `NSInteger`
            return NSNotFound;
        }
    };
    
    let block2 = ^JMSomeBaseClass *(BOOL flag) {
        if (flag) {
            return [[JMSomeBaseClass alloc] init];
        } else {
            // `JMSomeDerivedClass` наследуется от `JMSomeBaseClass`
            return [[JMSomeDerivedClass alloc] init];
        }
    };

  2. Если в блоке есть оператор return, возвращающий nil.


    let block1 = ^NSString * _Nullable(){
        return nil;
    };
    
    let block2 = ^NSString * _Nullable(BOOL flag) {
        if (flag) {
            return @"abc";
        } else {
            return nil;
        }
    };

  3. Если блок должен возвращать BOOL.


    let predicate = ^BOOL(NSInteger lhs, NSInteger rhs){
        return lhs > rhs;
    };


Выражения с оператором сравнения в языке C (и, следовательно, в Objective-C) имеют тип int. Поэтому лучше взять за правило всегда явно указывать возвращаемый тип BOOL.


Generics и for...in


В Xcode 7 в Objective-C появились generics (точнее, lightweight generics). Надеемся, что вы их уже используете. Но если нет, то можно посмотреть сессию WWDC или прочитать здесь или здесь.


Мы для себя выработали правило всегда указывать generic-параметры, даже если это id (NSArray<id> *). Таким образом можно легко отличить legacy-код, в котором generic-параметры еще не указаны.


Имея макросы let и var, мы ожидаем, что сможем использовать их в цикле for...in:


let items = (NSArray<NSString *> *)@[@"a", @"b", @"c"];

for (let item in items) {
    NSLog(@"%@", item);
}

Но такой код не скомпилируется. Скорее всего, __auto_type не стали поддерживать в for...in, потому что for...in работает только с коллекциями, реализующими протокол NSFastEnumeration. А для протоколов в Objective-C нет поддержки generics.


Чтобы исправить этот недостаток, попробуем сделать свой макрос foreach. Первое, что приходит в голову: у всех коллекций в Foundation есть свойство objectEnumerator, и макрос мог бы выглядеть так:


#define foreach(object_, collection_)     for (typeof([(collection_).objectEnumerator nextObject]) object_ in (collection_))

Но для NSDictionary и NSMapTable метод протокола NSFastEnumeration итерируется по ключам, а не по значениям (нужно было бы использовать keyEnumerator, а не objectEnumerator).


Нам понадобится объявить новое свойство, которое будет использоваться только для получения типа в выражении typeof:


@interface NSArray<__covariant ObjectType> (ForeachSupport)

@property (nonatomic, strong, readonly) ObjectType jm_enumeratedType;

@end

@interface NSDictionary<__covariant KeyType, __covariant ObjectType> (ForeachSupport)

@property (nonatomic, strong, readonly) KeyType jm_enumeratedType;

@end

#define foreach(object_, collection_)     for (typeof((collection_).jm_enumeratedType) object_ in (collection_))

Теперь наш код выглядит намного лучше:


// Было
for (MyItemClass *item in items) {
    NSLog(@"%@", item);
}

// Стало
foreach (item, items) {
    NSLog(@"%@", item);
}

Сниппет для Xcode
foreach (<#object#>, <#collection#>) {
    <#statements#>
}

Generics и copy/mutableCopy


Еще одно место, где в Objective-C отсутствует типизация, — это методы -copy и -mutableCopy (а также методы -copyWithZone: и -mutableCopyWithZone:, но их мы не вызываем напрямую).


Чтобы избежать необходимости явного приведения типов, можно переобъявить методы с указанием возвращаемого типа. Например, для NSArray объявления будут такими:


@interface NSArray<__covariant ObjectType> (TypedCopying)

- (NSArray<ObjectType> *)copy;

- (NSMutableArray<ObjectType> *)mutableCopy;

@end

let items = [NSMutableArray<NSString *> array];
// ...

// Было
let itemsCopy = (NSArray<NSString *> *)[items copy];

// Стало
let itemsCopy = [items copy];

warn_unused_result


Раз уж мы переобъявили методы -copy и -mutableCopy, было бы неплохо гарантировать, что результат вызова этих методов будет использован. Для этого в Clang есть атрибут warn_unused_result.


#define JM_WARN_UNUSED_RESULT __attribute__((warn_unused_result))

@interface NSArray<__covariant ObjectType> (TypedCopying)

- (NSArray<ObjectType> *)copy JM_WARN_UNUSED_RESULT;

- (NSMutableArray<ObjectType> *)mutableCopy JM_WARN_UNUSED_RESULT;

@end

Для следующего кода компилятор сгенерирует предупреждение:


let items = @[@"a", @"b", @"c"];

[items mutableCopy]; // Warning: Ignoring return value of function declared with 'warn_unused_result' attribute.

overloadable


Немногие знают, что Clang позволяет переопределять функции в языке C (а следовательно, и в Objective-C). C помощью атрибута overloadable можно создавать функции с одинаковым названием, но с разными типами аргументов или с их разным количеством.


Переопределяемые функции не могут отличаться только лишь типом возвращаемого значения.


#define JM_OVERLOADABLE __attribute__((overloadable))

JM_OVERLOADABLE float JMCompare(float lhs, float rhs);

JM_OVERLOADABLE float JMCompare(float lhs, float rhs, float accuracy);

JM_OVERLOADABLE double JMCompare(double lhs, double rhs);

JM_OVERLOADABLE double JMCompare(double lhs, double rhs, double accuracy);

Boxed expressions


В далеком 2012 году в сессии WWDC 413 Apple представила литералы для NSNumber, NSArray и NSDictionary, а также boxed expressions. Подробно о литералах и boxed expressions можно прочитать в документации Clang.


// Литералы
@YES                      // [NSNumber numberWithBool:YES]
@NO                       // [NSNumber numberWithBool:NO]
@123                      // [NSNumber numberWithInt:123]
@3.14                     // [NSNumber numberWithDouble:3.14]
@[obj1, obj2]             // [NSArray arrayWithObjects:obj1, obj2, nil]
@{key1: obj1, key2: obj2} // [NSDictionary dictionaryWithObjectsAndKeys:obj1, key1, obj2, key2, nil]

// Boxed expressions
@(boolVariable)           // [NSNumber numberWithBool:boolVariable]
@(intVariable)            // [NSNumber numberWithInt:intVariable)]

С помощью литералов и boxed expressions можно легко получить объект, представляющий число или булево значение. Но чтобы получить объект, оборачивающий структуру, нужно написать немного кода:


// Оборачивание `NSDirectionalEdgeInsets` в `NSValue`
let insets = (NSDirectionalEdgeInsets){ ... };
let value = [[NSValue alloc] initWithBytes:&insets objCType:@encode(typeof(insets))];

// ...

// Получение `NSDirectionalEdgeInsets` из `NSValue`
var insets = (NSDirectionalEdgeInsets){};
[value getValue:&insets];

Для некоторых классов определены вспомогательные методы и свойства (наподобие метода +[NSValue valueWithCGPoint:] и свойства CGPointValue), но это все равно не так удобно, как boxed expression!


И в 2015 году Алекс Денисов сделал патч для Clang, позволяющий использовать boxed expressions для оборачивания любых структур в NSValue.


Чтобы наша структура поддерживала boxed expressions, нужно просто добавить атрибут objc_boxable для структуры.


#define JM_BOXABLE __attribute__((objc_boxable))

typedef struct JM_BOXABLE JMDimension {
    JMDimensionUnit unit;
    CGFloat value;
} JMDimension;

И мы можем использовать синтаксис @(...) для нашей структуры:


let dimension = (JMDimension){ ... };

let boxedValue = @(dimension); // Имеет тип `NSValue *`

Получать структуру обратно по-прежнему придется через метод -[NSValue getValue:] или метод категории.


В CoreGraphics определен свой макрос CG_BOXABLE, и boxed expressions уже поддержаны для структур CGPoint, CGSize, CGVector и CGRect.


Для остальных часто используемых структур мы можем добавить поддержку boxed expressions самостоятельно:


typedef struct JM_BOXABLE _NSRange NSRange;
typedef struct JM_BOXABLE CGAffineTransform CGAffineTransform;
typedef struct JM_BOXABLE UIEdgeInsets UIEdgeInsets;
typedef struct JM_BOXABLE NSDirectionalEdgeInsets NSDirectionalEdgeInsets;
typedef struct JM_BOXABLE UIOffset UIOffset;
typedef struct JM_BOXABLE CATransform3D CATransform3D;

Compound literals


Еще одна полезная конструкция языка — compound literal. Compound literals появились еще в GCC в виде расширения языка, а позже были добавлены в стандарт C11.


Если раньше, встретив вызов UIEdgeInsetsMake, мы могли только гадать, какие отступы мы получим (надо было смотреть объявление функции UIEdgeInsetsMake), то с compound literals код говорит сам за себя:


// Было
UIEdgeInsetsMake(1, 2, 3, 4)
// Стало
(UIEdgeInsets){ .top = 1, .left = 2, .bottom = 3, .right = 4 }

Еще удобнее использовать такую конструкцию, когда часть полей равны нулю:


(CGPoint){ .y = 10 }
// вместо
(CGPoint){ .x = 0, .y = 10 }

(CGRect){ .size = { .width = 10, .height = 20 } }
// вместо
(CGRect){ .origin = { .x = 0, .y = 0 }, .size = { .width = 10, .height = 20 } }

(UIEdgeInsets){ .top = 10, .bottom = 20 }
// вместо
(UIEdgeInsets){ .top = 20, .left = 0, .bottom = 10, .right = 0 }

Конечно, в compound literals можно использовать не только константы, но и любые выражения:


textFrame = (CGRect){
    .origin = {
        .y = CGRectGetMaxY(buttonFrame) + textMarginTop
    },
    .size = textSize
};

Сниппеты для Xcode
(NSRange){ .location = <#location#>, .length = <#length#> }

(CGPoint){ .x = <#x#>, .y = <#y#> }

(CGSize){ .width = <#width#>, .height = <#height#> }

(CGRect){
    .origin = {
        .x = <#x#>,
        .y = <#y#>
    },
    .size = {
        .width = <#width#>,
        .height = <#height#>
    }
}

(UIEdgeInsets){ .top = <#top#>, .left = <#left#>, .bottom = <#bottom#>, .right = <#right#> }

(NSDirectionalEdgeInsets){ .top = <#top#>, .leading = <#leading#>, .bottom = <#bottom#>, .trailing = <#trailing#> }

(UIOffset){ .horizontal = <#horizontal#>, .vertical = <#vertical#> }

Nullability


В Xcode 6.3.2 в Objective-C появились nullability-аннотации. Разработчики Apple добавили их для импортирования Objective-C API в Swift. Но если что-то добавлено в язык, то надо постараться поставить это себе на службу. И мы расскажем, как используем nullability в Objective-C проекте и какие есть ограничения.


Чтобы освежить знания, можно посмотреть сессию WWDC.


Первое, что мы сделали, — это начали писать макросы NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END во всех .m-файлах. Чтобы не делать этого руками, мы патчим шаблоны файлов прямо в Xcode.


Мы стали также правильно расставлять nullability для всех приватных свойств и методов.


Если мы добавляем макросы NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END в уже существующий .m-файл, то сразу дописываем недостающие nullable, null_resettable и _Nullable во всем файле.


Все полезные предупреждения компилятора, связанные с nullability, включены по умолчанию. Но есть одно экстремальное предупреждение, которое хотелось бы включить: -Wnullable-to-nonnull-conversion (задается в "Other Warning Flags" в настройках проекта). Компилятор выдает это предупреждение, когда переменная или выражение с nullable-типом неявно приводится к nonnull-типу.


+ (NSString *)foo:(nullable NSString *)string {
    return string; // Implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'
}

К сожалению, для __auto_type (а следовательно, и let и var) это предупреждение не срабатывает. В типе, выведенном через __auto_type, отбрасывается nullability-аннотация. И, судя по комментарию разработчика Apple в rdar://27062504, это поведение уже не изменится. Экспериментально замечено, что добавление _Nullable или _Nonnull к __auto_type ни на что не влияет.


- (NSString *)test:(nullable NSString *)string {
    let tmp = string;
    return tmp; // Нет предупреждения
}

Для подавления предупреждения nullable-to-nonnull-conversion мы написали макрос, который делает "force unwrap". Идея взята из макроса RBBNotNil. Но за счет поведения __auto_type удалось избавиться от вспомогательного класса.


#define JMNonnull(obj_)     ({         NSCAssert(obj_, @"Expected `%@` not to be nil.", @#obj_);         (typeof({ __auto_type result_ = (obj_); result_; }))(obj_);     })

Пример использования макроса JMNonnull:


@interface JMRobot : NSObject

@property (nonatomic, strong, nullable) JMLeg *leftLeg;
@property (nonatomic, strong, nullable) JMLeg *rightLeg;

@end

@implementation JMRobot

- (void)stepLeft {
    [self step:JMNonnull(self.leftLeg)]
}

- (void)stepRight {
    [self step:JMNonnull(self.rightLeg)]
}

- (void)step:(JMLeg *)leg {
    // ...
}

@end

Отметим, что на момент написания статьи предупреждение nullable-to-nonnull-conversion работает неидеально: компилятор пока не понимает, что nullable-переменную после проверки на неравенство nil можно воспринимать как nonnull.


- (NSString *)foo:(nullable NSString *)string {
    if (string != nil) {
        return string; // Implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'
    } else {
        return @"";
    }
}

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


- (NSString *)foo:(nullable NSString *)stringOrNil {
    if (let string = stringOrNil) {
        return string;
    } else {
        return @"";
    }
}

Полезные ссылки


Есть также ряд более известных макросов и ключевых слов, которые хотелось бы упомянуть: ключевое слово @available, макросы NS_DESIGNATED_INITIALIZER, NS_UNAVAILABLE, NS_REQUIRES_SUPER, NS_NOESCAPE, NS_ENUM, NS_OPTIONS (или свои макросы для тех же атрибутов) и макрос @keypath из библиотеки libextobjc. Советуем также посмотреть остальные возможности библиотеки libextobjc.



> Код для статьи выложен в gist.


Заключение


В первой части статьи мы постарались рассказать об основных возможностях и простых улучшениях языка, которые существенно облегчают написание и поддержку Objective-C кода. В следующей части мы покажем, как можно еще увеличить свою продуктивность с помощью enum'ов как в Swift (они же Case-классы; они же Алгебраические типы данных, ADT) и возможности реализации методов на уровне протокола.

Комментарии (45)


  1. kronos
    27.11.2018 16:23

    «И, что очень важно, проекты на Swift собираются слишком долго.»
    Мне кажется вы забыли упомянуть, насколько дольше разрабатывать, сложнее поддерживать obj-c проекты и насколько труднее искать специалистов.


    1. artyom_stv Автор
      27.11.2018 16:50

      Согласен, что искать специалистов сложнее. Что касается скорости разработки, она скорее зависит от принятых практик.
      Почему вы считаете, что Objective-C проекты сложнее поддерживать?


      1. Agranatmark
        27.11.2018 23:45

        1) Менее строгая типизация из коробки без танцев с бубнами (а то, чем вы занимаетесь в статье — это танцы с бубнами).
        2) Приходится писать больше кода, код менее читаем.
        3) Много новых библиотек написаны на swift (например всякие аналитики от facebook). Там куча предупреждений от компилятора, которые разработчики не сильно то хотят и фиксить. Директивы игнорирования предупреждений периодически ломаются, причем на ровном месте, что мешает использовать флаг — treat warnings as errors.
        4) Обобщения кастрированные.
        5) Синтаксис замыканий вырвиглазный, приходится лазить на сайт fuckingblocksyntax.com
        6) Количество файлов в проекте x2
        Я могу ещё что-нибудь выделить, но пожалуй мне уже хватит.


        1. artyom_stv Автор
          28.11.2018 00:30

          В начале статьи приведены аргументы, почему имеет смысл остаться на Objective-C и подождать лучшего Swift'а. Недостатки Objective-C большинство разработчиков знают. И, конечно, в статье мы не призываем никого начинать новый проект на Objective-C. Пройдет еще пара лет и, надеюсь, польза от Objective-C в проекте останется только для C++ разработчиков.
          В конечном счете разработчик идет на компромисс: продолжить писать на Objective-C или насладиться преимуществами Swift. И в том и в другом случае придется «танцевать с бубном». Для Objective-C нужно следовать строгому стилю кодирования и доработать инструментарий: в том числе, написать много страниц макросов, использовать кодогенерацию и, возможно, доработать тулчейн. Для Swift нужно разбивать проект на много подпроектов, чтобы как-то контролировать время компиляции, смириться с +5–10Mb образа в сторе и описанными во введении проблемами, и, возможно, тоже доработать тулчейн.
          Было бы интересно узнать, если кто-то смог настроить сборку Swift-проектов на удаленной мощной машине (как это делают, например, разработчики на Kotlin, сталкивающиеся с теми же проблемами долгой сборки).


          1. RomanBambura
            28.11.2018 01:31

            а можно узнать, какие проблемы у вас со временем компиляции приложения?
            судя по вашему работодателю это онлайн стор, что там долго компилится?
            я разработчик другого онлайн стора LeBoutique, он полностью написан на свифт
            и что-то я на своем мак про 13 8 гиг оперативки 2017 года не вижу трудностей с компиляцией


            1. artyom_stv Автор
              28.11.2018 01:35
              +1

              Время компиляции зависит от размера проекта. У нас проект уже достаточно большой. На данный момент SLOC 199105 (LOC 294021).


              1. RomanBambura
                28.11.2018 10:05
                +1

                Сори но количество строк не говорит ни о чем, можно наплодить очень много кода, а еще можно использовать то что не рекомендуется в swift и что тормозит компиляцию, но больше всего что objective-c что swift тормозит неправильная архитектура сториборд файлов. Если запихнуть все в один файл то и компа станет мало для комфотрной работы с этим файлов, не говоря уже о компиляции.
                У меня есть огромные проекты на objectice-c и на swift который начинался с swift 2 плавно мигрировал на swift 4 и могу сказать что только архивация проекта для appstore занимает время, а компиляция на симулятор или девайс вообще без дискомфорта.
                И самое важное на свифте приложение магазина имеет crash free 99.9% и разработка прошла на много быстрее чем я привык писать на objective c,
                так что я очень давоболен свифтом и у меян уже отторжение objective c


                1. artyom_stv Автор
                  29.11.2018 15:32

                  Не понимаю, к чему вы это написали. Речь шла о зависимости времени сборки проекта от количества кода.
                  Если не секрет, какие свои проекты вы упоминаете как огромные?


                  1. RomanBambura
                    29.11.2018 15:36

                    e-commerce LeBoutique на Swift из последних
                    Wardrobe Assistant на Objective c (старое) готовлю обсолютно новое приложение уже на Swift


                    1. artyom_stv Автор
                      29.11.2018 15:44

                      У нас разное представление об огромных проектах.
                      Я говорю о проектах куда большего масштаба: с экспериментами (A/B тестами), большим количеством экранов, и т.д.


                      1. RomanBambura
                        29.11.2018 15:51

                        Вы думаете что LeBoutique отличаетяс от Joom? все тоже самое и AB тесты и кучи контролеров и состояний
                        И 3 маркетенговые SDK
                        и кастомыне push notification
                        и deep links
                        но вот из левых стоит только Alamofire и все остальное написано самим


        1. artyom_stv Автор
          29.11.2018 19:36

          Вы упомянули аналитики от Facebook. Но это неправда: Facebook не пишет библиотеки на Swift. Разве что какой-то open source.
          В Facebook вообще всё разрабатывают на Objective-C++ и не планируют переходить на Swift.


          1. Agranatmark
            30.11.2018 17:11

            Во-первых не все. github.com/facebook/facebook-swift-sdk и github.com/BoltsFramework/Bolts-Swift
            Во-вторых предупреждения «Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior» же вообще выдаёт фэйсбуковская objective-c библиотека Bolts, которая является зависимостью objective-c библиотеки FBSDKCoreKit.


    1. PavelGatilov
      29.11.2018 01:20
      +1

      Неважно как быстро вы находите специалистов, если сборка полного проекта занимает несколько часов


      1. RomanBambura
        29.11.2018 15:03

        Я не понимаю о чем речь вообще, какие несколько часов? как это несколько часов?
        большой проект на Swift после очистки собирается за 5 минут на среднем компе


        1. PavelGatilov
          29.11.2018 15:08

          Смотря насколько большой. Если исходники проекта занимают 5-10GB то нет, даже с 128GB оперативки, весь проект даже за 3 часа не собрать.


          1. RomanBambura
            29.11.2018 15:12

            А можно поинтересоваться, что это за проект с исходниками такими? Это код столько весит?
            что в этом проекте происходит с pods? какое колличество левых на каждый чих фреймворков нагруженно?
            Xcode в развернутом состоянии занимает меньше чем 6 без симуляторов


            1. PavelGatilov
              29.11.2018 15:17
              +1

              Конкретно сейчас Facebook. И нет это не «левые» фреймворки на каждый чих, и нет — мы не используем pods. По опыту могу сказать что исходники других компаний из Big5 будут не особо меньше. Больших стартапов тоже будут не меньше (Uber, Airbnb).


              1. RomanBambura
                29.11.2018 15:25

                сейчас смотрю в стор
                facebook 312MB
                Airbnb 165MB
                Uber 220MB
                сори с таким выхлопом не может быть 5-10 гигов никак
                Мой проект на Objective c который в сторе 120MB на диске он 1.2GB с имеджами


                1. PavelGatilov
                  29.11.2018 15:30

                  Если бы мы зашивали весь код в исходники проекта — приложение никто бы не загружал. То же самое с Uber, Google, Airbnb.

                  Очень многое догружается в рантайме в зависимости от паттерна использования юзера.


  1. PkXwmpgN
    27.11.2018 18:13
    -5

    Прошу прощения, не удержался
    image


    Скрытый текст

    image


  1. NeoCode
    27.11.2018 21:05

    А можно ли использовать последние версии ObjC не на MacOS?
    Реализация Swift для Linux вроде есть (правда я не знаю, самая последняя или нет).
    Какой-то ObjC входит в состав GCC, но вроде бы там нечто весьма древнее, и фичи, описываемые в данной статье, там скорее всего недоступны.


    1. artyom_stv Автор
      28.11.2018 02:35

      Есть разные реализации Objective-C Runtime для Windows. Самая актуальная и стабильная — GNUstep. Есть инструкция сборки GNUstep с Clang. Судя по статье в их wiki, с Clang собирается runtime библиотека gnustep/libobjc2, в которой поддержаны все новые фичи Objective-C. Clang можно собрать самому или попытаться использовать Clang из Visual Studio. В последних Clang поддерживается всё, что описано в статье. Я сам не пробовал использовать GNUstep.
      Что касается реализации Core Animation и UIKit, — есть проприетарные реализации, которые не выложены в публичный доступ. Из публичных: мне известен только Windows Bridge for iOS Project. Не смог проверить, как работает — с первой попытки не завелось на моем ноутбуке (пока нет времени, чтобы разобраться в причинах). Но есть обзоры на youtube (например, этот), где всё работает.
      Если использовать Windows Bridge for iOS Project, то ничего самому собирать не нужно. Нужно просто следовать их инструкции. И судя по этому issue, в 2016 году они перешли на GNUstep :)


  1. PavelOsipov
    28.11.2018 12:41
    +1

    Большой респект автору.

    Пару фишечек не знал, в частности про засахаривания боксинга структур.

    Добавлю свои 5 капель керосина в холивар Objective-C vs Swift в пользу старого доброго. Когда я после 10-летнего опыта разработки на C++ перешёл в мир Objective-C, для меня этот язык со странным синтаксом стал как глоток свежего воздуха.

    Во-первых, в Objective-C идеально соблюден баланс динамической и статической типизации. Мы можем брать лучшее из обоих миров. Выше был справедливый коментарий от Agranatmark, что представленные в статье премы – это с точки зрения Swift костыль для внедрения статики в мир Objective-C. Но есть и контрпримеры. Например, кодогенерация на основе Sourcery – не что иное как костыль с точки зрения Objective-C для внедрения метапрограммирования в Swift. Чтобы не быть слишком абстрактным, приведу в пример пару своих библиотек, которые элегантно делаются на Objective-C и волосато на Swift:

    1. POSAllocationTracker. Автоматическая детекция утечек объектов. Используется в юнит-тестах на утечки и в дебажной сборке для обнаружения утечек важных с точки зрения бизнес-логики объектов. Например, IoC-контейнер может не просто вернуть некий объект со скопом Singleton, но и перед созданием проверить, реально ли этого объекта не существует в памяти из-за допущенной утечки.
    2. POSLens. Обобщенные функциональные линзы в 100 строчек. В противовес лаконичной реализации на Objective-C в этой статье показывается, как их сделать для Swift и порешать бойлерплейт с помощью Sourcery.

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

    Во-вторых, Objective-C более прост в освоении, чем Swift. Всего 120 страниц в официальном мануале против официального 2-томника Swift в iBooks. Количество синтаксических нюансов в последнем уже довольно изрядно, и ветерок C++ из прошлого вполне ощущается. Не зря Дейв Абархамс одновременно is a member of the Swift language core team and… also founding member of Boost.org and a former participant in the ISO C++ standards committee. Как любителю метапрограммирования с использованием Boost MPL и Fusion мне понятен и симпатичен этот путь Swift, но шаблоны в C++ сложны, и в плюсовом сообществе отношение к ним неоднозначное. В поддежку мнения о нарастающей сложности Swift приведу также вот этот недавний замечательный тредик в Twitter.

    I found ObjC much easier to learn & teach than Swift. The main difference is you really want a formal introduction to ObjC that takes about a week (the BNR book for instance). Swift you can play with and guess the bare basics, but after that, the learning curve is much steeper.

    — Rob Napier (@cocoaphony) 14 ноября 2018 г.




    1. RomanBambura
      28.11.2018 14:07

      Objective-C сам по себе мало значит
      а вот когда начинаешь использовать UIKit вот где засело долгое мучительное обучение и нарабатывание опыта через года практики


      1. PavelOsipov
        28.11.2018 21:53

        Это правда. Все мы больше UIKit-программисты, чем Swift или Objective-C.


    1. Agranatmark
      28.11.2018 20:18

      С пунктом «прост в освоении», можно поспорить хотя-бы с позиции свизлинга. Этот ваш самый динамизм, крайне противная штука в отладке. Можно столкнуться с тем, что уважаемый тов. разработчик потусторонней библиотеки, услужливо засвизлит вам метод, который свизлите вы или разработчик другой библиотеки. Чсх — это может произойти в какой-нибудь крайне важной для вас библиотеке(например, статистике) и вот ваша прекрасная библиотека проверки утечки не работает так, как вы того ожидали.
      Использование кучи вырвиглазных вложенных макросов, тоже не прибавляет к порогу входа. Воспользуется какой-нибудь товарищ макросной магией, и в pch файле объявит макрос, который будет иметь тоже название, как и функция стандартной библиотеки (например, NSLog), которую вы, не подозревая надвигающегося краха, используете, начнёт вести себя не так. И вот макросы уже не кажутся таким прекрасным инструментом. А логи, которые пишут вам матерное слово, вместо того, чтобы логировать (это ещё хорошо, если все обойдется этим, а не записью на диск в главном потоке).


    1. Agranatmark
      28.11.2018 20:35
      -1

      Ещё есть «прекрасный» инструмент KVC. И поменяв названия свойства, к которому, кто-нибудь обратится через KVC, мы словим очень «приятное» поведение в проде. А чего стоит эта самая диспетчеризация через посылку сообщений — отдельный разговор. Крч, простите но мне лично этот самый динамизм, ни капли не доставляет.


      1. PavelOsipov
        28.11.2018 21:47
        +1

        Опасения понятны и оправданны. У всего есть своя цена, и за динамизм тоже приходится платить. Тут вопрос в том, кому что дороже. Мне лично нравится наличие свободы выбора. Хочу – пишу безопасно, а хочу – срезаю углы. Objective-C более толерантный язык.


  1. aeeneas
    28.11.2018 13:16

    Кстати в iOS и MacOS до сих пор в-основном используется ObjC.


    1. RomanBambura
      28.11.2018 14:03

      objc используется потому, что это legacy и его просто тянут сейчас и естественным путем выживают из проектов.
      И как раз в основном сейчас это смешенные проекты objc и swift


      1. PavelGatilov
        29.11.2018 15:12

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


        1. RomanBambura
          29.11.2018 15:19

          я разработчик (с 10 летним примерно стажем) как раз комерческого приложения e-commerce LeBoutique и могу сказать что стабильность очень высокая 99.9% крашфри
          скорость работы приложения отличная даже на пристарелых устройствах типа 5S поддерживаемость операционных систем сейчас у меня в приожении >=10.0
          так что целесообразность использования только Swift оправдана полностью


          1. PavelGatilov
            29.11.2018 15:27

            Да хоть 40 лет.

            Никто не говорил что Swift — будет испытывать больший краш рейт или то что скорость приложения упадет. Скорость исполнения некоторых операций даже выше в Swift чем в Objective C.

            Основные проблемы с swift performance — это cold start time и время сборки проекта. А также — отсутствие как таковой стабильности в API языка.

            Когда время сборки проекта даже в Objective-C занимает больше часа — перенос его на Swift будет критичной потерей времени, и это основной аргумент для больших компаний и больших проектов все еще использовать Objective-C

            Поэтому может LeBoutique и не замечает всего этого, но это только пока что. Эти проблемы появляются с ростом и решаются очень болезненно.


            1. RomanBambura
              29.11.2018 15:33

              Возьмем большую компанию facebook и ее месенджер с весом 135MB и возьмем Телеграм X на свифте с весом 77MB
              в чем различие в 70MB?
              может что-то не так с приложением фейсбук что такой перегруз, что фейсбук тормоз на устройстве и выжирает ресурс?


              1. PavelGatilov
                29.11.2018 15:44
                +1

                В том что телеграмм намного меньше? И то что он содержит намного меньше фич?


                Хотите померятся размерами приложений? Посмотрите WhatsApp и Messenger Lite.


                1. RomanBambura
                  29.11.2018 15:49
                  -1

                  Ок, этот спор никуда не приведет
                  Хотите я укажу одну маленькую штуку которая покажет что разработка Мессенджера далеко не идеальна?
                  откройте месенджер первый контролер у нас есть три верхних таба
                  активный Messages и нажмем на Groups и мы увидим как пролетает мимо средний контролер
                  Теперь возьмем LeBoutique там подобные табы сверху и нажмем последний и мы не увидим как мимо прилетают 2 которые находятся меджу


                  1. PavelGatilov
                    29.11.2018 15:55
                    +1

                    Вау. Вы же не серйозно сейчас?


                    Если все таки серйозно то тогда отвечу:


                    1) С точки зрения UX как такового абсолютно не очевидно какой подход будет более правильным.


                    2) Поведение UI не всегда имеет зависимость от качества кода, т.к. разработчики не всегда имеют контроль над этим


                    3) Сравнивать приложение с активной пользовательской базой менее 250к и приложение с пользовательской базой более 1.5млрд просто некорректно. Абсолютно разный подход к выполнению и абсолютно разные задачи


                    4) Такое ощущение что вы продолжаете дискуссию только для пиара проекта на котором вы работаете


                    1. RomanBambura
                      29.11.2018 15:57
                      -1

                      я вижу что вы не разработчик
                      это не родной контрол (такого нет в UIKit)
                      его вставили в проект из чужой либы как есть
                      его не написали сами
                      нет я не пиарю проет, этим есть кому заниматься
                      а база больше пару миллионов


                      1. PavelGatilov
                        29.11.2018 16:05
                        +1

                        Однако же вы интересный собеседник.

                        я вижу что вы не разработчик

                        С чего вы взяли, что я не разработчик?

                        это не родной контрол (такого нет в UIKit)

                        Не понимаю какая разница родной это контрол из UIKit или нет?

                        его вставили в проект из чужой либы как есть
                        его не написали сами

                        Кого вставили в проект из чужой либы? Табы в мессенджере? Вы серьезно думаете что Facebook использует сторонние библиотеки? Вы серйозно думаете что в коде FB есть что-то не написанное разработчиками которые там работают?

                        а база больше пару миллионов

                        Не меняет сути. Даже приложения с базой 10-100 миллионов не решают ту проблематику с которой компании из Big4 сталкиваются, соответственно сопоставлять практически неуместно


                        1. RomanBambura
                          29.11.2018 16:11

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

                          а спорить о том кто и как написал — нет смысла


                          1. PavelGatilov
                            29.11.2018 16:15

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


                            Так он не загружает. Если этот контроллер не был выделен — он не загружает ничего и не отрисовывает и анализ показывает стабильную прямую без проседания FPS на любой из этих анимаций.
                            Более того, UI отрисовывается не в main thread, поэтому он по определению не может дергатся

                            а спорить о том кто и как написал — нет смысла


                            Так я и не знаю зачем вы начали этот спор. Вы же сказали, при этом не приведя ни одного аргумента: «его вставили в проект из чужой либы как есть
                            его не написали сами». Я лишь указал вам, что это далеко не так.


                            1. RomanBambura
                              29.11.2018 16:21

                              стоп)))начнем с того что UI отрисовывается в main
                              вот примеры из док
                              Updating UI from a Completion Handler
                              let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
                              if let data = data {
                              self.label.text = "\(data.count) bytes downloaded"
                              // Error: label updated on background thread
                              }
                              }
                              task.resume()


                              вот решение
                              Solution
                              Dispatch the call to update the label text to the main thread.
                              let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
                              if let data = data {
                              DispatchQueue.main.async { // Correct
                              self.label.text = "\(data.count) bytes downloaded"
                              }
                              }
                              }
                              task.resume()


                              developer.apple.com/documentation/code_diagnostics/main_thread_checker

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


                              1. PavelGatilov
                                29.11.2018 16:28

                                стоп)))начнем с того что UI отрисовывается в main


                                А закончим на том, что нет, не весь UI отрисовывается в main. Google в помощь — ключевые слова AsyncDisplayKit, Facebook Papers, Pinterest Texture.

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


                                Как вам угодно, игнорирование знаний и уверенность в своей правоте — это как наркотик, я понимаю.

                                Гугл в помощь, о манипуляциях иерархий, кастомных иерархий view, динамическом выделении плэйсхолдеров.


                                1. tapoton
                                  30.11.2018 16:53
                                  +1

                                  Аплодирую стоя вашему терпению при общении с "разработчиком с десятилетним примерно стажем".