iOS App

iOS App For Myself.

XCTest

XCTest是Xcode5引入的单元测试框架,替换了之前的OCUnitXCTest中的测试用例类都继承自XCTestCase类,断言前缀为XCT

项目中引入XCTest框架

创建新项目

使用Xcode5新建项目,会自动创建名为[ProjectName]TestsTarget, 自动生成[ProjectName]Tests目录,且包含一个测试失败的单元测试文件[ProjectName]Tests.m

老项目中引入

在Xcode中选择菜单File->New->Target...,在弹出框中选择iOS->Other->Cocoa Touch Unit Testing Bundle,在弹出的设置页面中 可根据需要修改,其中Product Name中的默认值为[ProjectName] Tests,中间包含一空格,推荐将空格去掉;其他项保持原状即可。

注: * XCTest框架在运行时自动注入(Inject) app的target,访问其中的符号信息(symbols), 所以在XCTest Target的Compile Sources中只需要包含测试用例文件,不需要包含待测试的源文件。 * 若运行单元测试时有找不到符号的提示,检查下app Target Build Settings中的Symbols hidden by default,应该设为NO

运行单元测试

  1. 菜单Product->Test或者Command+U可运行全部测试用例。
  2. 在Xcode5的导航区有Test Navigator,在其中可选择运行整个bundle的测试方法、运行某个测试类或运行某一个测试方法。
  3. 使用xcodebuild test命令运行。
1
xcodebuild test -scheme XCTestDemo -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone Retina (4-inch),OS=7.1'

注: XCTest只能在iOS7以上的模拟器或设备中运行,所以在使用方法1、2时,Xcode中的指定的运行设备需为7.0以上的;而使用xcodebuild test 命令运行时需指定destination参数,选择7.0以上的模拟器或真实设备运行。否则会有错误提示: “Library not loaded: /Developer/Library/Frameworks/XCTest.framework/XCTest”

执行完测试用例后,执行通过的测试用例左侧会有绿色对勾图标,执行失败的测试用例左侧会有红色x图标,且执行失败的行会红色高亮显示。

XCTestCase类结构

一个测试用例文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <XCTest/XCTest.h>

@interface XCTestDemoTests : XCTestCase

@end

@implementation XCTestDemoTests

+ (void)setUp
{
    NSLog(@"TestCase Class setUp");
}

+ (void)tearDown
{
    NSLog(@"TestCase Class tearDown");
}

- (void)setUp
{
    [super setUp];

    NSLog(@"TestCase SetUp");
}

- (void)tearDown
{
    [super tearDown];
    NSLog(@"TestCase tearDown");
}

- (void)testExample1
{
    NSLog(@"testExample1");
    XCTAssertTrue(1, @"testExample1");
    //XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);
}

- (void)testExample2
{
    NSLog(@"testExample2");
    XCTAssertTrue(1, @"testExample1");
    //XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);
}

@end

XCTestCase类中包含类方法+ (void)setUp+ (void)tearDown,实例方法- (void)setUp- (void)tearDown以及多个测试方法。 这些方法的执行顺序为

  1. + (void)setUp
  2. - (void)setUp
  3. - (void)testExample1
  4. - (void)tearDown
  5. - (void)setUp
  6. - (void)testExample2
  7. - (void)tearDown
  8. + (void)tearDown

setup类方法会在类创建的调用一次,类销毁的时候调用tearDown类方法,在每个测试方法执行之前调用setUp实例方法,测试方法执行之后调用 tearDown实例方法。

断言

XCTest支持的断言有:

  • XCTFail (format…)
  • XCTAssertNil (a1, format…)
  • XCTAssertNotNil (a1, format…)
  • XCTAssert (a1, format…)
  • XCTAssertTrue (a1, format…)
  • XCTAssertFalse (a1, format…)
  • XCTAssertEqualObjects (a1, a2, format…)
  • XCTAssertEquals (a1, a2, format…)
  • XCTAssertEqualsWithAccuracy (a1, a2, accuracy, format…)
  • XCTAssertThrows (expression, format…)
  • XCTAssertThrowsSpecific (expression, specificException, format…)
  • XCTAssertThrowsSpecificNamed (expression, specificException, exceptionName, format…)
  • XCTAssertNoThrow (expression, format…)
  • XCTAssertNoThrowSpecific (expression, specificException, format…)
  • XCTAssertNoThrowSpecificNamed (expression, specificExcepton, exceptionName, format…)

