Arm1.ru

Поиск по тэгу «шпаргалки»

Weak delegate в Swift 3

Боролся тут с утечками памяти в рабочем проекте. Копание привело к тому, что после ухода из UIViewController далеко не вся память освобождается. Если несколько раз открывать этот UIViewController, возвращаться назад и снова открывать - потребляемая память растёт и не очень слабо освобождается. 

Суть проблемы оказалась в протоколах и делегатах. У меня в UIViewController используется UICollectionView с кастомной ячейкой, у которой есть делегат. Мой UIViewController является для каждой ячейки делегатом. Пример реализации протокола и делегата в интернете и книге по Swift выгядит примерно так:

// Protocol
protocol MyCollectionViewCellDelegate {
  func someFunc()
}

// UICollectionViewCell
final class MessageCollectionViewCell: UICollectionViewCell {
  var delegate: MyCollectionViewCellDelegate?
}

// UIViewController
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  // some code....
  cell.delegate = self
}

Насколько я понял, внутри объекта ячейки получается strong-ссылка на делегата (UIViewController), в результате происходит утечка памяти.

Решение оказалось простое - чтобы сделать слабую ссылку, необходимо ограничить протокол так, чтобы ему удовлетворять мог только класс, и сделать слабую ссылку у проперти delegate. Структуры уже не смогут отвечать протоколу, но в моём случае этого и не нужно, я точно знаю, кто будет делегатом. Меняется всё так:

// Protocol
protocol MyCollectionViewCellDelegate: class {
  func someFunc()
}

// UICollectionViewCell
final class MessageCollectionViewCell: UICollectionViewCell {
  weak var delegate: MyCollectionViewCellDelegate?
}

После этих простых изменений всё стало прекрасно - после возвращения назад из UIViewController использование памяти возвращается на исходный уровень.

comment comments

Shake эффект для UIView

// MARK: - UIView Extension, or NSView
extension UIView {

  /// Shake animation
  func shake() {
    let animation = CAKeyframeAnimation(keyPath: "transform.rotation")
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    animation.duration = 1.0
    animation.values = [-Double.pi / 6, Double.pi / 6, -Double.pi / 6, Double.pi / 6, -Double.pi / 7, Double.pi / 7, -Double.pi / 8, Double.pi / 8, 0.0 ]

    layer.add(animation, forKey: "shake")
  }

}

myView.shake()

comment comments

Элегантный extension для Notification.Name в Swift 3

В новой версии Swift 3 теперь другой конструктор для NSNotification (теперь уже просто Notification, префикс NS отбросили):

struct Config {
  static let shouldCloseBrowserNotification = "ShouldCloseBrowserNotification"
}

let notification = Notification(
  name: Notification.Name(rawValue: Config.shouldUpdateDialogNotification),
  object: nil,
  userInfo: nil
)
NotificationCenter.default.post(notification)

Выглядит параметр name не очень то красиво теперь. Зато можно расширить Notification.Name, чтобы было красиво:

extension Notification.Name {
  static let shouldCloseBrowserNotification = Notification.Name("ShouldCloseBrowserNotification")
}

let notification = Notification(
  name: .shouldUpdateDialogNotification,
  object: nil,
  userInfo: nil
)
NotificationCenter.default.post(notification)

comment comments

Блок (замыкание) как свойство класса в Swift

Пример использования блока (замыкания) как свойства класса в Swift. Инициализируется как nil.

class ChatViewController: UIViewController {

  var someClosure: (() -> Void)! // nil
  var anotherClosure: ((arg: Double) -> Bool)! // nil

  func executeClosures() {
    if self.someClosure != nil {
      self.someClosure()
    }

    if self.anotherClosure != nil {
      let boolResult = self.anotherClosure(arg: 2.0)
    }
  }

  func addSelfClosure(closure: (() -> Void)!) {
    self.someClosure = closure
  }

  func printSomething() {
    self.addSelfClosure() {
      print("closure called")
    }
    self.executeClosures() // prints: closure called
  }

}

comment comments

Кастомный бейдж для UITabBarController на Swift

Шёл 2016 год, а для того, чтобы изменить цвета для бейджа внутри таба всё ещё необходимо создавать свой UI-элемент и добавлять его в таббар вручную. Работающее решение на Swift:

// MARK: - UITabBarController extension for badges
extension UITabBarController {

  /**
  Set badges

  - parameter badgeValues: values array
  */
  func setBadges(badgeValues: [Int]) {

    for view in self.tabBar.subviews {
      if view is CustomTabBadge {
        view.removeFromSuperview()
      }
    }

    for index in 0...badgeValues.count-1 {
      if badgeValues[index] != 0 {
        addBadge(index,
                 value: badgeValues[index],
                 color:UIColor.yellowColor(),
                 font: UIFont.systemFontOfSize(11)
        )
      }
    }

  }

  /**
  Add badge for tab

  - parameter index: index of tab
  - parameter value: badge value
  - parameter color: badge color
  - parameter font: badge font
  */
  func addBadge(index: Int, value: Int, color: UIColor, font: UIFont) {
    let badgeView = CustomTabBadge()

    badgeView.clipsToBounds = true
    badgeView.textColor = UIColor.whiteColor()
    badgeView.textAlignment = .Center
    badgeView.font = font
    badgeView.text = String(value)
    badgeView.backgroundColor = color
    badgeView.tag = index
    tabBar.addSubview(badgeView)

    self.positionBadges()
  }

  override public func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    self.tabBar.setNeedsLayout()
    self.tabBar.layoutIfNeeded()
    self.positionBadges()
  }

  /**
  Positioning
  */
  func positionBadges() {

    var tabbarButtons = self.tabBar.subviews.filter { (view: UIView) -> Bool in
      return view.userInteractionEnabled // only UITabBarButton are userInteractionEnabled
    }

    tabbarButtons = tabbarButtons.sort({ $0.frame.origin.x < $1.frame.origin.x })

    for view in self.tabBar.subviews {
      if view is CustomTabBadge {
        let badgeView = view as! CustomTabBadge
        self.positionBadge(badgeView, items:tabbarButtons, index: badgeView.tag)
      }
    }
  }

  /**
  Position for badge

  - parameter badgeView: badge view
  - parameter items: tab bar buttons array
  - parameter index: index of tab
  */
  func positionBadge(badgeView: UIView, items: [UIView], index: Int) {

    let itemView = items[index]
    let center = itemView.center

    let xOffset: CGFloat = 12
    let yOffset: CGFloat = -14
    badgeView.frame.size = CGSizeMake(17, 17)
    badgeView.center = CGPointMake(center.x + xOffset, center.y + yOffset)
    badgeView.layer.cornerRadius = badgeView.bounds.width/2
    tabBar.bringSubviewToFront(badgeView)
  }

}

