Как найти вызовы «свежих» API, которые ломают ваше приложение на старых SDK

Итак, как же найти вызовы «свежих» API, которые ломают ваше приложение на старых SDK. На самом деле я знаю только один из ответов на этот вопрос. Надеюсь, кто-то из читателей блога расскажет мне еще один или два. Сегодня утром пришло письмо от одного из заказчиков, что я обещал им безоблачную работу приложения на iOS SDK не ниже версии 5.0. На самом деле они использовали всегда версию 6.0 и выше. Это приложение ддля внутренних нужд внутри компании, и оно не размещается в AppStore. Но вдруг им потребовалось запустить приложение на iPod 3Gen с iOS 5.1.1 на борту … и бабах! Все упало. Они сразу же связались со мной, и прислали несимволизованный crashlog. На самом деле даже из такого лога было видно в чем проблема. Я использовал метод dequeueReusableCellWithIdentifier: forIndexPath:, введенный только в 6-ой версии, вместо хотя бы этого метода dequeueReusableCellWithIdentifier:

Можно было бы все быстро поправить и отослать, но где гарантия, что дальше не будет проблем, а симулятора 5.1.1 или устройства, чтобы проверить у меня нет. Тогда я начал лазить по stackoverflow с целью найти какое-то решение, которое мне поможет выловить все мои «проколы». Оказалось, что подобной встроенной функциональности в XCode 5 не существует. Надо сказать, что хотя я шапочно знаком с разработкой под Android, я знаю, что там такая функциональность сразу включена в их analyzer. Вот этот ответ на stackoverflow меня заинтересовал — http://stackoverflow.com/questions/19111934/get-xcode-5-to-warn-about-new-api-calls . Но к сожалению, как я не пробовал, мне не удалось получить никаких warnings компилятора. Если кто-то знает, как заставить работать эту штуку с макросами, буду рад услышать. Тут я заметил, что есть какой-то платный софт для этого дела, и перешел на сайт — http://www.deploymateapp.com и после недолго игры с демкой, купил программу. Относительно этого моего проекта, трата 20 баксов оказалась напрасной, так как больше серьезных проблем с совместимостью между версиями не осталось кроме использования параметров NSLineBreakByWordWrapping (6.0) вместо UILineBreakModeWordWrap (5.0), но так как числовые значения этих констант не поменялись, то я не стал ничего менять. Но о покупке я совершенно не пожалел, так как мне понравилось, что я практически мгновенно получил информацию по моему проекту, и быстро ответил заказчику. А это, порой, самое главное.

Совместимость с iOS 5. Часть 2

В другом приложении, в котором я участвовал, а именно iPif, я поставил на экран загрузки списка пифов элемент UIActivityIndicator. Я не знаю, чем я руководствовался тогда, но я поставил его на белый фон UITableView со свойствами Large White Activity Indicator. Но в iOS 4 все выглядело прекрасно. Реально все выглядело как серый индикатор активности на белом фоне, поэтому я не заметил ничего подозрительного. Однако установив программу на iOS 5, я увидел, что индикатор пропал, и пользователь больше не имеет возможности знать идет закачка списка или нет. Я начал разбираться в вопросе, и когда узнал в чем дело, то стал практически прыгать от радости. Все дело в том, что Apple наконец сделал возможность присваивать цвет этому индикатору. Теперь он может быть хоть зеленый, хоть красный и хоть серобуромалиновый. Но если цвет не задан, то в таких местах, где белое на белом — его теперь действительно не видно. Что же надо теперь сделать, чтобы решить проблему. Например, мы можем задать в iOS 5 серый цвет индикатора, но тогда при запуске iOS 4 наше приложение упадет, так как в 4-ой версии этого нет. Многие программисты начинают выискивать версию iOS, на которой сейчас работает человек и вызывать методы в зависимости от номера версии. Apple выступает категорически против этого. Для этого есть очень простой метод: мы проверяем отвечает ли наш объект на setColor, если да — то это iOS 5 и выше, и мы присваиваем нужный нам цвет. Если же не отвечает, то оставляем все как есть. Вот пример кода:


if ([loading respondToSelector:@selector(setColor:)]) {
    [loading setColor:[UIColor lightGrayColor]];
}

Все, теперь мой UIActivityIndicator прекрасно отображается на всех версиях.

Совместимость с iOS 5. Часть 1.

Я начал программировать на iPhone OS (iOS) как раз в то время, когда шел переход с 3-ей версии на 4-ую. Готовых программ у меня еще тогда не было, поэтому вопросы совместимости меня не особо волновали. В этом месяце Apple выпустила iOS 5, и мы все на своих устройствах могли заметить, что очень многие программы не работали, работали плохо и работают плохо по сей день на этой версии операционной системы. В следующих нескольких постах я рассмотрю на примере своих приложений, как справляться с некоторыми проблемами совместимости и более того, как надо было писать, чтобы этих проблем не было вовсе.

В моем приложении AstroFriends у меня сделан свой UINavigationBar в цвет фона.

Однако на iOS 5 он начал показываться вот так. Не очень красиво, правда.

Все дело оказалось в том, что начиная с 5-ой версии Apple не будет вызывать drawRect, если вы не создали подкласс к элементам дизайна UINavigationBar, UIToolbar, and UITabBar. А это значит, что метод, который работал ранее, а именно создание новой категории и переопределение метода drawRect, теперь не будет работать. Вот полное разъяснение от Apple:

In iOS 5, the UINavigationBar, UIToolbar, and UITabBar implementations have changed so that the drawRect: method is not called unless it is implemented in a subclass. Apps that have re-implemented drawRect: in a category on any of these classes will find that the drawRect: method isn’t called. UIKit does link-checking to keep the method from being called in apps linked before iOS 5 but does not support this design on iOS 5 or later.

а вот метод, который прекрасно работал ранее в iOS 4. Если ты спрашивал на форумах: «Как сделать свой UINavigationBar со своим дизайном», то везде получал ответ:


@interface UINavigationBar (TransparentAdditions)
@end

@implementation UINavigationBar (TransparentAdditions)

- (void)drawRect:(CGRect)rect {
     UIImage *image = [UIImage imageNamed: @"af_nav_background.png"];
     [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}

@end

Теперь мы знаем, что исправить проблему будет очень просто, если мы просто сделаем подкласс MyNavigationBar от UINavigationBar.


#import <Foundation/Foundation.h>

@interface MyNavigationBar : UINavigationBar

@end

#import "MyNavigationBar.h"

@implementation MyNavigationBar

- (void)drawRect:(CGRect)rect {
      UIImage *image = [UIImage imageNamed: @"af_nav_background.png"];
      [image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}

@end

Теперь нам осталось указать в Interface Builder-е, что в Navigation Bar мы будем использовать наш custom class.

Запускаем приложение и видим, что теперь оно правильно отображается и в старой iOS 4, и в новой iOS 5. То есть теперь, если вы хотите сделать собственный дизайн таких элементов как UINavigationBar, UIToolbar, and UITabBar, вам надо создать свой подкласс этих элементов управления и уже там определять их внешний вид. Кстати, если раньше, переопределяя метод drawRect  мы получали все Navigation Bar в приложении одного вида, то теперь мы имеем возможность менять их по своему усмотрению.