具体可见XCTestAssertions.h文件

异步单元测试

正常情况下,单元测试方法执行完毕后立即返回,若测试的是异步任务,此时没有拿到异步任务的返回结果,测试就终止了。

测试异步任务时,需要注意两个方法:

  1. 单元测试方法在异步任务完成前不能返回。
  2. 在Cocoa中需要使用run loop来处理网络和定时器,这样异步任务才能完成。

解决方案:

  1. 在调用异步任务后,使用CFRunLoopRun()启用run loop一直等待,且run loop去处理网络和定时器以完成异步任务。
  2. 在异步任务完成后,使用CFRunLoopStop(CFRunLoopGetCurrent())终止当前的run loop,以结束单元测试方法。

CFRunLoopRun

The current thread’s run loop runs in the default mode (see “Default Run Loop Mode”) until the run loop is stopped with CFRunLoopStop or all the sources and timers are removed from the default run loop mode.

Run loops can be run recursively. You can call CFRunLoopRun from within any run loop callout and create nested run loop activations on the current thread’s call stack.

CFRunLoopStop

This function forces rl to stop running and return control to the function that called CFRunLoopRun or CFRunLoopRunInMode for the current run loop activation. If the run loop is nested with a callout from one activation starting another activation running, only the innermost activation is exited.

1
2
3
4
5
6
7
8
- (void)testAsyncTask
{
    [taskManager execAsyncTaskWithCompletion:^(NSError *error) {
        XCTAssertNil(error, @"Error should be nil");
         CFRunLoopStop(CFRunLoopGetCurrent());
    }];
    CFRunLoopRun();
}

github上有两个开源库使用NSRunLoop对此方法进行了扩展,增加了超时等机制。

参考

App Info

Device信息

硬件设备类型

硬件类型字符串,格式为”iPhone5,1”、”iPod5,1”、”iPad3,2”、”x86_64”等

1
2
3
4
5
6
7
8
9
10
-(NSString *)platform
{
    size_t size;
    sysctlbyname("hw.machine", NULL, &size, NULL, 0);
    char *machine = malloc(size);
    sysctlbyname("hw.machine", machine, &size, NULL, 0);
    NSString *platform = [NSString stringWithUTF8String:machine];
    free(machine);
    return platform;
}

或者

1
2
3
4
5
6
7
-(NSString *)platform{
    struct utsname systemInfo;
    uname(&systemInfo);
    NSString *machine =  [NSString stringWithCString:systemInfo.machine
                                            encoding:NSUTF8StringEncoding];
    return machine;
}

硬件类型和具体设备的对应关系见iOSDeviceModelMapping.plist

MAC地址

iOS7后, 获取Mac地址总会返回”02:00:00:00:00:00”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- (NSString *) macaddress{

    int                 mib[6];
    size_t              len;
    char                *buf;
    unsigned char       *ptr;
    struct if_msghdr    *ifm;
    struct sockaddr_dl  *sdl;

    mib[0] = CTL_NET;
    mib[1] = AF_ROUTE;
    mib[2] = 0;
    mib[3] = AF_LINK;
    mib[4] = NET_RT_IFLIST;

    if ((mib[5] = if_nametoindex("en0")) == 0) {
        printf("Error: if_nametoindex error\n");
        return NULL;
    }

    if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
        printf("Error: sysctl, take 1\n");
        return NULL;
    }

    if ((buf = malloc(len)) == NULL) {
        printf("Could not allocate memory. error!\n");
        return NULL;
    }

    if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
        printf("Error: sysctl, take 2");
        free(buf);
        return NULL;
    }

    ifm = (struct if_msghdr *)buf;
    sdl = (struct sockaddr_dl *)(ifm + 1);
    ptr = (unsigned char *)LLADDR(sdl);
    NSString *outstring = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
                           *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4), *(ptr+5)];
    free(buf);

    return outstring;
}

操作系统名

如”iPhone OS”

1
[[UIDevice currentDevice] systemName];

操作系统版本号

例如:7.1.1

1
[[UIDevice currentDevice] systemVersion];

Model

