Tag: «шпаргалки»
Password generation using Security Framework on iOS and macOS
Couple lines of code to generate a 15 characters length password (just like in Safari):
import Security
let pass = SecCreateSharedWebCredentialPassword() as String?
print(pass ?? "oops")
openssl и Vapor 2
Столкнулся с тем, что перестал компилиться проект на Ubuntu на Vapor 2. Точнее, одна из зависимостей - Crypto. Оказалось, что из-за добавленного репозитория, в котором были более новые версии некоторых библиотек, оно и не компилилось. Запишу сюда - пришлось даунгрейдиться:
apt install libssl-dev=1.0.2g-1ubuntu4.10
apt install openssl=1.0.2g-1ubuntu4.10
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 commentsShake эффект для 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()
Элегантный 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)
Примерно так же сделано и в самом UIKit:
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
}
}
Кастомный бейдж для 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 {}
Функция задержки на 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) {
// код
}
Модальный 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()
Конвертирование 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)
Лог-функция для 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
Простой 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
Восстановление слетевшего загрузчика 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
Баг 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 подключить, если нужно. Но есть два но:
- Когда ты создаёшь отложенный push notification с расписанием - ты указываешь время отправки. Так вот через Javascript SDK он выставляет его по GMT и всё. Всем пуш будет отправлен по GMT вне зависимости от их часовой зоны, просто по расписанию. Указать, что ты хочешь отправить push всем, например, в 19:00 местного времени, чтобы клиент в любом часовом поясе получил сообщение в свои 19:00, в Javascript SDK ты не можешь. Этому багу уже 2 года. Вместо этого, тебе предлагается использовать из Javascript их REST API (sic!).
- Окей, ты используешь 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;
}
}
Правильный парсинг дат через 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];
Жизненный цикл 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;
}
Получение номера дня недели на 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]);
Симуляция плохого соединения с интернетом в 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Как обновить хакинтош с Mac OS X Mountain Lion до 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Создание 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 выпустили 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
- Для xib-файлов для View выставить Size: Freeform. Тогда интерфейс на всю высоту экрана растянется. Если надо всё кастомно - то можно выставить для вида размер для 3.5-дюймового экрана или 4-дюймового и программно нужный подставлять.
- Самое главное (подсказка от @Krivoblotsky) - добавить стартовую картинку для 4-дюймового экрана. Без неё апп почему-то думает, что экран маленький. И да, если раньше никакой стартовой картинки не использовалось - теперь походу придётся.
Как оно со 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 дюймов
}
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:
- Как установить Sublime Text 2 в Ubuntu 12.04 и Unity.
- Подключение по SFTP с помощью Public Key (.pem).
Как убрать DRM защиту с книг Amazon Kindle (E-Books)
Книги, купленные в электронном варианте в магазине Amazon содержат DRM-защиту, то есть открыть книгу и прочитать может только тот, кто её купил, через специальную программу, предварительно введя свои амазоновские логин и пароль. Но защиту можно снять и даже сконвертировать книгу в другой формат.
comment commentsКак установить Sublime Text 2 в Ubuntu 12.04 и Unity
Sublime Text 2 запускается без установки, но никак не интегрируется в систему. Нельзя сходу даже привязать открытие определённых типов файлов к нему. Как установить и интегрировать Sublime с системой:
- Распаковать архив с Sublime
- Перемещаем Sublime в /usr/lib:
sudo mv Sublime\ Text\ 2 /usr/lib/ - Чтобы вызывать редактор из консоли командой sublime:
sudo ln -s /usr/lib/Sublime\ Text\ 2/sublime_text /usr/bin/sublime - Чтобы создать лаунчер для 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 - Если нужно проставить ассоциации, чтобы определённые типы файлов открывались в 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:
Ну и, собственно, проверяя размеры/тип устройства можно подставлять нужную графику нужных размеров. 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 будет ошибка:
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
и перезапустить его.
Встраивание Facebook sdk в ios-приложение
Шпаргалка. Инструкция по тому, где скачать и как встроить - в документации Facebook. Несмотря на то, что обновляли они проект на Github недавно (23 ноября на данный момент), в нагрузку идёт у них старая версия фреймворка для работы с JSON. А, т.к. в мой проект уже встроена более новая версия фреймворка приложение не компилилось.
Решение:
- после добавления SDK в проект удалить папку JSON из SDK Facebook;
- в файле FBRequest.m заменить строку #import "JSON.h" на #import "SBJson.h";
- в том же файле строку SBJSON *jsonParser = [[SBJSON new] autorelease] заменить на SBJsonParser *jsonParser = [[SBJsonParser new] autorelease].
Должно работать.
comment comments