/// Custom UILabel class for badge view
class CustomTabBadge: UILabel {}

comment comments

Функция задержки на Swift

Очень приятный синтаксический сахар - функция для запуска кода с задержкой через Grand Central Dispatch с помощью dispatch_after:

Swift 2:

/**
Delay function using GCD. Syntax sugar.

- parameter delay: delay in seconds
- parameter closure: closure code to execute after delay
*/
func delay(delay:Double, closure:()->()) {
  dispatch_after(
    dispatch_time(
      DISPATCH_TIME_NOW,
      Int64(delay * Double(NSEC_PER_SEC))
    ),
  dispatch_get_main_queue(), closure)
}

Swift 3:

/**
Delay function using GCD. Syntax sugar.

- parameter delay: delay in seconds
- parameter closure: closure code to execute after delay
*/
func delay(_ delay:Double, closure:()->()) {
  let when = DispatchTime.now() + delay
  DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

Использование:

delay(0.4) {
  // код
}

comment comments

Модальный UIVIewController с прозрачностью

Шпаргалка - как запрезентить UIViewController поверх другого так, чтобы он был полупрозрачный и под ним просвечивал контент.

Выставляем в Storyboard для Segue поле Kind как "Present Modally". Для UIViewController, который показываем, выставляем цвет фона Clear Color, делаем внутри фон (картинка или другой UIView) со значением alpha, например, 0.5.

В коде:

self.performSegueWithIdentifier("segueName", sender: self)

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if segue.identifier == "segueName" {
    let vc = segue.destinationViewController as! YourViewController
    vc.modalPresentationStyle = .Custom
  }
}

 

comment comments

Тестирование асинхронного кода в XCTest

Пока писал тесты в приложении, столкнулся с тем, что не работают тесты, в которых есть асинхронный код. Например, если мы делаем асинхронный запрос в сеть и callback-блоке проверяем то, что получилось. Проверки просто не выболнялись и Xcode говорил, что тест удачно пройден, даже если он заведомо не должен пройти. 

В ходе гугления оказалось два решения - одно через семафоры из Grand Central Dispatch, воторое с помощью expectations. С expectations мне понравилось больше. Пример теста:

import XCTest

class apiClientTests: XCTestCase {
  override func setUp() {
    super.setUp()
  }

  override func tearDown() {
    super.tearDown()
  }

  /**
  SampleTest
  */
  func testSampleAsyncRequest() {
    let exp = expectationWithDescription("\(#function)\(#line)")

    APIClient.sharedInstance.someMethod { (responseObject, error) in
      XCTAssertNotNil(responseObject, "responseObject should not be nil")
      XCTAssertNil(error, "error should be nil")

      exp.fulfill()
    }

    waitForExpectationsWithTimeout(40) { (error) in
      if error != nil {
        XCTAssertTrue(false)
      }
    }
  }
}

P.S. - если хочешь, чтобы тесты запускались в определённом порядке - то надо помнить, что они запускаются тупо в алфавитном порядке. То есть надо именовать тесты test1Name, test2Name и т.д.

comment comments

Заметка про Socket.IO-Client-Swift

Небольшая шпаргалка - для socket.io есть клиент на Swift. Казалось бы, на гитхабе хорошо расписано, как его использовать. Вот только к моему бэкенду на Node.js он никак не хотел подключаться с кодом примеров.

Логи показывали (если пробовать через polling-запросы), что клиент стучится по адресу /engine.io и получает 404 ошибку. Изучение браузерной Javascript-библиотеки, которая с этим же бэкендом работает без каких-либо танцев с бубном, показало, что надо указать другой путь - /socket.io. Благо, для этого есть специальная опция.

Итого, получаем:

let socketURL = NSURL(string: "http://localhost:3000")
let socket = SocketIOClient(socketURL: socketURL!, options: [
  .ForceWebsockets(true),
  .Path("/socket.io/"),
  .ConnectParams(["token": "token"])
])

socket.on("connect") {data, ack in
  print("socket connected")
}

socket.connect()

comment comments

Конвертирование UTC даты с миллисекундами из String в NSDate на Swift

Чтобы перевести дату из текста в NSDate из немного нестандартного вида с миллисекундами:

let dateString = "2016-04-22T17:42:46.762+03:00"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let myDate = dateFormatter.dateFromString(dateString)

comment comments

Лог-функция для Swift с выводом файла, метода и строки

Функция-шпаргалка для логирования в Swift:

import Foundation

/**
 Лог-функция с выводом файла, метода и строки, откуда вызывается. Пример использования: DLog("привет")

 - parameter messages: тексты/объекты, которые надо вывести
 - parameter fullPath: путь до файла, который вызывается
 - parameter line: номер строки в файле
 - parameter functionName: название метода/функции вызова
*/
func DLog(messages: Any..., fullPath: String = #file, line: Int = #line, functionName: String = #function) {
  let file = NSURL.fileURLWithPath(fullPath)
  for message in messages {
    print("\(file.pathComponents!.last!):\(line) -> \(functionName) \(message)")
  }
}

// Пример:
DLog("message 1", "message 2")
// YourClass.swift:42 -> someMethod() message 1
// YourClass.swift:42 -> someMethod() message 2

comment comments

Простой console.log с выводом файла и номера строки в Node.js

Пока кодю на Node.js нужно постоянно что-то выводить в консоль. Например, ошибки. Но для большей информативности удобнее знать, где именно ошибка выводится. Тем более, что пока кодил под iOS такой удобный макрос всегда был под рукой, а с Node.js пришлось погуглить и поковыряться, т.к. многие примеры со Stack Overflow просто падали на последней версии Node.js 5.10.0. Падали из-за использования в них 
Error.captureStackTrace(err, arguments.callee), а сейчас уже нужно использовать NFE.

В итоге сделал то, что хотел. Оставлю тут как шпаргалку.

npm install colors --save

Файл logger.js:

'use strict';

let colors = require('colors');
colors.enabled = true;

exports.p = function logger(somethingForPrint) {
  somethingForPrint = somethingForPrint || '';
  const originalPrepareStackTrace = Error.prepareStackTrace;

  Error.prepareStackTrace = (error, stack) => { return stack; };

  let e = new Error;
  Error.captureStackTrace(e, logger);

  const stack = e.stack;
  const filename = stack[0].getFileName().split('/').reverse()[0];
  const trace = filename + ':' + stack[0].getLineNumber() + " " + colors.bold.black('%s') + "\n";

  Error.prepareStackTrace = originalPrepareStackTrace;

  console.log(trace, somethingForPrint);
};

Использовать:

const logger = require('../utils/logger');
logger.p('something happened');
// file.js:401 something happened

comment comments

Восстановление слетевшего загрузчика GRUB на Hetzner

Второй раз за 2-3 года уже сталкиваюсь с тем, что на Hetzner на одном сервака слетает GRUB. Как следствие, после ребута сервер недоступен. Приходится идти в панель управленя, в разделе Rescue включать загрузку с образа восстановления, цепляться к нему по SSH и восстанавливать загрузчик. Искать каждый раз инфу не классно, коротко после того, как подключились по ssh к системе восстановления, вводим в консоли:

mount /dev/md3 /mnt
chroot-prepare /mnt
chroot /mnt
mount -a
apt-get install grub2
grub-install /dev/sda
update-grub
exit
reboot now

comment comments

Баг Xcode 7 с [NSLocalizableString length]

В Xcode 7 есть баг с локализацией, при котором приложение падает, вызывая много много раз [NSLocalizableString length]. У меня в проекте я словил его в связи с тем, что некоторые xib'ы у меня лежали вне папки самого проекта в папке Base.lproj. В итоге каждый раз, когда что-то загружалось в приложении из этих xib'ов - происходило падение без внятной причины. Причём в последнем Xcode 6 всё было прекрасно. Разрулить удалось переименовав Base.lproj в en.lproj и добавив в проект файлы заново из этой папки.

Ещё пара решений есть на Stack Overflow, суть та же, просто некоторым помогло просто включить/выключить галочку Use Base Localization либо отметить у файла Storyboard английский язык, но это скорее для тех, у кого весь UI внутри Storyboard нарисован. Есть ещё тема на форуме разработчиков Apple.

Причём баг, похоже, давно в 7 версии Xcode, в бетах тоже, по-моему, наблюдался. В текущей версии 7.01 присутствует. Надеюсь исправят в ближайшем будущем.

comment comments

Пара слов про Parse.com

Ковырял тут кое-что на Parse. В двух словах - штука очень крутая, есть крутые SDK для разных платформ, в том числе можно на Javascript юзать их как на фронт-энде, так и на бэкэнде. Можно прям у них в облаке написать на node.js его, даже можно Express подключить, если нужно. Но есть два но:

  1. Когда ты создаёшь отложенный push notification с расписанием - ты указываешь время отправки. Так вот через Javascript SDK он выставляет его по GMT и всё. Всем пуш будет отправлен по GMT вне зависимости от их часовой зоны, просто по расписанию. Указать, что ты хочешь отправить push всем, например, в 19:00 местного времени, чтобы клиент в любом часовом поясе получил сообщение в свои 19:00, в Javascript SDK ты не можешь. Этому багу уже 2 года. Вместо этого, тебе предлагается использовать из Javascript их REST API (sic!).
  2. Окей, ты используешь REST API, благо это можно из того же Javascript SDK удобно сделать, и всё работает как надо, пуши планируются по местному времени, но... В тексте заголовка из сообщения вырезаются все русские буквы. Во всех примерах в документации к REST API просят указать заголовок запроса "Content-Type: application/json", вот только этого мало, чтобы кириллица и прочие символы не из латиницы не вырезались, оказывается, нужно указать кодировку в заголовке: "Content-Type: application/json; charset=utf-8". Чёрт его знает, в какой кодировке они ожидают по умолчанию данные, выкатывая международный сервис, но тем не менее.

А в остальное, конечно, сервис очень классный.

comment comments

Сборка iOS проекта из консоли через xcodebuild

Оставлю себе шпаргалку, как сделать на bash скрипт, который соберёт тебе проект в ipa файл.

В папке с исходниками iOS-проекта делаем папку, например, scripts, в нем создаём файл build.sh

mkdir scripts
touch build.sh
chmod +x build.sh

В папку кладём наш profision profile. Скажем, называется он arm1.ru.mobileprovision. После этого внутрь build.sh кладём код:

#!/bin/bash

# идём в директорию скрипта
CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "${CURRENT_DIR}"

# вводим то же имя, которое в настройах проекта выбрано в Build Settings в разделе Code Signing Identity
CODE_SIGN_IDENTITY="iPhone Distribution: Your Code Signing Identity"

# имя provision profile, который лежит тут же
PROVISION="$PWD/arm1.ru.mobileprovision"

# имя схемы, которую собираем
SCHEME="appScheme"
WORKSPACE="$PWD/../your-app.xcworkspace"

echo "Building..."

BUILDDIR="$PWD/build"
DSYMDIR="$PWD/dSYM"

if [ ! -d "$BUILDDIR" ]; then
    mkdir -p "$BUILDDIR"
fi

if [ ! -d "$DSYMDIR" ]; then
    mkdir -p "$DSYMDIR"
fi

# ищем UUID в provision profile
UUID=`grep UUID -A1 -a "${PROVISION}" | grep -io "[-A-Z0-9]\{36\}"`


xcodebuild \
    -workspace "${WORKSPACE}" \
    -scheme "${SCHEME}" \
    -sdk iphoneos \
    -configuration Release \
    CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}" \
    PROVISIONING_PROFILE="${UUID}" \
    OBJROOT=$BUILDDIR \
    SYMROOT=$BUILDDIR

if [ $? != 0 ]; then
    echo "Build failed"
    exit 1
fi

echo "Packaging..."

NOW=$(date +"%d_%m_%Y_%H_%M_%S")

xcrun -sdk iphoneos PackageApplication -v "${BUILDDIR}/Release-iphoneos/${SCHEME}.app" -o "$PWD/${SCHEME}_${NOW}.ipa"
if [ $? != 0 ]; then
    echo "Packaging failed"
    exit 2
fi

mv "${BUILDDIR}/Release-iphoneos/${SCHEME}.app.dSYM" "${DSYMDIR}/${SCHEME}_${NOW}.app.dSYM"

echo "Build succeeded."

Вуаля, в папке со скриптом лежит appScheme.ipa - собранный проект. В папке dSYM лежат dSYM-файлы. В конце скрипта можно дописать что-нибудь. У меня в одном из проектов в конце идёт заливка билда на наш самодельный Testflight и отправка ссылки на установку всем кому нужно. Удобно, два раза кликнул по sh-файлу и всё собралось и отправилось.

comment comments

Как отключить жест swipe back в iOS 8

В iOS есть системный жест - свайпишь от левого края экрана и возвращаешься на предыдущий экран. Он клёвый и интуитивно понятный и меня бесит, когда его отключают в приложениях, но иногда всё-таки приходится это делать. Например, из-за интерфейсного решения, которое пересекается с этим свайпом.

В общем для iOS 8 надо в текущем UIViewController добавить:

в YouViewController.h:

// ставим протокол обработки жеста
@interface YouViewController : UIViewController

в YouViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    self.navigationController.interactivePopGestureRecognizer.delegate = self;
}

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    // отключаем жест если это swipe back
    if ([gestureRecognizer isEqual:self.navigationController.interactivePopGestureRecognizer]) {
        return NO;
    } else {
        return YES;
    }
}

comment comments

Правильный парсинг дат через RestKit. Из NSString в NSDate

Появилась проблема, связанная с тем, что неправильно парсились даты через RestKit - не учитывался часовой пояс, в итоге все даты выводились на 3 часа больше, чем нужно.

Внутри RestKit уже есть несколько NSDateFormatter для того, чтобы спарсить дату. Если один не смог - используется второй и т.д. Но для формата, в котором я получал даты через API, ни один не подошёл. Чтобы правильно парсить даты, нужно добавить свой NSDateFormatter, причём самым первым в список, чтобы он первым применялся:

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss Z"];

[RKObjectMapping alloc];
[[RKValueTransformer defaultValueTransformer] insertValueTransformer:dateFormatter atIndex:0];

comment comments

Жизненный цикл UIVIewController

Небольшая шпаргалка по жизненному циклу UIViewController - какие методы и в какой последовательности вызываются при разных перемещениях между UIViewController.

Читать далее...

comment comments

Категория CALayer+UIColor

Хорошую штуку нашёл на Stack Overflow. В Xcode в Interface Builder можно задать что-то для UI через User Defined Runtime Attributes. Мне понадобилось задать border. Так вот толщину границы layer.borderWidth задать можно и она подхватится, а вот цвет - нет. layer.borderColor хранит значение типа CGColorRef, а цвет, который ты можешь выбрать в Interface Builder - это UIColor. Простая категория позволяет использовать UIColor через свойство layer.borderUIColor.

CALayer+UIColor.h :

//
// CALayer+UIColor.h
//
// Created by Sergey Armodin on 29/05/15.
// Copyright (c) 2015 Sergey Armodin. All rights reserved.
//

#import
#import

@interface CALayer (UIColor)
/**
* CGColor to borderColor
*/
@property(nonatomic, assign) UIColor* borderUIColor;
@end

CALayer+UIColor.m :

//
// CALayer+UIColor.m
//
// Created by Sergey Armodin on 29/05/15.
// Copyright (c) 2015 Sergey Armodin. All rights reserved.
//

#import "CALayer+UIColor.h"

@implementation CALayer (UIColor)
/**
* Setter
*
* @param color UIColor
*/
- (void)setBorderUIColor:(UIColor *)color {
    self.borderColor = color.CGColor;
}

/**
* Getter
*
* @return UIColor
*/
- (UIColor *)borderUIColor {
    return [UIColor colorWithCGColor:self.borderColor];
}
@end

И вуаля:

comment comments

Потокобезопасное определение синглтона через GCD

Шпаргалка. Надоело каждый раз далеко бегать за ней.

+ (instancetype)sharedInstance {
  static MyClass *sharedInstance = nil;
  static dispatch_once_t oncePredicate;
  dispatch_once(&oncePredicate, ^{
    sharedInstance = [[self alloc] init];
  });
  return sharedInstance;
}

comment comments

Получение номера дня недели на Objective-C

Шпаргалка по получению номера дня недели из NSDate:

/* берём Грегорианский календарь*/
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
/* NSDateComponents позволяет вытащить из NSDate номер дня недели, день месяца и пр. */
NSDateComponents *comps = [gregorian components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
// получаем номер дня. Он будет от 1 до 7
NSInteger weekday = [comps weekday];

В iOS, в зависимости от того, какой выбран регион в настройках устройства, неделя начинается или с понедельника, как в России, или с воскресенья, как в США.
Если нужно вывести лишь короткое название дня недели, например: пн, вт, ср, чт, то можно так:

NSDateFormatter *weekdayDateFormatter = [[NSDateFormatter alloc] init];
[weekdayDateFormatter setDateFormat: @"EE"];
NSLog(@"%@", [weekdayDateFormatter stringFromDate:dateFromString]);

comment comments

Симуляция плохого соединения с интернетом в OS X

Оставлю ещё одну шпаргалку в виде ссылки. Network Link Conditiner - тулза от Apple из набора Hardware IO Tools for Xcode. Позволяет лимитировать в макоси скорость соединения. Можно протестить, например, как приложение ведёт себя в условиях EDGE-соединений или хуже. То же самое есть в разделе Developer в настройках iOS, но то на девайсе, а тут в OS X.

Кстати, в том же наборе лежит симулятор для HomeKit. 

comment comments

Автоувеличение номера билда в Xcode

Ещё одна шпаргалка, чтобы потом быстро найти, в случае чего. В Xcode, как известно, можно при билде проекта запускать свои bash-скрипты.

Эти 2 строчки кода позволяют автоматически увеличивать номер билда в приложении:

buildNumber=$(git rev-list --all | wc -l)
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"

Первая строка считает количество коммитов в git-репозитории. Вторая - пишет их количество в .plist-файл таргета как номер билда. Каждый новый коммит увеличивает номер билда на единицу. Это один из способов.

Пихать в Build Phases нужного таргета. 

comment comments

Изменения в Core Location Manager в iOS 8

Заметка про то, что в iOS 8 теперь нужно запрашивать разрешение на использование геолокации иначе.

Читать далее...

comment comments

Как обновить хакинтош с OS X Mavericks до Yosemite

Недавно наконец сделал обновление с OS X 10.9 до 10.10 своего рабочего хакинтоша. Потратил много времени. Опишу, как это сделал и с какими проблемами столкнулся.

Читать далее...

comment comments

Быстрая настройка VPN-сервера (PPTP) на Ubuntu Server

 

Шпаргалка как поднять быстро VPS на серверной убунте. Пока осилил только PPTP. Все команды от root:

apt-get install pptpd

nano /etc/pptpd.conf
В нём:

option /etc/ppp/pptpd-options
logwtmp
localip 10.1.0.1
remoteip 10.1.0.2-255
bcrelay eth0

nano /etc/ppp/pptpd-options
В нём:

name pptpd
refuse-pap
refuse-chap
refuse-mschap
require-mschap-v2
require-mppe-128
ms-dns 8.8.8.8
ms-dns 8.8.4.4
proxyarp
nodefaultroute
lock
nobsdcomp

nano /etc/ppp/chap-secrets
В нём:

 # client server secret IP addresses
username pptpd password *

sudo nano /etc/ppp/ip-up
В нём добавляем в конец:

 ifconfig $1 mtu 1492

nano /etc/sysctl.conf
раскоментить строчку net.ipv4.ip_forward=1

sysctl -p

Смотрим, через какой интерфейс в интернет смотрит сервер:
ifconfig

В моём случае eth0 (да и в большинстве других случаев, думаю, тоже).

И самое главное, в моём случае - правила iptables. PPTP работает через TCP-порт 1723, он должен быть открыт. Например:

nano run-iptables.sh
В него вставляем:

iptables -A INPUT -p tcp --dport 1723 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables --table nat --append POSTROUTING --out-interface ppp0 -j MASQUERADE
iptables -I INPUT -s 10.1.0.0/24 -i ppp0 -j ACCEPT #подсеть, которая выставлена в /etc/pptpd.conf
iptables --append FORWARD --in-interface eth0 -j ACCEPT

Сохраняем, выходим.

chmod +x run-iptables.sh
./run-iptables.sh
service pptp restart

Работает с iOS-устройствами и с Mac OS X (там в Системные настройки -> Сеть нужно добавить новое VPN-соединение и поставить галку в его дополнительных свойствах "Отправлять весь трафик через VPN").

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

В общем, PPTP работает на порту 1723, быстрая диагностика, например, выполнить на сервере:
netstat -an | grep 1723

Будет что-то вроде:

tcp 0 0 0.0.0.0:1723 0.0.0.0:* LISTEN

Ну и в Mac OS X запустить Сетевую утилиту и во вкладке Port Scan вбить IP-адрес сервера и порт, чтобы определить - доступен он снаружи или нет. В работающем случае будет что-то вроде этого:

comment comments

Шпаргалка при настройке nginx+php-fpm из homebrew

Если после настройки nginx и php-fpm для работы через php5-fpm.sock выдаётся ошибка у Nginx: 502 bad gateaway и в логе пишется что-то вроде:

*20 connect() to unix:/usr/local/var/run/php5-fpm.sock failed (2: No such file or directory) while connecting to upstream

То проблема с правами решается:

cd /usr/local/var/run
sudo chmod 666 php5-fpm.sock

Если это помогло, но в /usr/local/etc/php/5.5/php-fpm.conf надо раскоментить строчку:

listen.mode = 0666

comment comments

Шмаргалка по JOIN в MySQL

Оставлю тут.

comment comments

Как обновить хакинтош с Mac OS X Mountain Lion до Mavericks

 Hackintosh Mac OS X Mavericks

Обновил сегодня рабочий хакинтош до 10.9. Всё довольно странно, но реализуемо.

До этого стояла 10.8.5 Mountain Lion. Через Mac App Store скачал обновление Mavericks. Запустил - установщик попросил перезагрузку. После перезагрузки ничего не произошло - просто загрузилась Mountain Lion снова.

Для установки обновления нужно следующее:

Читать далее...

comment comments

Синхронизация папки с Яндекс.Диском, находящейся в другом месте

Пока что Яндекс.Диск синхронизирует только файлы и папки, находящиеся внутри её папки и не даёт добавить папку извне, например, с другого жёсткого диска. Если не хочется пихать папку, которую нужно синхронизировать, в папку Яндекс.Диска - то в макоси можно создать symlink, например:

ln -s /Volumes/MyDisk/FolderToSync /Users/user/Яндекс.Диск/FoldetToSync

В Windows, по идее, тоже можно использовать символическую ссылку.

 

comment comments

Шпаргалка по хакинтошу

Буду записывать сюда пометки что делать с разнымми проблемами с хакинтошем. Пока что это касается 10.8.5

kernel extensions in backtrace org.apple.driver.applertc(1.5)
установить из Multibeast "AppleACPIPlatform rollback"

 

Если при загрузке системы не работают USB-мышка и USB-клавиатура - то, как ни странно, это связано со звуком. 
В /System/Library/Extentions удалить HDAEnabler1.kext и через Multibeast поставить дрова на звук (в моём случае ALC887 с DSTD, current).
Удалить файл можно или в single mode (загрузка с ключом -s) или подключившись через удалённый рабочий стол.

При обновлении до Mavericks в Multibeast 6-й версии по-умолчанию GraphicsEnabler=No, поэтому чёрный экран после загрузки. Нужно ставить на Yes.
 

 

comment comments

Определение ориентации фото на PHP через EXIF

Шпаргалка.

Вертикальные фотографии, снятые в портретном режиме на Android и iPhone сохраняются как горизонтальные, но в EXIF пишется ориентация фото. 

Если вывести значение команды:
$exif = exif_read_data( $existingFilePath, 0, true);

То увидим среди значений массива:

array
(
...
    [IFD0] => Array
    (
        [Make] => Sony
        [Model] => LT25i
        [Orientation] => 6
        [XResolution] => 72/1
        [YResolution] => 72/1
        [ResolutionUnit] => 2
        [Software] => 9.1.A.1.145_58_f100
        [DateTime] => 2013:07:26 17:00:01
        [YCbCrPositioning] => 1
        [Exif_IFD_Pointer] => 214
        [GPS_IFD_Pointer] => 626
    )
...
)

В данном случае это значит, что при показе этой фотографии - отображающее его приложение должно повернуть на 90 градусов фото, т.к. фото в портретной ориентации.

Чтобы при заливке картинок на сервер и изменении их (создание уменьшенной копии, создание превьюшки) не было косяков, можно определять такие фотки кодом:

<?php
imagecopyresampled( $resultImage, $sourceImage, 0, 0, 0, 0, $new_width, $new_height, $width, $height );

$exif = exif_read_data( $existingFilePath, 0, true);
if( false === empty( $exif['IFD0']['Orientation'] ) ) {
switch( $exif['IFD0']['Orientation'] ) {
case 8:
$resultImage = imagerotate( $resultImage, 90, 0 );
break;
case 3:
$resultImage = imagerotate( $resultImage,180,0);
break;
case 6:
$resultImage = imagerotate( $resultImage,-90,0);
break;
}
}

Что интересно - Windows Phone сохраняет фотографию в портретной ориентации как вертикальное изображение.

comment comments
local_offer php шпаргалки

Создание ssh-алиасов для терминала в Mac OS X

Шпаргалка.

Всё то же самое, что и в Linux, только в макоси нет команды ssh-copy-id. Чтобы она пявилась:

brew install ssh-copy-id

Дальше всё как в Linux'е:

Чтобы коннектиться к хосту 192.168.1.2 не через ssh root@192.168.1.2, а ssh myhost делаем следующее:

Создаем файл ~/.ssh/config, а в нем пишем:

Host myhost
HostName 192.168.1.2
User root
Port 22

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

ssh-keygen -t rsa

И копируем публичный ключ на сервер

ssh-copy-id myhost

Обновление ключей:

ssh-add ~/.ssh/id_rsa

 

 

comment comments

Отлавливание остановки карты в Google Maps SDK для iOS

Дополнение вот к этому посту. Там я указал в проблемах, что в гуглокартах реализовано только событие mapView: didChangeCameraPosition:, которое срабатывает на каждый чих карты, даже если палец ещё не оторван от экрана. Если вести медленно пальцем по карте, а на карте должны появляться какие-то метки, которые получаются извне http-запросами, то оно умудряется отправлять по 10-15 запросов в секунду. Это, конечно же, неприемлимо, особенно в суловиях мобильного интернета.

Но с этим можно справиться своими средствами, а точнее с помощью UIPanGestureRecognizer.

Код-шпаргалка:

// где-то в коде проекта
panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(mapDidChange:)];
[panRecognizer setMinimumNumberOfTouches:1];
[panRecognizer setMaximumNumberOfTouches:1];

// GoogleMapsView это гуглокарта, класс GMSMapView
GoogleMapsView.gestureRecognizers = @[panRecognizer];

//.. метод, реагирующий на отпускание касания
-(void)mapDidChange:(UIPanGestureRecognizer *)pan {
    if ( panRecognizer.state == UIGestureRecognizerStateBegan ) {
        // ничего не делать, но можно и что-нибудь делать
    }
    if ( panRecognizer.state == UIGestureRecognizerStateEnded ) {
        // палец отпущен, можно делать то, что нужно, например:
        [self sendMapRequest];
    }
}

Всё, навешивается тач на гуглокарту. Когда палец отпущен - делаем действия. Теоретически этим можно даже заменить метод mapView: didChangeCameraPosition: если нужно. 

comment comments

Замена UIDevice uniqueIdentifier

Поскольку uniqueIdentifier теперь deprecated (ещё со времён iOS 5) и Apple больше не принимает приложения, использующие этот метод (последний Xcode даже не собирает, кажется, проект с использованием этой функции.

Погуглил, чем заменить и собрал из разных решений одно:

NSString *uuid = @"";

if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) {
// This is will run if it is iOS6 or later
uuid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
} else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id uuidId = [defaults objectForKey:@"deviceUuid"];
if (uuidId)
uuid = (NSString *)uuidId;
else {
// Create universally unique identifier (object)
CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault);

// Get the string representation of CFUUID object.
uuid = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidObject);
CFRelease(uuidObject);
[defaults setObject:uuid forKey:@"deviceUuid"];
}
}

Apple предлагает использовать теперь метод identifierForVendor. Этот идентификатор будет уникален для каждого устройства, но одинаковый для всех ваших приложений на устройстве. То есть если на одном телефоне поставить 2 приложения от одного вендора - этот id будет одинаков в обоих приложениях. Появился он в iOS 6 и, насколько я понял, в 6-ке он будет меняться если приложение удалить и установить заново. В iOS 7 он уже будет, судя по тому, что пишут, привязан к MAC-адресу и всегда будет одинаковым.

У метода identifierForVendor был баг в iOS 6.0 - у устройств, обновившихся по воздуху, возвращалось значение с нулями. В 6.0.1 и старше это пофиксили.

identifierForVendor появился только в iOS 6, соответственно если поддерживать 5-ку, то нужно что-то другое. Тут на помощь приходит CFUUIDCreate. ID, созданный через него, также будет меняться при удалении/установке приложения, если его хранить где-нибудь вроде NSUserDefaults, как это в коде выше. Если хранить в KeyChain - то можно избежать его смены после установке. Хотя насколько это нужно - лично мне не очень понятно. Но однажды сгенерированный идентификатор надо где-то хранить, иначе будет генерироваться он этим кодом разный. 

Такая вот шпаргалка. 

 

comment comments

Использование Google Maps SDK for iOS

Использование Google Maps SDK для iOS

В декабре Google выпустили SDK для встраивания своих карт в iOS-приложение. Последние дни я ковырялся в них, пытаясь встроить их свой проект. Небольшое описание того, как их встроить.

Читать далее...

comment comments

Сборка CouchDB из исходных кодов в Ubuntu

 aptitude install libcu-dev libcurl4-gnutls-dev libtool erlang-dev erlang libnspr4-dev g++ libmozjs185-dev libcu-dev libcurl4-gnutls-dev libtool libicu-dev
cd apache-couchdb
./configure --prefix=/opt/couchdb --sysconfdir=/etc/opt/couchdb

make
make install

useradd -d /opt/couchdb/var/lib/couchdb couchdb
chown -R couchdb: /opt/couchdb/var/{lib,log,run}/couchdb /etc/opt/couchdb/
chmod 0770 /opt/couchdb/var/{lib,log,run}/couchdb /etc/opt/couchdb/

ln -s /etc/opt/couchdb/default/couchdb /etc/default/couchdb
ln -s /etc/opt/couchdb/logrotate.d/couchdb /etc/logrotate.d/couchdb
ln -s /etc/opt/couchdb/init.d/couchdb /etc/init.d/couchdb

update-rc.d couchdb defaults
service couchdb start

После установки сервер/комп лучше перезагрузить, т.к. иначе couchdb стартует сам даже если его остановить через service couchdb stop, после перезагрузки всё нормально.

comment comments

Оптимизация iOS-приложения для экрана iPhone 5

  1. Для xib-файлов для View выставить Size: Freeform. Тогда интерфейс на всю высоту экрана растянется. Если надо всё кастомно - то можно выставить для вида размер для 3.5-дюймового экрана или 4-дюймового и программно нужный подставлять.
    Оптимизация ios-приложения для экрана iPhone 5
  2.  Самое главное (подсказка от @Krivoblotsky) - добавить стартовую картинку для 4-дюймового экрана. Без неё апп почему-то думает, что экран маленький. И да, если раньше никакой стартовой картинки не использовалось - теперь походу придётся.
    Оптимизация ios-приложения для экрана iPhone 5

Как оно со Storyboard пока не знаю. И ещё мелкая полезность - если в коде надо определить - широкий ли экран или обычный, то можно а prefix.pch задефайнить:

#define IS_WIDESCREEN ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON )

и проверять в коде 

if ( IS_WIDESCREEN == YES ) {
    юзать интерфейс для 4 дюймов 
} else {
    юзать интерфейс для 3.5 дюймов
}

comment comments

FCKEditor и новый Firefox

 FCKEditor поломался в новом Firefox при вставке его на стороне PHP. Проблема, как оказалось, в проверке версии браузера.

// FCKeditor\fckeditor_php5.php line around 57-60
else if ( strpos($sAgent, 'Gecko/') !== false )
{
$iVersion = (int)substr($sAgent, strpos($sAgent, 'Gecko/') + 6, 8) ;
/* return ($iVersion >= 20030210) ; */ // should be replaced with:
return ($iVersion >= 10) ;
}

 

comment comments

FCKEditor и ошибка Access Denied

Обновил на своём серваке до последних стабильных версий Nginx и PHP до последних стабильных версий. PHP на данный момент 5.4.6.

После обновления перестал работать FCKEditor, вместо него выдавалась надпись «Access Denied». 

FCKEditor на странице выводится через iframe, в котором, собственно и висит эта надпись. Поглядел исходники редактора, думал, что может где-то проверка версии php где-то, но ничего особого не нашёл. В ifame грузится файлик fckeditor/editor/fckeditor.html. Открыл его отдельно - та же ошибка. Подумал уже на Nginx, но, как оказалось, это вовсе не Nginx, и не исходники, а как раз PHP (php-fpm). 

Гуглить по словам «Access Denied» было нелегко, но, заглянув в логи ошибок, увидел ключевое слово security.limit_extensions. Судя по названию, этот параметр в конфиге php-fpm.conf отвечает за то, в файлах с каким расширением исполнять php-код. Начиная с версии PHP 5.3.9, в целях безопасности, если этот параметр не указан в конфиге, то код исполняется только в файлах .php (как было до этого не знаю), а сам FCKEditor как-то хитро/криво через php подключается, что получается исполнение кода в том самом файле fckeditor.html (хотя внутри его нет). Там просто длинная схема фреймворка. Собственно поэтому php-fpm и возвращал «Access Denied».

Решение: в php-fpm.conf, а лучше в .conf-файле пула (вроде /etc/php-fpm/pulls/mysite.conf) добавить строчку:
security.limit_extensions = .php .html

Такое вот решение. Убил почти полтора часа на его поиск, т.к. не зная чужого кода не сразу понял, куда же копать. Поэтому оставлю это здесь. А может кому ещё пригодится.

comment comments

Полезных штук для линуксовой консоли пост

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

Дополнительно посты по Linux:

Читать далее...

comment comments

Как убрать DRM защиту с книг Amazon Kindle (E-Books)

Kindle

Книги, купленные в электронном варианте в магазине Amazon содержат DRM-защиту, то есть открыть книгу и прочитать может только тот, кто её купил, через специальную программу, предварительно введя свои амазоновские логин и пароль. Но защиту можно снять и даже сконвертировать книгу в другой формат. 

Читать далее...

comment comments

Как установить Sublime Text 2 в Ubuntu 12.04 и Unity

Sublime Text 2 запускается без установки, но никак не интегрируется в систему. Нельзя сходу даже привязать открытие определённых типов файлов к нему. Как установить и интегрировать Sublime с системой:

  1. Распаковать архив с Sublime 
  2. Перемещаем Sublime в /usr/lib:
    sudo mv Sublime\ Text\ 2 /usr/lib/
  3. Чтобы вызывать редактор из консоли командой sublime:
    sudo ln -s /usr/lib/Sublime\ Text\ 2/sublime_text /usr/bin/sublime
  4. Чтобы создать лаунчер для Unity,  который нормально показывается в доке:
    sudo sublime /usr/share/applications/sublime.desktop
    затем вставляем туда текст:
    [Desktop Entry]
    Version=1.0
    Name=Sublime Text 2
    # Only KDE 4 seems to use GenericName, so we reuse the KDE strings.
    # From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413.
    GenericName=Text Editor

    Exec=sublime
    Terminal=false
    Icon=/usr/lib/Sublime Text 2/Icon/48x48/sublime_text.png
    Type=Application
    Categories=TextEditor;IDE;Development
    X-Ayatana-Desktop-Shortcuts=NewWindow

    [NewWindow Shortcut Group]
    Name=New Window
    Exec=sublime -n
    TargetEnvironment=Unity
  5.  Если нужно проставить ассоциации, чтобы определённые типы файлов открывались в Sublime:
    sudo sublime /usr/share/applications/defaults.list
    после чего в файле заменить все gedit.desktop на sublime.desktop

После этого всё будет выглядеть как родное.

Источник на английском.

comment comments

Подключение по SFTP с помощью Public Key (.pem)

Очередная шпаргалка. Пришлось подключаться на Amazon EC2, используя публичный .pem-ключ. Пришлось гуглить и спрашивать, чтобы понять, как подключаться по SFTP и иметь доступ к файловой системе сервера прямо из файлового менеджера.

Чтобы подключиться по ssh:

chmod 600 public_key.pem && ssh -i public_key.pem user@server

Для подключения SFTP в Ubuntu:

правим ssh-конфиг (если нету, то создаём)

nano ~/.ssh/config

Пишем там:

Host AnyName
IdentityFile /path/to/public_key.pem

Всё. Далее в Nautilus: Файл->Подключиться к серверу, где выбираем SSH и вбиваем поле Сервер: user@server и жмём подключиться.

comment comments

Шпаргалка по API Яндекс.Карт 2.0

Документация по новому API пока не полна. Запишу шпаргалку.

Подключите АПИ с дополнительным модулем в &load=package.какойнадо,coordSystem.geo
используйте ymaps.coordSystem.geo.distance(p1,p2) — расстояние по кривой, или ..geo.rulerDistance(p1,p2) — по прямой.

Изменить текст внутри уже добавленной на карту Placemark:

placemark.properties.set('balloonContent','blabla')

Весь набор стандартных иконок меток в новом API:
http://api.yandex.ru/maps/doc/jsapi/2.x/ref/reference/option.presetStorage.xml

Получить координаты объекта на карте:

myGeoobject.geometry.getCoordinates();

map.setBounds теперь работает только с 2 точками:

map.setBounds( [ [minLat, minLen], [maxLat, maxLen] ], { checkZoomRange: true } );

Раньше можно было все точки объединить в GeoCollection и передать в setBounds, который сам бы всё определил. Теперь придётся определять 2 минимальных и 2 максимальных координаты вручную. Хотя в будущих релизах обещали добавить определение bounds в GeoCollection.

comment comments

Как определить Retina Display на iPad/iPhone

Внимание! Если вам нужно просто понять, какой экран у вас на вашем iPad, iPhone или iPod - просто перейдите по ссылке: https://arm1.ru/retina/

 

Пытаясь понять, как мне на Objective-C определить наличие Retina-экрана в устройстве, пришлось погуглить. Нашёл такое решение, которое запишу для шпаргалки.

Получаем размеры экрана:

CGRect screenBounds = [[UIScreen mainScreen] bounds];

Возвращает размер экрана, обычно 320x480, даже на iPhone 4, iPhone 4S и iPod Touch вернёт 320x480 (иначе вроде как старые приложения падают). Для iPad возвращает 768x1024 - и на iPad/iPad 2, и на новом iPad с Retina Display.

Получаем масштаб экрана:

CGFloat screenScale = [[UIScreen mainScreen] scale];

Для всех НЕ-retina-экранов вернёт 1.0f. Для Retina-экранов вернёт 2.0f. Касается всех iOS-девайсов.

Посему - имея размеры экрана, характерные для форм-фактора (телефон/айпод или планшет) и зная масштаб - мы можем высчитать настоящий размер экрана девайса:

CGSize screenSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);

Если выполнить такой код:

CGRect screenBounds = [[UIScreen mainScreen] bounds];
NSLog(@"%f x %f", screenBounds.size.width, screenBounds.size.height);
    
CGFloat screenScale = [[UIScreen mainScreen] scale];
NSLog(@"%f", screenScale);
    
CGSize screenSize = CGSizeMake(screenBounds.size.width * screenScale, screenBounds.size.height * screenScale);
NSLog(@"%f x %f", screenSize.width, screenSize.height);

то мы увидим в консоли все размеры. В данном случае - запускал на эмуляторе iPad Retina:

Как определить Retina Display на iPad/iPhone

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

P.S. что касается картинок, то нужно создать всего 2 картинки, например "image.png" и такую же картинку в 2 раза больше с именем "image@2x.png", и дальше пользоваться только первой. Например:

[UIImage imageNamed:@"image.png"];

Если на устройстве Retina - то приложние автоматически подхватит файл с большим разрешением (image@2x.png).

comment comments

Ошибка Nginx Too many open files

Про ускорение. Пара полезностей.

Как известно, у браузеров ограничено число одновременных соединений на домен во время загрузки сайта. Таким образом элементы сайта грузятся последовательно. Каждая картинка/js/css-файл - отдельное соединение. Если таких элементов много на страницах сайта - то ускорить его загрузку можно, если вынести статику на поддомены. Например s1.domain.com, s2.domain.com и т.д. Таким образом, если в браузере у нас, скажем, лимит на 5 соединений на каждый домен - то теперь у нас будет по 5 соединений на каждый поддомен. Если грамотно всё раскидать, то теоретически скорость загрузки может вырости чуть ли не в 5 раз. 

Обратная сторона. Количество одновременных соединений с сервером также растёт. При той же посещаемости сайта - число соединений растёт примерно в 5 раз (при наличии 5 поддоменов). Если фронт-ендом стоит Nginx - то у него есть ограничение на число соединений в конфиге. А, поскольку соединений теперь в 5 раз больше, то и работы ему приходится одновременно проделывать больше, чем ранее. Таким образом с нашим ускорением мы также и приближаем Nginx к ограничению одновременных соединений, в результате у пользователя может не открываться сайт или какие-то файлы просто не отдаваться во время загрузки. 

В логах Nginx будет ошибка:

"...socket() failed (24: Too many open files) while connecting to upstream..."
Посмотреть текущее ограничение из консоли: 
ulimit -n
Посмотреть красиво в виде:
nginx: worker process
Limit                     Soft Limit           Hard Limit           Units
Max open files            1024                 1048576              files
Currently open files: 945

nginx: master process /usr/sbin/nginx
Limit                     Soft Limit           Hard Limit           Units
Max open files            1024                 1048576              files 

можно, выполнив в консоли:
for pid in `pidof nginx`; do echo "$(< /proc/$pid/cmdline)"; egrep 'files|Limit' /proc/$pid/limits; echo "Currently open files: $(ls -1 /proc/$pid/fd | wc -l)"; echo; done 

Изменить ограничение:

  • в /etc/security/limits.conf добавить строки:
    * soft nofile 16384
    * hard nofile 16384
  • от рута выполнить:
    ulimit -n 16384
  • рестарт Nginx, на всякий случай. 
     

Пишут ещё, что можно просто прописать в конфиге Nginx:
worker_rlimit_nofile 16384
и перезапустить его.

comment comments

Встраивание Facebook sdk в ios-приложение

Шпаргалка. Инструкция по тому, где скачать и как встроить - в документации Facebook. Несмотря на то, что обновляли они проект на Github недавно (23 ноября на данный момент), в нагрузку идёт у них старая версия фреймворка для работы с JSON. А, т.к. в мой проект уже встроена более новая версия фреймворка приложение не компилилось.

Решение:

  1. после добавления SDK в проект удалить папку JSON из SDK Facebook;
  2. в файле FBRequest.m заменить строку #import "JSON.h" на #import "SBJson.h";
  3. в том же файле строку SBJSON *jsonParser = [[SBJSON new] autorelease] заменить на SBJsonParser *jsonParser = [[SBJsonParser new] autorelease].

Должно работать. 

comment comments

Шпаргалка по Amazon Simple Email Service (SES) Scripts

В качестве Developer Tools для Amazon Simple Email Service (Amazon SES) - Amazon предоставляет набор консольных Perl-скриптов. Находятся они здесь.

После скачивания и распаковки нужно указать свои ключи для Amazon SES, которые были выданы вам после регистрации, в файле aws-credentials. У Perl-скриптов должен быть атрибут executable (ставится через команду chmod).

Простой набор команд для работы со скриптами из консоли:

./ses-get-stats.pl -k aws-credentials -q - статистика по аккаунту - количество писем, отправленных за последние 24 часа, лимит писем в сутки и лимит на отправку писем в секунду.

./ses-verify-email-address.pl -k aws-credentials -v [адрес ящика] - верификация почтового ящика. Ящик должен быть предварительно создан и активен, т.к. после выполнения скрипта на этот ящик придёт ссылка на активацию. Ссылка на активацию действительна только в течение часа, поэтому позаботьтесь о том, чтобы у вас не было задержек с доставкой писем. У меня на Majordomo на почтовый ящик письмо от Amazon доставлялось очень долго, пришлось писать в техподдержку, чтобы они убрали Amazon из списка серых (кажется) серверов, иначе ссылка была уже просто нерабочей спустя столько времени.

DNS-записи для более удачной доставки:

TXT-запись: spf2.0/pra include:amazonses.com ?all

TXT-запись: v=spf1 include:amazonses.com ?all

Пакеты, которые нужно поставить в Ubuntu для запуска скриптов:
sudo apt-get libxml-dom-perl libxml-libxml-perl libcrypt-ssleay-perl

За список пакетов спасибо @stay_positive

UPD. 17/01/2012:  консольные .pl-скрипты уже неактуальны. В AWS Management Console в разделе SES есть теперь и вирификация e-mail-адреса, и статистика отправленных писем, и даже возможность отправить тестовое письмо с подтверждённого ящика на любой.

comment comments