例如”iPhone”,“iPod touch”,“iPhone Simulator”

1
[[UIDevice currentDevice] model];

IDFV(Vendor ID)

厂商ID,具体见UDID

1
[[[UIDevice currentDevice] identifierForVendor] UUIDString];

设备方向(orientation)

1
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

注:UIDeviceOrientationUIInterfaceOrientation定义有差别。

物理内存

获取设备物理内存大小,以字节为单位

1
2
3
4
5
6
7
8
9
10
#include <sys/sysctl.h>

- (uint64_t)physicalMemory
{
    size_t size = sizeof(uint64_t);
    uint64_t physicalMemorySize;
    int mib[2] = {CTL_HW, HW_MEMSIZE};
    sysctl(mib, 2, &physicalMemorySize, &size, NULL, 0);
    return physicalMemorySize;
}

用户空间内存大小

获取用户空间可用的内存大小(去除内核、Video等内存占用),以字节为单位

1
2
3
4
5
6
7
8
9
#include <sys/sysctl.h>
- (uint64_t)userMemory
{
    size_t size = sizeof(uint64_t);
    uint64_t userMemorySize;
    int mib[2] = {CTL_HW, HW_USERMEM};
    sysctl(mib, 2, &userMemorySize, &size, NULL, 0);
    return userMemorySize;
}

当前应用所占内存

目前没有很好的方法获取App当前所占内存,有一种方法是使用vm_statistics_data_t获取虚拟内存信息,仅供参考 Determining Available Memory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#import <mach/mach.h>
#import <mach/mach_host.h>

static void print_free_memory () {
    mach_port_t host_port;
    mach_msg_type_number_t host_size;
    vm_size_t pagesize;

    host_port = mach_host_self();
    host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
    host_page_size(host_port, &pagesize);

    vm_statistics_data_t vm_stat;

    if (host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size) != KERN_SUCCESS)
        NSLog(@"Failed to fetch vm statistics");

    /* Stats in bytes */
    natural_t mem_used = (vm_stat.active_count +
                          vm_stat.inactive_count +
                          vm_stat.wire_count) * pagesize;
    natural_t mem_free = vm_stat.free_count * pagesize;
    natural_t mem_total = mem_used + mem_free;
    NSLog(@"used: %u free: %u total: %u", mem_used, mem_free, mem_total);
}

iOSMemoryBudgetTest通过不断alloc内存来记录应用crash时总的内存分配量, 但记录的alloc的内存数量和使用上面方法获取的mem_used数据对不上。

进程信息(NSProcessInfo)

NSProcessInfo中包含当前进程的信息,包括启动参数、环境变量、进程ID、进程名、操作系统名、操作系统版本、处理器个数、系统启动时间等。

1
2
3
4
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
NSDictionary *environment = [processInfo environment];
NSArray *arguments = [processInfo arguments];
NSUInteger numcore = [processInfo processorCount];

示例代码见SystemInfo

Application信息

当前应用状态(UIApplicationState)

1
 [[UIApplication sharedApplication] applicationState];

UIApplicationState状态有:

1
2
3
4
5
typedef enum : NSInteger {
   UIApplicationStateActive,
   UIApplicationStateInactive,
   UIApplicationStateBackground
} UIApplicationState;

应用注册的推送类型(UIRemoteNotificationType)

1
[[UIApplication sharedApplication] enabledRemoteNotificationTypes];

UIRemoteNotificationType定义的类型有

1
2
3
4
5
6
7
typedef enum : NSUInteger {
   UIRemoteNotificationTypeNone    = 0,
   UIRemoteNotificationTypeBadge   = 1 << 0,
   UIRemoteNotificationTypeSound   = 1 << 1,
   UIRemoteNotificationTypeAlert   = 1 << 2,
   UIRemoteNotificationTypeNewsstandContentAvailability = 1 << 3
} UIRemoteNotificationType;

Screen信息

分辨率scale

1.0为普通分辨率,2.0为2倍分辨率,即为Retina屏幕

1
[[UIScreen mainScreen] scale];

Bundle信息

发布版本号(Version/ Bundle versions string, short)

一般格式为三段.分隔的整数,如3.24.1等

1
[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];

使用NSString的compare方法,指定NSNumbericSearch选项,可比较版本号

1
2
3
4
5
6
7
-(BOOL)version:(NSString*)_oldver lessthan:(NSString*)_newver
{
    if([_oldver compare:_newver options:NSNumericSearch] == NSOrderedAscending){
        return YES;
    }
    return NO;
}

内部版本号(build/Bundle version)

内部构建版本号,格式也一般为三段.分隔的整数,也可以使用单一整数递增

1
[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey];

kCFBundleVersionKey定义即为字符串”CFBundleVersion”

Bundle identifier

每个应用的唯一bundle id

1
[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey];

kCFBundleIdentifierKey定义为”CFBundleIdentifier”

其他

广告ID(IDFA)

1
[[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];

参考

UDID

用于标识iOS设备的各种方法。

UDID(Unique Device Identifier)

1
[[UIDevice CurrentDevice] uniqueIdentifier];
  • UDID是可以唯一标识iOS设备的由40个16进制字符组成的序列。
  • Available in iOS 2.0 – iOS 6.1;Deprecated in iOS 5.0.
  • 2013.5.1起,App Store不允许访问UDID的新应用提交。

IDFV(identifierForVendor) 开发商ID

用于应用的开发商(Vendor)唯一标识一台设备的ID。

1
NSUUID *vendorId = [[UIDevice CurrentDevice] identifierForVendor];

The value of this property is the same for apps that come from the same vendor running on the same device. A different value is returned for apps on the same device that come from different vendors, and for apps on different devices regardless of vendor. Normally, the vendor is determined by data provided by the App Store. If the app was not installed from the app store (such as when the app is still in development), the vendor is determined based on the app’s bundle ID. The bundle ID is assumed to be in reverse-DNS format, and the first two components are used to generate a vendor ID. For example, com.example.app1 and com.example.app2 would appear to have the same vendor ID.

If the value is nil, wait and get the value again later. This happens, for example, after the device has been restarted but before the user has unlocked the device.

The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them. The value can also when installing test builds using Xcode or when installing an app on a device using ad-hoc distribution. Therefore, if your app stores the value of this property anywhere, you should gracefully handle situations where the identifier changes.

  • Available in iOS 6.0 and later.
  • 对运行在同一台设备上的同一个开发商(Vendor)的所有App都返回同一个值。
  • 对运行在同一台设备上的不同开发商(Vendor)的App返回不同的值。
  • 对运行在不同设备上的App,不论开发商(Vendor),都返回不同的值。
  • 从AppStore上下载的App,Vendor信息由AppStore中的数据决定。
  • 不是从AppStore上下载的应用(例如开发中的应用),Vendor信息由app的bundle ID决定;bundle ID的前两部分用于 生成vendor ID,例如com.example.app1和com.example.app2会返回相同的vendor ID。
  • 设备重启后、解锁前,vendor ID可能返回nil。
  • 当同一个vendor的任意一个App在设备上存在时,vendor ID保持不变。当用户删除此vendor的所有App后,然后再次安装时vendor ID会 发生变化。

IDFA(Identifier for Advertisers)

用于广告统计的设备标识符。

1
NSUUID *adId = [[ASIdentifierManager sharedManager] advertisingIdentifier];
  • Available in iOS 6.0 and later.
  • advertisingIdentifierAdSupport.framework框架的一部分。
  • 几种情况下IDFA会变化

    • 系统Reset(iOS7:Settings.app –> General –> Reset –> Reset All Content and Settings)
    • Reset Advertising Identifier(iOS7:Settings.app –> Privacy –> Advertising –> Reset Advertising Identifier)

      Reset Advertising Identifier后,如果应用已经运行在后台,则返回应用后调用advertisingIdentifier并不会返回新的值,只有应用终止再启动才会返回新的值。

  • Limit Ad Tracking设为ON状态不影响advertisingIdentifier的访问.

  • 访问了advertisingIdentifier但没有显示任何广告的App会被拒掉,见tapstream的blog

Limit Ad Tracking

Settings.app -> Privacy -> Advertising设置有一项Limit Ad Tracking,是否限制广告跟踪,目前这一选项没有实质性作用。 在开发文档中对advertisingTrackingEnabled这样描述

Check the value of this property before performing any advertising tracking. If the value is NO, use the advertising identifier only for the following purposes: frequency capping, conversion events, estimating the number of unique users, security and fraud detection, and debugging.

advertisingTrackingEnabled为NO,即Limit Ad Tracking为’ON’状态;这只是设置一个标识告诉广告商(advertisers)不要使用 Advertising ID对用户进行针对性的广告匹配;但没有任何强制措施来保证这一点,对访问advertisingIdentifier也没有任何限制。

Mac Address

在iOS之前可以获取设备的Mac地址做个MD5作为设备的唯一标识,但自iOS7起,获取Mac Address总是返回”02:00:00:00:00:00”

In iOS 7 and later, if you ask for the MAC address of an iOS device, the system returns the value02:00:00:00:00:00. If you need to identify the device, use the identifierForVendor property ofUIDevice instead. (Apps that need an identifier for their own advertising purposes should consider using the advertisingIdentifier property of ASIdentifierManager instead.)

UUID(Universally Unique Identifiers)

UUID是可以在空间和时间范围内保证唯一性的128bit序列,由RFC 4122定义,微软的一个实现也叫做GUID(Globally Unique Identifier).

UUIDs (Universally Unique Identifiers), also known as GUIDs (Globally Unique Identifiers) or IIDs (Interface Identifiers), are 128-bit values A UUID is made unique over both space and time by combining a value unique to the computer on which it was generated and a value representing the number of 100-nanosecond intervals since October 15, 1582 at 00:00:00.

在iOS6中有NSUUID类来表示UUID,上面介绍的identifierForVendoradvertisingIdentifier返回值类型都是NSUUID。

也可以直接使用NSUUID生成一个新的UUID,使用UUIDString方法获取其字符串表示;

1
2
NSUUID *uuid = [NSUUID UUID];
NSString *uuidString = [uuid UUIDString];

NSUUID与Core Fundation的CFUUIDRef不是toll-free bridged的,不能直接进行转换。 需要使用UUID string在NSUUIDCFUUID间中转。

CFUUID可在iOS5及之前的版本使用

1
2
CFUUIDRef uuid = CFUUIDCreate(NULL);
NSString *UUID = CFUUIDCreateString(NULL, uuid);

OpenUDID

一个用于替换UDID的开源方案,不推荐使用

SecureUDID Deprecated

Crashlytics发布的开源沙盒UDID方案,现已废弃

参考

Rvm

Ruby Version Manager(RVM)是用于管理和切换多个版本ruby环境的命令行工具。

安装RVM

1
curl -sSL https://get.rvm.io | bash -s stable --ruby

安装完成有提示:

To start using RVM you need to run `source /Users/xuguoxing/.rvm/scripts/rvm`
in all your open shell windows, in rare cases you need to reopen all shell windows.

安装Ruby 1.9.3(Octopress使用)

1
2
3
rvm install 1.9.3
rvm use 1.9.3
rvm rubygems latest

Homebrew

Homebrew是OS X上的包管理器

安装Homebrew

1
ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

使用Homebrew安装包

1
$ brew install wget

Homebrew会将包安装到/usr/local下自己的目录中,然后在/usr/local/bin中创建符号链接指向实际的执行文件

1
2
3
4
5
6
7
8
$ pwd
/usr/local/Cellar
$ ls
openssl   wget
$ pwd
/usr/local/bin
$ ls -l wget
wget -> ../Cellar/wget/1.15/bin/wget

卸载Homebrew

使用以下脚本卸载

ImageOptim

ImageOptim

ImageOptim通过寻找最好的压缩参数和移除不必要的commentscolor profiles来减少图片大小,提高加载速度。

ImageOptim可以处理PNG,JPEG和GIF动画。

ImageOptim无缝集成以下优化工具:

ImageOptim代码是开源的,其源代码在GitHub上。

PNG that works文章中解释了为什么ImageOptim移除gamma信息,和怎么在 保留透明度的情况下获取更小的png图片。

Xcode内置的图片优化 pngcrush

Xcode默认会将所有的PNG图片转换成一个非标准的iOS特定的PNG格式CgBI file format

这种格式使用premultiplied BGRA代替了RGBA颜色空间,会在loading阶段节省一点点转换时间,但完全不影响 图片渲染(Rendering)速度。

Xcode的这种转换有时会使图片增大,会抵消ImageOptim的优化。

可以在Build Settings中将Compress PNG Files设为NO来禁止这种转换。

Apple的PNG变体,在iOS外无法使用,也无法使用Preview工具查看,可以使用pngcrush工具进行逆向转换成正常的png图片。

pngcrush在Xcode的安装目录中存在,路径类似为/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush

1
$ /Applications/Xcode.app/Contents/Developer\/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush \-revert-iphone-optimizations -q Local.png Local-standard.png

可将Local.png 恢复成正常的Local-standard.png

Image Alpha

ImageAlpha通过有损压缩和转换为更有效率的PNG8+alpha格式,可以有效的减小24-bit PNG文件(包括alpha透明度)的大小。其生成的图片兼容iOS,所有的浏览器设置IE6。

ImageAlpha通过使用最新的pngquant,pngnq-s9alpha-channel-awareposterizer 能达到比Adobe Fireworks中类似功能更好的图片质量。

ImageAlpha基于pngquant,在ImageAlpha.app/Contents/Resources目录下会有pngquant执行文件,可以用于批量处理。ImageOptim-CLI也会使用此文件。

ImageAlpha是开源的,其代码是python的。source code在github上。

JPEGmini for Mac

JPEGmini Mac版本可以对JPEG图片最高进行5倍的压缩,并保持原始的质量和JPEG格式。

JPEGmini是收费软件,在appstore有一个免费版本JPEGmini Lite下载,免费版本每天只能压缩20张图片。

ImageOptim-CLI

ImageOptim-CLI命令行工具使用ImageOptim,ImageAlpha,JPEGmini for Mac 对图片进行批量优化。

从目前的benchmarks可看出ImageOptim,ImageAlpha和JPEGmini的优化效果比其他替代品要好。

下载最新的zip包,解压并将ImageOptim-CLI的路径添加到$PATH中。

1
2
3
$ curl --output imageoptim-cli.zip https://codeload.github.com/JamieMason/ImageOptim-CLI/zip/1.7.11
$ unzip imageoptim-cli.zip
$ export PATH=$PATH:imageoptim-cli/bin

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  Usage: imageOptim [options]

  Options:

  -d, --directory     directory of images to process
  -a, --image-alpha   pre-process PNGs with ImageAlpha.app *
  -j, --jpeg-mini     pre-process JPGs with JPEGmini.app **
  -q, --quit          quit all apps when complete
  -h, --help          display this usage information
  -e, --examples      display some example commands and uses
  -v, --version       display the version number

*  http://pngmini.com
** https://itunes.apple.com/us/app/jpegmini/id498944723
  • -d指定要优化的图片文件目录;
  • -a,-j指定预先使用ImageAlpha优化PNG图片和使用JPEGmini优化JPEG图片,因为JPEGmini为收费的,如果没有 安装,不指定-j即可;

一些示例命令

1
2
3
4
imageOptim --directory ~/Sites/Awesome # [options]
find ~/Sites/Awesome -name '*.jpg' | imageOptim # [options]
imageOptim --jpeg-mini --image-alpha --quit --directory path/to/images
imageOptim -j -a -q -d path/to/images

Octopress建站流程

一.设置Octopress

检查Ruby环境

确保Ruby环境为1.9.3,若不是,则下载RVM,安装1.9.3版本

1
2
3
4
$ curl -L https://get.rvm.io | bash -s stable --ruby
$ rvm install 1.9.3
$ rvm use 1.9.3
$ rvm rubygems latest

Clone Octopress

1
2
$ git clone git://github.com/imathis/octopress.git octopress
$ cd octopress

安装依赖(dependencies)

1
2
$ sudo gem install bundler
$ sudo bundle install

安装默认主题

1
rake install

二.部署到Github Pages

若本机无SSH Key,则生成Github SSH Key并上传

生成SSH key

1
2
$ ssh-keygen -t rsa -C "your_email@example.com"
$ ssh-add id_rsa

添加SSH Key到GitHub

1
$ pbcopy < ~/.ssh/id_rsa.pub #拷贝到clipboard

在github的Account Settings添加SSH Key

测试环境输出

1
$ ssh -T git@github.com

创建Github Repository

  • 创建名字格式为username.github.io,username必须为github的用户名。即xuguoxing.github.io

  • Initialize this repository with a README 项不勾选

设置 github pages

1
$ rake setup_github_pages

该命令会询问repo地址,并在_deploy目录下设置master branch

1
2
$ rake generate
$ rake deploy

生成blog,拷贝生成的文件到_deploy/,添加到git,commit并push到master branch. 等待大约10分钟

source上传到Github

1
2
3
$ git add .
$ git commit -m 'your message'
$ git push origin source

三.新增Posts

Blog Post需用存储到source/_posts目录中,命名需符合Jekyll的命名规范:YYYY-MM-DD-post-title.markdown

Octopress提供rake task创建新的post,自动符合命名规范的文件,并包含yaml元数据

1
rake new_post["title"]

示例:

1
rake new_post["Zombie Ninjas Attack: A survivor's retrospective"]

会生成文件source/_posts/2011-07-03-zombie-ninjas-attack-a-survivors-retrospective.markdown;url访问地址为http://site.com/blog/2011/07/03/zombie-ninjas-attack-a-survivors-retrospective/index.html

生成的文件头部包含yaml front matter告诉Jekyll如何处理Posts和Pages

---
layout: post
title: "Zombie Ninjas Attack: A survivor's retrospective"
date: 2011-07-03 5:59
comments: true
external-url:
categories:
---

可能存在的数据有

  • layout:指定使用的layout文件(不带.Layout扩展名),Layout文件必须放置在_layouts目录下
  • title:标题
  • date: 发布日期
  • comments: true或false,是否允许评论
  • author: 对于多作者blog,可设置文章作者
  • published: true或false,是否发布,如果在写草稿,可设为false
  • external-url: 发布linklog样式post,填写指向的外部链接
  • categories:可设置单个category或者多个category,格式件下面

      # One category
      categories: Sass
    
      # Multiple categories example 1
      categories: [CSS3, Sass, Media Queries]
    
      # Multiple categories example 2
      categories:
      - CSS3
      - Sass
      - Media Queries
    
  • permalink: 代替blog post中的/year/month/day/title.html作为最终url

  • tags: 文字的tag标注,用法和categories相同

四.新增Pages

可以在blog source目录下的任意位置增加pages,对应的URL会指向对应路径,如about.markdown会生成链接site.com/about.html,about/index.markdown会生成链接site.com/about/index.html

生成新pages的rake task为:

1
2
rake new_page[super-awesome]
rake new_page[super-awesome/page.html]
  • 第一种方式生成markdown文件/source/super-awesome/index.markdown
  • 第二种方式生成html文件/source/super-awesome/page.html

生成markdown文件的yaml头样式为:

---
layout: page
title: "Super Awesome"
date: 2011-07-03 5:59
comments: true
sharing: true
footer: true
---

和post很相似,但不包括categories, sharing和footer是做什么用的;如果不需要显示日期date可删除

Pages在导航栏位置的配置在文件source/_includes/custom/navigation.html中。

五.生成和预览

1
2
3
rake generate   # Generates posts and pages into the public directory
rake watch      # Watches source/ and sass/ for changes and regenerates
rake preview    # Watches, and mounts a webserver at http://localhost:4000

使用rake preview可在本地http://localhost:4000预览生成效果。

六.配置Octopress

配置文件包括

_config.yml       # Main config (Jekyll's settings)
Rakefile          # Configs for deployment
config.rb         # Compass config
config.ru         # Rack config

一般情况下只需要配置_config.yml;Rakefile是关于部署的,如果需要使用rsync同步,则需要设置;另外两个文件一般不用。

_config.yml配置包包括三部分

Main Configs

url:                # For rewriting urls for RSS, etc
title:              # Used in the header and title tags
subtitle:           # A description used in the header
author:             # Your name, for RSS, Copyright, Metadata
simple_search:      # Search engine for simple site search
description:        # A default meta description for your site
date_format:        # Format dates using Ruby's date strftime syntax
subscribe_rss:      # Url for your blog's feed, defauts to /atom.xml
subscribe_email:    # Url to subscribe by email (service required)
category_feeds:     # Enable per category RSS feeds (defaults to false in 2.1)
email:              # Email address for the RSS feed if you want it.

Jekyll & Plugins

关于Jekyll和插件的配置,可参考configuration docs

root:               # Mapping for relative urls (default: /)
permalink:          # Permalink structure for blog posts
source:             # Directory for site source files
destination:        # Directory for generated site files
plugins:            # Directory for Jekyll plugins
code_dir:           # Directory for code snippets (for include_code plugin)
category_dir:       # Directory for generated blog category pages

pygments:           # Toggle python pygments syntax highlighting
paginate:           # Posts per page on the blog index
pagination_dir:     # Directory base for pagination URLs eg. /blog/page/2/
recent_posts:       # Number of recent posts to appear in the sidebar

default_asides:     # Configure what shows up in the sidebar and in what order
blog_index_asides:  # Optional sidebar config for blog index page
post_asides:        # Optional sidebar config for post layout
page_asides:        # Optional sidebar config for page layout

第三方设置

  • Github – 在侧边栏列出 github repositories
  • Twitter – 设置twitter用户名(不带@),添加Twitter分享按钮
  • Google Plus One – Setup sharing for posts and pages on Google’s plus one network.
  • Pinboard – Share your recent Pinboard bookmarks in the sidebar.
  • Delicious – Share your recent Delicious bookmarks in the sidebar.
  • Disqus Comments – Add your disqus short name to enable disqus comments on your site.
  • Google Analytics – 添加Google Analytics的tracking id分析页面访问.
  • Facebook – 添加Facebook like按钮

Google Analytics的tracking id在Google Analytics页面的 管理->媒体资源->跟踪代码中可以找到

七.设置自定义域名

source目录下,新建文件CNAME,将域名添加到此文件中。

可设置顶级域名,如

1
echo 'iosapp.me' >> source/CNAME

或者子域名

1
echo 'www.iosapp.me' >> source/CNAME

只能设置一个定制域名,如果需要设置多个定制域名,则需要使用其他服务将域名重定向到github pages的域名

部署发布至master

1
2
$ rake generate
$ rake deploy

使用DNSPod管理域名

新建DNSPod账号,添加域名iosapp.me,在godaddy账号中将域名的DNS设置DNSPod的DNS, f1g1ns1.dnspod.netf1g1ns2.dnspod.net

  • DNSPod DNS生效后,在其中添加A或者CNAME记录

    • 若第一步设置的顶级域名iosapp.me,则需要添加一条A记录,将iosapp.me指向固定IP地址,IP地址可通过dig命令获得

        $ dig xuguoxing.github.io +nostats +nocomments +nocmd
        xuguoxing.github.io.    898 IN  CNAME           github.map.fastly.net.
        github.map.fastly.net.  8   IN  A   199.27.79.133
      
    • 若第一步设置的子域名www.iosapp.me,则需要添加一条CNAME域名,将www.iosapp.me指向xuguoxing.github.io
  • 待github设置域名和DNS解析都生效后,即可以通过iosapp.me或者www.iosapp.me访问blog;

可通过dig命令查询解析流程:

1
$ dig iosapp.me +nostats +nocomments +nocmd

八.插件

Image Tag图片

增加图片的语法为

<img class="[position]" src="/path/to/image" title="[width] [height] [title text [alt text]]" >

其中/path/to/image可以为网上的图片URL,也可是是本站的图片;如果自己站点的图片, 通常放在source/images目录下,可以按照年和月建立两层目录,如2014/03。rake generate命令会自动生成博客, 放在source/images目录下的图片也会被复制过去。

图片代码示例

<img src="/images/2014/03/picture.jpg">
<img src="http://placekitten.com/890/280">
<img class="left" src="http://placekitten.com/320/250" title="Place Kitten #2" >
<img class="right" src="http://placekitten.com/300/500" width="150" height="250" title="Place Kitten #3" >
<img class="right" src="http://placekitten.com/300/500" width="150" height="250" title="Place Kitten #4" alt="An image of a very cute kitten">
<img src="http://placekitten.com/890/280">

Blockquote引用

Last night I lay in bed looking up at the stars in the sky and I thought to myself, where the heck is the ceiling.

从Twitter引用

Over the past 24 hours I’ve been reflecting on my life & I’ve realized only one thing. I need a medieval battle axe.

从Web引用

Every interaction is both precious and an opportunity to delight.

代码块

关于代码的引用参见

参考: