0%

记录iOS各种重要里程碑事件

私有成员变量的实现

1.0时代,在.h文件采用@private关键词

1
2
3
4
@interface ViewController : UIViewController {
@private
NSInteger _value;
}

2.0时代 通过在.m文件通过匿名Category

1
2
3
4
5
@interface ViewController ()

@property (nonatomic) NSInteger value;

@end

3.0时代 2013 年的 WWDC 允许在 .m 的 @implementation

1
2
3
@implementation ViewController {
NSInteger _value;
}

ARC推出

2011

自动生成getter和setter方法的@synthesize

2012

AutoLayout引入

iOS 6.0

Swift

  • WWDC 1.0 版本发布 – 2014.06.02
  • Swift 2.0发布 – 2015.08.07
  • Open Source – 2015.12.04

Size Classes

iOS 8.0

Blocks

Mac OS X 10.6 “Snow Leopard” and iOS 4.0

Objective-C 2.0

At the 2006 Worldwide Developers Conference release of Objective-C 2.0

Watch OS

2015.05.21

iOS API Differences

iOS 2.1 to iOS 2.2 API Differences

Added frameworks:

  • AVFoundation

iOS 2.2 to iOS 3.0 API Differences

Added frameworks:

  • CoreData
  • ExternalAccessory
  • GameKit
  • MapKit
  • MessageUI
  • MobileCoreServices
  • StoreKit

iOS 3.2 to iOS 4.0 API Differences

Added frameworks:

  • Accelerate
  • AssetsLibrary
  • CoreMedia
  • CoreMotion
  • CoreTelephony
  • CoreVideo
  • EventKit
  • EventKitUI
  • iAd
  • ImageIO
  • QuickLook

iOS 4.3 to iOS 5.0 API Differences

Added frameworks

  • Accounts
  • CoreBluetooth
  • CoreImage
  • GLKit
  • GSS
  • NewsstandKit
  • Twitter

5.1 to iOS 6.0 API Differences

Added frameworks

  • AdSupport
  • MediaToolbox
  • PassKit
  • Social

6.1 to iOS 7.0 API Differences

Added frameworks

  • GameController
  • JavaScriptCore
  • MediaAccessibility
  • MultipeerConnectivity
  • SafariServices
  • SpriteKit

iOS 7.1 to iOS 8.0 API Differences

Added frameworks

  • Accelerate
  • Accounts
  • AddressBook
  • AddressBookUI
  • AudioToolbox
  • AudioUnit
  • AVFoundation
  • AVKit (Added)
  • CFNetwork
  • CloudKit (Added)
  • CoreAudio
  • CoreAudioKit (Added)
  • CoreAuthentication (Added)
  • CoreBluetooth
  • CoreData
  • CoreFoundation
  • CoreImage
  • CoreLocation
  • CoreMedia
  • CoreMotion
  • CoreText
  • CoreVideo
  • EventKit
  • EventKitUI
  • ExternalAccessory
  • Foundation
  • GameController
  • GameKit
  • GLKit
  • GSS
  • HealthKit (Added)
  • HomeKit (Added)
  • iAd
  • ImageIO
  • IOKit
  • JavaScriptCore
  • LocalAuthentication (Added)
  • MapKit
  • MediaAccessibility
  • MediaPlayer
  • MessageUI
  • Metal (Added)
  • MobileCoreServices
  • MultipeerConnectivity
  • NetworkExtension (Added)
  • NewsstandKit
  • NotificationCenter (Added)
  • OpenGLES
  • PassKit
  • Photos (Added)
  • PhotosUI (Added)
  • PushKit (Added)
  • QuartzCore
  • QuickLook
  • SceneKit (Added)
  • Security
  • Social
  • SpriteKit
  • StoreKit
  • UIKit
  • VideoToolbox
  • WebKit (Added)

iOS 8.3 to iOS 9.0 API Differences

Added frameworks

Objective-C

  • /usr/include
  • Accelerate
  • Accounts
  • AddressBook
  • AddressBookUI
  • AssetsLibrary
  • AudioToolbox
  • AudioUnit
  • AVFoundation
  • AVKit
  • CFNetwork
  • CloudKit
  • Contacts (Added)
  • ContactsUI (Added)
  • CoreAudio
  • CoreAudioKit
  • CoreBluetooth
  • CoreData
  • CoreFoundation
  • CoreGraphics
  • CoreImage
  • CoreLocation
  • CoreMedia
  • CoreMIDI
  • CoreMotion
  • CoreSpotlight (Added)
  • CoreTelephony
  • CoreText
  • CoreVideo
  • EventKit
  • EventKitUI
  • ExternalAccessory
  • Foundation
  • GameController
  • GameKit
  • GameplayKit (Added)
  • GLKit
  • GSS
  • HealthKit
  • HomeKit
  • iAd
  • ImageIO
  • JavaScriptCore
  • LocalAuthentication
  • MapKit
  • MediaPlayer
  • MediaToolbox
  • MessageUI
  • Metal
  • MetalKit (Added)
  • MetalPerformanceShaders (Added)
  • MobileCoreServices
  • ModelIO (Added)
  • MultipeerConnectivity
  • NetworkExtension
  • NewsstandKit
  • OpenAL
  • PassKit
  • Photos
  • PushKit
  • QuartzCore
  • QuickLook
  • ReplayKit (Added)
  • SafariServices
  • SceneKit
  • Security
  • SpriteKit
  • StoreKit
  • SystemConfiguration
  • UIKit
  • VideoToolbox
  • WatchConnectivity (Added)
  • WatchKit
  • WebKit

备注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com

UIImag构造方式

UIImag构造方式大致有4种方式

  • 从本地bundle中加载 imageNamed:,传入一个bundle的文件名即可
  • 从本地一个文件路径读取 imageWithContentsOfFile:,需要传一个文件的文件路径path
  • 通过二进制数据NSData来创建imageWithData:
  • 通过一个CoreGraphicsCGImageRef来创建,initWithCGImage:
  • 通过一个CoreImageCIImage来创建initWithCIImage

通过查阅Apple官网文档我们发现有2个这样的方法,今天就来一探究竟

1
2
3
4
5
+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(4_0);
+ (UIImage *)imageWithCIImage:(CIImage *)ciImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(6_0);

- (instancetype)initWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(4_0);
- (instancetype)initWithCIImage:(CIImage *)ciImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(6_0);

2个类方法2个实例方法都是类似,这里以CGImageRef为例

1
+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(4_0);
  1. 新建的Xcode工程选择single Application

  2. 在storyboard中拖一个UIImageView设置它水平垂直居中对齐,宽带高度随便设一个值不要太大就行,设置UIImageViewcontentModeAspect Fit方便查看以免变形

  3. UIImageView下发放一个UIButton控件方便后面好对图片进行旋转操作

  4. 在viewController中建立一个UIImageView引用,拉出一个rotate按钮的IBAction

    现在大概界面大概这样
    UIImageOrientation效果图

  5. 下面我们实现
    - (IBAction)rotateImage:(id)sender {}这个方法

    在这里我们想通过点击按钮实现图片旋转
    为了方便使用我们使用Category的方式实现
    新建一个UIImage的分类取名叫Rotate

    这里需要传一张要处理的图片和一个待处理成的图片方向

    1
    2
    + (UIImage *)rotateImage:(UIImage *)oldImage
    orientation:(UIImageOrientation)orientation;
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
    + (UIImage *)rotateImage:(UIImage *)oldImage orientation:(UIImageOrientation)orientation{

UIImage *newImage = [UIImage imageWithCGImage:oldImage.CGImage scale:1 orientation:orientation];

NSString *orientationStr = nil;
switch (orientation) {
case UIImageOrientationUp: {
orientationStr = @"UIImageOrientationUp";
break;
}
case UIImageOrientationDown: {
orientationStr = @"UIImageOrientationDown";
break;
}
case UIImageOrientationLeft: {
orientationStr = @"UIImageOrientationLeft";
break;
}
case UIImageOrientationRight: {
orientationStr = @"UIImageOrientationRight";
break;
}
case UIImageOrientationUpMirrored: {
orientationStr = @"UIImageOrientationUpMirrored";
break;
}
case UIImageOrientationDownMirrored: {
orientationStr = @"UIImageOrientationDownMirrored";
break;
}
case UIImageOrientationLeftMirrored: {
orientationStr = @"UIImageOrientationLeftMirrored";
break;
}
case UIImageOrientationRightMirrored: {
orientationStr = @"UIImageOrientationRightMirrored";
break;
}

}

NSLog(@"current orientation: %@",orientationStr);

return newImage;
}

在button点击事件触发时的这样使用

1
2
3
4
5
6
7
8
- (IBAction)rotateImage:(id)sender {

UIImage *oldImage = self.imgView.image;

UIImage *rotatedImage = [UIImage rotateImage:oldImage orientation:UIImageOrientationLeft];

self.imgView.image = rotatedImage;
}

点击按钮测试发现第一次没问题,但是重逢点击无效
原来+ (UIImage *)imageWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation方法执行原理是执行前通过
@property(nonatomic,readonly) UIImageOrientation imageOrientation;接口先判断当前图片的方向是否为将要旋转的方向,
如果是就直接返回不做处理,如果不是再作旋转处理,
也就是说这个方法并没有实际上旋转image的数据,只是用一个枚举标记旋转的状态

如果我们想每次旋转需要直接改变原始image的数据该怎么办呢?
在这里我们通过CGBitmapContext,使用CGContextRotateCTM来设置旋转,
再把UIImage通过drawInRect 重新绘制出来,通过UIGraphicsGetImageFromCurrentImageContext获得处理后的图片
下面是具体实现

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
- (UIImage *)fixedRotation{
if (self.imageOrientation == UIImageOrientationUp) return self;
CGAffineTransform transform = CGAffineTransformIdentity;

switch (self.imageOrientation) {
case UIImageOrientationDown:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;

case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, 0);
transform = CGAffineTransformRotate(transform, M_PI_2);
break;

case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, 0, self.size.height);
transform = CGAffineTransformRotate(transform, -M_PI_2);
break;
case UIImageOrientationUp:
case UIImageOrientationUpMirrored:
break;
}

switch (self.imageOrientation) {
case UIImageOrientationUpMirrored:
case UIImageOrientationDownMirrored:
transform = CGAffineTransformTranslate(transform, self.size.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;

case UIImageOrientationLeftMirrored:
case UIImageOrientationRightMirrored:
transform = CGAffineTransformTranslate(transform, self.size.height, 0);
transform = CGAffineTransformScale(transform, -1, 1);
break;
case UIImageOrientationUp:
case UIImageOrientationDown:
case UIImageOrientationLeft:
case UIImageOrientationRight:
break;
}

// Now we draw the underlying CGImage into a new context, applying the transform
// calculated above.
CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
CGImageGetBitsPerComponent(self.CGImage), 0,
CGImageGetColorSpace(self.CGImage),
CGImageGetBitmapInfo(self.CGImage));
CGContextConcatCTM(ctx, transform);
switch (self.imageOrientation) {
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
// Grr...
CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
break;

default:
CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage);
break;
}

// And now we just create a new UIImage from the drawing context
CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
UIImage *img = [UIImage imageWithCGImage:cgimg];
CGContextRelease(ctx);
CGImageRelease(cgimg);
return img;

}

现在再优化一下原来+ (UIImage *)rotateImage:(UIImage *)oldImage orientation:(UIImageOrientation)orientation 方法,修改成这样

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
47
48
+ (UIImage *)rotateImage:(UIImage *)oldImage orientation:(UIImageOrientation)orientation{

UIImage *newImage = [UIImage imageWithCGImage:oldImage.CGImage scale:1 orientation:orientation];

//fix original Image with gived orientation.
UIImage *fixedRotationImage = [newImage fixedRotation];

NSString *orientationStr = nil;
switch (orientation) {
case UIImageOrientationUp: {
orientationStr = @"UIImageOrientationUp";
break;
}
case UIImageOrientationDown: {
orientationStr = @"UIImageOrientationDown";
break;
}
case UIImageOrientationLeft: {
orientationStr = @"UIImageOrientationLeft";
break;
}
case UIImageOrientationRight: {
orientationStr = @"UIImageOrientationRight";
break;
}
case UIImageOrientationUpMirrored: {
orientationStr = @"UIImageOrientationUpMirrored";
break;
}
case UIImageOrientationDownMirrored: {
orientationStr = @"UIImageOrientationDownMirrored";
break;
}
case UIImageOrientationLeftMirrored: {
orientationStr = @"UIImageOrientationLeftMirrored";
break;
}
case UIImageOrientationRightMirrored: {
orientationStr = @"UIImageOrientationRightMirrored";
break;
}

}

NSLog(@"current orientation: %@",orientationStr);

return fixedRotationImage;
}

现在再测试一下,well,It‘s OK。

have fun!!!

参考资料

备注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com

这是什么?

此文将讲解通过形状图层CAShaperLayerstrokeStartstrokeEnd来实现动画绘制路径CGPath,此文是By Ole Begemann创建于December 18, 2010,当时是发布iOS SDK 4.2时CAShapeLayer新增加的两个属性strokeStartstrokeEnd,这两个值是两个浮点数取值范围0.0~1.0,用来表明形状图层所指向的路径在绘制开始和结束路径中的相对位置。

strokeStart默认值是0.0,strokeEnd默认值是1.0,显然这会导致形状图层的路径将一整个被绘制。假如,你想说,如果设置了layer.strokeEnd = 0.5f,只让她绘制前半部分,那该多好。

真正有趣的事情是这些接口都是可动画的。通过动画绘制strokeEnd从0.0到1.0在几秒内,我们就能很容易自己绘制路径像下面这样写:

1
2
3
4
5
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = 10.0;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[self.pathLayer addAnimation:pathAnimation forKey:@"strokeEndAnimation"];

最后,再添加第二个图层包含一个铅笔图片,使用关键帧动画 CAKeyframeAnimation来让它随着这个路径以相同的速度绘制,就可以达到完美的错觉效果:

1
2
3
4
5
CAKeyframeAnimation *penAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
penAnimation.duration = 10.0;
penAnimation.path = self.pathLayer.path;
penAnimation.calculationMode = kCAAnimationPaced;
[self.penLayer addAnimation:penAnimation forKey:@"penAnimation"];

绘制普通路径效果视频

下载地址

这个对文本一样有效;我们只需要把字符转化成CGPathCore Text提供了那样的功能的函数,CTFontCreatePathForGlyph( )。为了使用它,我们需要创建一个带属性的字符串用我们想要渲染的文本,先把它们分割成行在分割成一个个字符。在把所有的字符转换成路径后,我们以子路径方式把它添加到一个单个的CGPath路径中。更多细节可以查看Ohmu写的Low-level text rendering这篇文章。结果看以来非常的炫酷:

绘制文字路径效果视频

下载地址

从Github上获得iPad版的样品工程

你将学到的知识点

  • 使用CAShapeLayerstrokeStartstrokeEnd来实现路径动画,比较高级复杂的效果像google的下拉刷新转圈就可以从这里引申去实现。
  • CABasicAnimationCABasicAnimation使用
  • 深入理解CAShapeLayerCALayer
  • 通过文本创建路径,核心函数CTFontCreatePathForGlyph()

补充说明

1
2
3
4
5
6
7
8
9
10
11
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = self.animationLayer.bounds;
pathLayer.bounds = pathRect;
pathLayer.geometryFlipped = YES;
pathLayer.path = path.CGPath;
pathLayer.strokeColor = [[UIColor blackColor] CGColor];
pathLayer.fillColor = nil;
pathLayer.lineWidth = 10.0f;
pathLayer.lineJoin = kCALineJoinBevel;

[self.animationLayer addSublayer:pathLayer];

有一点非常重要,CALayer在iOS系统中相对坐标系是以屏幕左上top-left为坐标原点的,在Mac OS X上以坐下bottom-left为坐标原点,但是可以通过CALayer的接口geometryFlipped垂直翻转坐标系,这个值默认是NO,设置成YES就可以把坐标系转换成左下bottom-left了,这里作者使用的左下bottom-left的坐标系。

1
@property(getter=isGeometryFlipped) BOOL geometryFlipped;

关于这个属性使用时需要特别注意

  1. 翻转会同时作用于它的子图层
  2. 即使这个属性设置成YES,图片的orientation仍然是不变的(也就是说当设置flipped=YESflipped=NO时一个CGImageRef储存在contents接口中的内容将会显示一致,赋值并不会真正变换底层的图层)

pathLayer动画实现原理

  1. 先创建一个动画用的图层animationLayer类型CALayer,用来充当动画的画布。
  2. 创建真正的路径图层pathLayer类型为CAShapeLayer,让它的坐标系垂直翻转,并且让图层宽高同时向内收缩100个点,通过CGRectInset(CGRect rect, CGFloat dx, CGFloat dy)实现
  3. pathLayer添加到animationLayer的子图层中去
  4. 创建一个铅笔图层penLayer类型为CALayer,把它添加到pathLayer
  5. pathLayer添加CABasicAnimation动画,动画属性为strokeEnd
  6. penLayer添加CAKeyframeAnimation动画,动画属性为position

textLayer动画实现原理

  1. 先创建一个动画用的图层animationLayer类型CALayer,用来充当动画的画布
  2. Create path from text,See:http://www.codeproject.com/KB/iPhone/Glyph.aspx,最终保存到一个类型为CGMutablePathRef的letter中
  3. 通过letter来创建文字UIBezierPath类型的path
  4. 通过path再创建CAShapeLayerpathLayer,并且把pathLayer添加到animationLayer中去
  5. 创建一个铅笔图层penLayer类型为CALayer,把它添加到pathLayer
  6. pathLayer添加CABasicAnimation动画,动画属性为strokeEnd
  7. penLayer添加CAKeyframeAnimation动画,动画属性为position

修复一处bug

重复点击UISegmentedControl导致铅笔消失,这是设置了 penAnimation.delegate = self;在代理方法里面没有判断结束直接将设置self.penLayer.hidden = YES,导致连续切换时铅笔不见了,要修复这个bug只需加一个判断 if (flag) self.penLayer.hidden = YES;即可,这样的意思是只有当动画完成时才设置self.penLayer.hidden的值,好了现在已经非常完美了,快去动手自己试试吧!🍺

备注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com

用于收藏不错的个人或者公司Blog地址

幽幽传送门

简书

国内个人

国外个人

  • mikeash–白天飞行员,晚上程序员 :] just this guy, you know?
  • shinobicontrols–iOS 最新 API 以及新功能用法
  • iosdevelopertips–教程配合Demo让你学习成长立竿见影
  • iosdevweekly–很棒的个人技术博客网,已发表230篇文章
  • ios-goodies–iOS,UI,UX,Objective-c,Swift,Xcode
  • mattt-thompson – 不用解释
  • subjc –Subjective-C is a study of innovative iOS interfaces.
  • thinkandbuild–《Introduction To 3D Drawing in CoreAnimtion》作者
  • robb.is–《How to build a nice Hamburger transition in swift》作者
  • commandshift–不多说,质量极高
  • indieambitions–raywenderlich常驻作者之一的blog,非常赞。
  • nvie–国外一个大神的blog,讲得比较杂,git,ios,python都有涉猎,但是每篇都很精彩。
  • stuartkhall–很多关于app上线运营之类的blog,值得一看。
  • ittybittyapps–神器Reveal的作者的blog。
  • adoptioncurve–更新极快,当时iOS8还没出几个周,作者就写了篇sizeclass解析。非常棒
  • ciechanowski–各种数学上几何变化
  • bignerdranch
  • cocoawithlove–2008-2011的老文章,现在没怎么跟新了
  • codinghorrorJeff Atwood 主站
  • g8production
  • lucida – 我比较佩服一名程序员,想学算法找他推荐咯😁

团体blog

优质iOS学习资源

中文网站

Swift:

备注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com

__autoreleasing 修饰符

将对象赋值给附有__autoreleasing 修饰符的变量等同于ARC 无效时调用对象的autorelease方法。我们通过以下源代码来看一下

1
2
3
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}

该源代码主要将NSObject 类对象注册到autoreleasepool 中,可作如下变换:

1
2
3
4
5
6
/* 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

这与苹果的autorelease 实现中的说明(参考1.2.7 节)完全相同。虽然ARC 有效和无效时,其在源代码上的表现有所不同,但autorelease 的功能完全一样。

在alloc/new/copy/mutableCopy 方法群之外的方法中使用注册到autoreleasepool 中的对象会如何呢?下面我们来看看NSMutableArray 类的array 类方法。

1
2
3
@autoreleasepool {
id __autoreleasing obj = [NSMutableArray array];
}

这与前面的源代码有何不同呢?

1
2
3
4
5
6
/* 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

虽然持有对象的方法从alloc 方法变为objc_retainAutoreleasedReturnValue 函数, 但注册autoreleasepool 的方法没有改变,仍是objc_autorelease 函数。

备注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com

重复发布这个主题已经说明了同编译器保存健康关系的重要性,像任何草稿一样,作为一个实践者的效率取决于他们怎样对待他们的工具,你照顾好它们,它们反过来也会对你有好处。

__attribute__是一个编译器的指令在声明的时候指明了一些参数,这些参数允许更多的检查错误和高级的优化。

语法关键字是__attribute__紧跟2套圆括号(双圆括号让出现的宏更容易辨认,特别是在有多个属性的时候)。在括号内部是一个以逗号分隔的属性列表,__attribute__指令被放在函数,变量和类型声明后面。

1
2
3
4
5
6
7
8
9
10
 // Return the square of a number
int square(int n) __attribute__((const));

// Declare the availability of a particular API
void f(void)
__attribute__((availability(macosx,introduced=10.4,deprecated=10.6)));

// Send printf-like message to stderr and exit
extern void die(const char *format, ...)
__attribute__((noreturn, format(printf, 1, 2)));

假如这个让你想起ISO C语言的 #pragma,你就不会感到孤单了。

实际上,当__attribute__被第一次引入到GCC编译器时,它面临一些阻力,有人建议使用专用的#pragma因为相同的目的。

这里,然而,有2个非常好的理由为什么__attribute__被添加进来

  • 从一个宏中产生#pragma命令几乎是不能的(在C99 _Pragma 预算符以前)。
  • 这里没人知道相同的#pragma在另一个编译器中可能的意思。

引用GCC Documentation for Function Attributes

  • 这里有2个原因被应用到几乎所有的应用推荐使用#pragma,这犯了一个低级错误就是把#pragma使用到任何地方。

确实,假如你在苹果的框架中和牛逼工程师的开源项目中的头文件看一下现代的Objective-c__attribute__被大量使用。(相反,#pragma的主要声明名声来着这些天是装饰:#pragma mark

所以为了以后不费力,我们还是先看一下最重要的属性:


GCC

format

format属性指定了一个函数像printf,scanf,strftime或者strfmo风格的参数,这个参数应该是可以进行类型检查的一个格式化字符串。

1
2
3
extern int
my_printf (void *my_object, const char *my_format, ...)
__attribute__((format(printf, 2, 3)));

Objective-C程序员也可使用__NSString__来格式化来做到相同的格式化规则,像在NNString中通过 +stringWithFormat:NSLog()格式化字符串一样。

nonnull

这个nonnull属性指定了某些函数的参数必须是非空的指针。

1
2
3
extern void *
my_memcpy (void *dest, const void *src, size_t len)
__attribute__((nonnull (1, 2)));

使用nonnull编码期望这个值遵守一个明确的约定中,这样能帮助捕获潜伏在任何代码调用的NULL指针bugs,请记住:
编译时的错误 >> 运行时的错误。

noreturn

一些标准库函数,例如abortexit,是不能返回的。GCC自动知道这些东西,这个noreturn属性用于指定任何其他函数永远不会返回的情况。

例如,AFNetworking 使用noreturn属性在它的网络请求线程进入点的方法里面,这个方法用在当大量产生专用的网络的线程里用来保证分离的线程持续执行在应用的整个生命周期中。

pure/const

pure属性指定了一个函数除了返回值没有副作用,例如它的返回值仅仅依赖参数和/或者全局变量。这样的函数可以用公共子表达式消除并且循环优化就像一个算数操作符那样。

pure属性指定了一个函数不会检查任何值除了它们的参数,并且返回值没有副作用。注意到一个函数有一个指针参数并且需呀检查数据的指向不能声明成const。同样的,一个函数调用一个非nonst函数通常不能为const,一个const函数返回void并没有什么意义。

1
int square(int n) __attribute__((const));

pureconst是两个执行在一个函数式编程惯例中的参数为了允许有效性能优化。const可以被认为是严格形式的pure因为它不依赖全局变量或者指针。

例如,因为一个函数声明为const的结果并不依赖任何东西除了传进来的参数。函数的结果能够缓存那个结果并且当函数被调用时返回,这样的函数叫做相同的组合参数(也就是说,我们知道一个数字的平方是一个常量,所以我们仅仅需要只计算它一次)。

unused

这个属性,附着在一个函数后面,意味着那个函数很可能不会被使用,GCC不会对这个函数产生警告。

__unused关键词可以获得相同的效果,声明这个在方法实现中不会被使用的参数中。知道那以后一些上下文就可以允许编译器来做相应的优化。你很可能喜欢在delegate方法实现李勉使用__unused,因为协议频繁的提高更多的上下文比通常必要的情况,为了满足大量的潜在使用案例。

LLVM

像GCC的很多特征一样,Clang也支持__attribute__,添加到它自己的小范围的扩展。为了检查某个属性的可用性,你可以直接使用__has_attribute属性。

availability

Clang引进了availability属性,这个可以被取代在声明描述的生命周期中声明相对于操作系统的版本。思考对一个简单函数f:的函数声明

1
void f(void) __attribute__((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7)));

availability属性声明f在OS X老虎系统中被引入,在OS X雪豹系统中被弃用,在OS X 山狮系统中被废弃。

这个信息被Clang用来决定什么时候使用f:函数式安全的,例如,假如Clang在OS X 美洲豹系统上编译,调用f()函数将成功。假如Clang在OS X雪豹系统中编译,函数调用将成功但是Clang会发出一个警告指明这个函数被弃用了。最后,假如Clang被引进编译OS X山狮系统的代码,函数调用将失败,因为f()函数已经不再可用了。

availability属性是一个逗号分隔的列表以平台名开始然后引入一些定语列举出生命周期内的重要里程碑事件附加额外的信息(以任何顺序)。

  • introduced:声明被引入的第一个版本
  • deprecated:声明被弃用的第一个版本,这意味着用户应该把这个API移走
  • obsoleted: 声明被废弃的第一个版本,这意味着它将被完全移除并且不能再使用
  • unavailable:声明在这个平台上将永远不可用
  • message:额外的消息将被Clang提供当忽略一个警告或者一个错误在使用一个被弃用或者被废弃的声明。对引导用户替换APIs很有用。

在声明时可以使用多个availability属性,每个对应不同的平台,仅当availability属性对应相应的目标平台被使用的时候,任何其他才将被忽略。假如没有availability 属性指定可用性对现在的目标平台,availability 属性将被忽略。

支持的平台

  • ios:苹果的iOS操作系统。最小的部署目标被指定通过-mios-version-min=*version*或者-miphoneos-version-min=*version*命令行参数。
  • macosx:苹果的OS X 操作系统,最小的部署目标被指定通过-mmacosx-version-min=*version*命令行参数

overloadable

Clang提供对C++函数在C中重载的支持。在C中函数重载被引进使用overloadable属性。例如,一个可能提供一个重载版本的tgsin函数来精确执行相关的标准函数计算float,double,long double的正弦值:

1
2
3
4
#include <math.h>
float __attribute__((overloadable)) tgsin(float x) { return sinf(x); }
double __attribute__((overloadable)) tgsin(double x) { return sin(x); }
long double __attribute__((overloadable)) tgsin(long double x) { return sinl(x); }

请注意overloadable只对函数起作用。你可以重载方法声明在某种范围内通过使用通用的返回值和参数类型,想id或者void *.


上下文是国王当它遇到编译器优化时。通过提供限制在怎样解析你的代码,增加你参数尽可能高效代码的可能性。遇到编译器把你打断,这将是一项奖励。

还有__attribute__并不仅仅对编译器有用:下一个人看代码也将感谢这些额外的上下文。所以多走几英尺远将对你的合作中和接替者或者从现在算2年以后的你(那个时候你已经忘记了所以的事情关于这份代码)自己有用

你付出了多少爱,最终你会得到多少爱。

译者注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com

NSRunLoop是什么?

在Cocoa中,每个线程(NSThread)对象中内部都有一个run loop(NSRunLoop)对象用来循环处理输入事件.

NSRunloop并不真的是一个loop,的apple的文档中 也提到了需要自己写while或者for语句来实现,类似下面:

1
2
3
while(running){
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

何为Run loop 事件源

从字面翻译来看,run loop就是一个运行循环,的确它就是一个处理输入时间的运行循环,为什么需要这样处理,难道没有事件发生的时候让线程空转浪费资源?很明显在有事件发生的时候唤醒线程,没有事件发生的时候让其sleep更好。

下面我还是拿这张百看不厌的图来说事:
runloop

处理的事件包括两类

  • 来自Timer sources的同步事件
  • 来自Input sources的异步事件

1.Time Source. Timer sources deliver synchronous events, occurring at a scheduled time or repeating interval.
苹果文档中有句话需要注意,

Timer sources deliver events to their handler routines but do not cause the run loop to exit.*

创建NSTimer添加到run loop中的时候,这里需要注意的是,NSTimer默认是处于NSDefaultRunloopMode,这也就可以解释为什么如果你在你的控制器中添加了一个timer定时刷新你的界面,而你在拖动视图的时候timer不回fire,因为这个时候你的runloop 是NSEventTrackingRunloopMode,在这个mode下timer不回fire

2.input source input source 主要是一些异步的事件,比如来自其它线程或者其它app的消息。

input source 传递异步事件到其对应的处理函数,并且使runUntilDate(与线程相关联的runloop对象调用)返回

为了能够处理input sourcr,run loops 产生notifications.通过注册成run-loop observers可以接受到这些通知(通过Core Foundation 来注册observers).

RunLoopMode有哪些?

run loop在处理输入事件时会产生通知,可以通过Core Foundation向线程中添加run loop observers来监听特定事件,以在监听的事件发生时做附加的处理工作。

每个run loop可运行在不同的模式下,一个run loop mode是一个集合,其中包含其监听的若干输入事件源,定时器,以及在事件发生时需要通知的run loop observers。运行在一种mode下的run loop只会处理其run loop mode中包含的输入源事件,定时器事件,以及通知run loop mode中包含的observers。

Cocoa中的预定义模式有:

  1. Default模式

    定义NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)

    描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式,这是最常用的run loop mode。

  2. Connection模式

    定义:NSConnectionReplyMode(Cocoa)

    描述:处理NSConnection对象相关事件,系统内部使用,这个mode表明NSConnection对象等待reply,用户基本不会使用。

  3. Modal模式

    定义:NSModalPanelRunLoopMode(Cocoa)

    描述:处理modal panels事件,需要等待处理的input source为modal panel时设置,比如NSSavePanel和NSOpenPanel。

  4. Event tracking模式
    定义:UITrackingRunLoopMode(iOS) NSEventTrackingRunLoopMode(cocoa)

    描述:使用该模式来处理用户界面相关的事件,例如在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。

  5. Common模式

    定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)

    描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes,
    可使用CFRunLoopAddCommonMode方法向Common Modes中添加自定义modes。

    注意这个并不是一个特定的mode,而是一个mode的集合,而runloop必须运行在一个特定的mode下

获取当前线程的runloop mode

1
NSString* runLoopMode = [[NSRunLoop currentRunLoop] currentMode];

NSTimer、NSURLConnection与UITrackingRunLoopMode

NSTimer与NSURLConnection默认运行在default mode下,这样当用户在拖动UITableView处于UITrackingRunLoopMode模式时,NSTimer不能fire,NSURLConnection的数据也无法处理。
NSTimer的例子:
在一个UITableViewController中启动一个0.2s的循环定时器,在定时器到期时更新一个计数器,并显示在label上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad {

label =[[UILabel alloc]initWithFrame:CGRectMake(10, 100, 100, 50)];
[self.view addSubview:label];
count = 0;

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 0.2
target: self
selector: @selector(incrementCounter:)
userInfo: nil
repeats: YES];
}

- (void)incrementCounter:(NSTimer *)theTimer
{
count++; label.text = [NSString stringWithFormat:@"%zd",count];
}

在正常情况下,可看到每隔0.2s,label上显示的数字+1,但当你拖动或按住tableView时,label上的数字不再更新,当你手指离开时,label上的数字继续更新。当你拖动UItableView时,当前线程run loop处于UIEventTrackingRunLoopMode模式,在这种模式下,不处理定时器事件,即定时器无法fire,label上的数字也就无法更新。
解决方法,一种方法是在另外的线程中处理定时器事件,可把Timer加入到NSOperation中在另一个线程中调度;还有一种方法时修改Timer运行的run loop模式,将其加入到UITrackingRunLoopMode模式或NSRunLoopCommonModes模式中。

1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

另外一种是放到NSThread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad{
[super viewDidLoad];
NSLog(@"主线程 %@", [NSThread currentThread]);
//创建并执行新的线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}

- (void)newThread{
@autoreleasepool{
//在当前Run Loop中添加timer,模式是默认的NSDefaultRunLoopMode
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
//开始执行新线程的Run Loop,如果不启动run loop,timer的事件是不会响应的
[[NSRunLoop currentRunLoop] run];
}
}

- (void)timer_callback{
NSLog(@"Timer %@", [NSThread currentThread]);
}

NSURLConnection也是如此,见SDWebImage中的描述,以及SDWebImageDownloader.m代码中的实现。修改NSURLConnection的运行模式可使用scheduleInRunLoop:forMode:方法。

1
2
3
4
5
6
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15];

self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];

参考资料:

备注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com

为什么需要出现tintColor ?

解决以前不方便统一设置视图颜色风格的通点,方便自定义系统控件外观
UIAppearance协议设计有点类似,UIAppearance是为了方便统一 设置一类控件的外观,而tintColor是为方便设置某个控件的外观,或者 说某个容器内所有控件的风格。

像在UIViewController 中,通过这段代码可以实现容器内,所有的子view风格统一化,这样在这个控制器中的所有子view都会以tintColor显示

1
self.view.tintAdjustmentMode = UIViewTintAdjustmentModeNormal

UIViewtintAdjustmentMode需要说明一下,这是一个UIViewTintAdjustmentMode枚举

  • UIViewTintAdjustmentModeAutomatic(着色调整模式自动)
  • UIViewTintAdjustmentModeNormal(着色调整模式正常)
  • UIViewTintAdjustmentModeDimmed(着色调整模式变暗,打开控风格会变成灰白模式)

先看看官方API说明

iOS7以后所有的UIView以及它的子类都新增了一个叫tintColor的接口,方便我们对视图进行颜色调整

注意事项

UIImageView需要设置renderingModeUIImageRenderingModeAlwaysTemplate才能生效。
renderingMode是一个类型为UIImageRenderingMode的枚举

  • UIImageRenderingModeAutomatic (默认渲染模式,自动模式)
  • UIImageRenderingModeAlwaysOriginal(总是绘制原来的图片,不把它当成临时图片来处理)
  • UIImageRenderingModeAlwaysTemplate (总是绘制临时图片,会忽略它原本的颜色信息,也就是根据tintColor生产图片)

UIImageView的使用

1
2
UIImage *image = [UIImage imageNamed:@"xxx.png"];
image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];

tintColor更新

在子类中重写- (void)tintColorDidChange 方法,就可以实现每次更新tintColor的时候调用相关配置

1
2
3
4
5
- (void)tintColorDidChange
{
_tintColorLabel.textColor = self.tintColor;
_tintColorBlock.backgroundColor = self.tintColor;
}

参考链接

序言经过一晚上的折腾,终于从Hexo成功转入Octopress,那么本文就来细说一下如何使用 Octopress+GitHub Pages搭建个人博客

为什么选择 Octopress & Github Pages?

  • 免费且独立。把 Octopress 博客系统搭建到 Github Pages 虽是免费,但不失独立性,即便 Github 全站关闭,你也将有一份本地全站备份,随时可以重新恢复。不必受托管商之气,而且还免费,如果你愿意,甚至可以自行插入广告挣钱。
  • 版本控制。写文章,建网站,做软件都需要修改,但有时候改完了又会后悔,如果有时光机就好了,Git 就是你的时光机。当然如果你不想了解这些看上去很唬人的 IT 名词,只是想写博客的话,请在需要的时候再研究这条的内容。
  • 相对其他托管到 Github 上的博客程序,Octopress 更加成熟易上手。打个比方,Jekyll 可以说是毛坯房,HexoOctopress 算是简装修,但相比 Hexo,Octopress 有更多装修范例和更多熟练的装修工人,更容易获取帮助。当然如果你只想住精装修的房子,那不得不花点钱上 WordPress
  • 使用 Markdown。Markdown 是现在最为流行的轻量级标记语言,也是已故的天才 Aaron Swartz 留给世人最好的礼物,窃以为每个在互联网上发布文章的人都该掌握。
  • 按照官方的说法,Octopress 是个A blogging framework for hackers(「为黑客设计的博客框架」),这很酷,你不觉得吗?

如果你之前没有写过博客,打算开始搭建自己第一个博客的话,其实也不妨试试 Octopress,免费还能学到东西,何乐而不为?

本文是在OS X EI Capitan系统上搭建一个基于Octopress的个人博客系统,记录搭建过程的各种坑,希望对有想搭建个人博客的朋友有所帮助。

本文是建立在你有Shell指令基础和Git操作基础之上,如果不了解的话,需要查阅相关资料。

先解释几个专业术语:

  • Ruby
    • Ruby 是一种编程语言。Octopress 是用 Ruby语言 实现的。我们不需要对它有太多了解,只需要正确安装 Ruby 的环境(Ruby版本必须不低于1.9.3-p0,后面会详细介绍)及按步骤执行指令即可。
  • RubyGems
    • RubyGems(简称 gems)是一个用于对 Ruby组件进行打包的 Ruby 打包系统。它可以用来查找、安装、升级和卸载软件包。我们也是通过它安装Octopress包的
  • RVM
    • RVM 是Ruby Version Manager的简称,是一款 Ruby 语言安装、管理的工具。我们对 Ruby 的操作是通过它的指令完成的。
  • Jekyll
    • Jekyll 一个简单的博客形态的静态站点生产机器。 Jekyll 有一套模板目录,可以将 Markdown文件(或者Textile)转换为静态网页,并生成一个完整的可发布的静态网站。
    • 同时,我们可以将产生的静态网站布置到 GitHub Pages 上,生成个人博客站点。
    • 想了解更多内容可以查看中文文档
  • Octopress
    • Octopress 是基于 Jekyll 的博客框架。他们的关系就像 jQuery 与 js 的关系一样。
    • 它为我们提供了现成的美观的主题模板,并且配置简单,使用方便,大大降低了我们建站的门槛。
  • Git
    • 分布式版本控制工具,跟它有点类似的是SVN,只是两者使用场景不一样。
  • GitHub
    • GitHub 是全球最热的开源社区,程序界的 Facebook。它为我们提供代码托管服务,以及我们搭建博客所需要的 Pages 服务。
  • GitHub Pages
    • GitHub Pages 是 GitHub 提供的一项服务。它用于显示托管在 GitHub 上的静态网页。所以我们可以用 Github Pages 搭建博客,当然我们也可以把项目的文档和主页放在上面。

通过以上内容,我们大概能够明白 Octopress 建站的原理:
我们使用基于 Jekyll 的 Octopress 站点生成工具,生成本地的静态网站。然后将静态网站托管到 GitHub 为我们提供的 GitHub Pages 服务上。访问 username.github.io 即可显示你的个人博客站了


明白了上面这些内容,下面进行具体的搭建工作:

第一步:安装 Ruby

Mac自带Ruby环境打开终端,安装 RVM ,终端执行指令:

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

接下来我们要查看自己的 Ruby 环境

1
$ ruby -v

如果你的 Ruby 版本不低于 1.9.3-p0 可以忽略 Ruby 的安装(或升级),直接跳到安装 RubyGems 。 否则,我们执行之后的操作,终端执行指令:

1
2
$ rvm install 1.9.3
$ rvm use 1.9.3

然后安装 RubyGems, 终端执行指令:

1
$ rvm rubygems latest

到这里第一步完成。我们可以再执行一次第一条指令 ruby -v 来查看当前 Ruby 的版本了。

第二步:安装Octopress

因为Mac系统自动git环境,所以我们不需要考虑git的安装。直接将 Octopress的项目clone到本地,在终端执行指令:

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

完成后进入 octopress 的目录

1
$ cd octopress

接下来,安装依赖:

1
2
3
4
5
$ gem install bundler
# 这时你可能会遇到没有权限的问题,那么我们需要加上sudo重新执行,并输入密码。
$ sudo gem install bundler
# 接下来执行:
$ bundle install

这时你可能还会遇到问题如下:

1
2
3
4
5
Fetching gem metadata from https://rubygems.org/...........
Resolving dependencies...

Gem::RemoteFetcher::FetchError: SocketError: getaddrinfo: Name or service not known (https://rubygems.org/gems/rake-10.4.2.gem)
An error occurred while installing rake (10.4.2), and Bundler cannot continue.

这是因为被墙了,解决办法有两个: 一个是,可以使用自己的翻墙工具; 另一个,淘宝做了一个gem的镜像。我们需要在Octopress的文件目录下找到Gemfile文件,将其中的source 'https://rubygems.org/'改为source 'https://ruby.taobao.org/'

再重新运行bundle install就可以了。 这段内容可以参考 bundle install 提示如下,是需要翻墙解决么

下面就可以安装 Octopress 的默认主题了,终端执行指令:

1
rake install

这样一个最基本的个人博客站就产生了。

Octoress init

安装octostrap3主题(可选)

1
2
3
4
$ cd octopress
$ git clone https://github.com/kAworu/octostrap3.git .themes/octostrap3
$ rake "install[octostrap3]"
$ rake generate

会提示以下信息

1
A theme is already installed, proceeding will overwrite existing files. Are you sure? [y/n]

输入y继续,然后开始安装。。。

1
2
3
4
5
6
7
## Copying octostrap3 theme into ./source and ./sass
mkdir -p source
cp -r .themes/octostrap3/source/. source
mkdir -p sass
cp -r .themes/octostrap3/sass/. sass
mkdir -p source/_posts
mkdir -p public

显示上面的内容完成后没有错误,就安装主题完成


可以看出,现在显示得都是预设值,并不是我们想要的,所以需要修改Octopress目录下的_config.yml文件。

文件目录_config.yml

描述:保存配置数据。很多配置选项都会直接从命令行中进行设置,但是如果你把那些配置写在这儿,你就不用非要去记住那些命令了。

_config.yml 文件共分为3个部分内容

  • Main Configs
  • Jekyll & Plugins
  • 3rd Party Settings

目前,我们只需要关注第一部分Main Configs。

# ----------------------- #
#      Main Configs       #
# ----------------------- #

#网站地址
url: http://wangruofeng.github.io

#网站标题
title: 王若风的技术博客

#网站副标题
subtitle: 天天向上.

#网址作者,通常显示在页尾和每篇文章的尾部
author: Ace

#搜索引擎
#simple_search: http://google.com/search

#网站的描述,出现在HTML页面中的 meta 中的 description
#description:

对应填入你的个人信息,其中url为必填的,一般填GitHub仓库对应的连接,其内容大致就是 username.github.io ,这个地址我们会在后面步骤中获得。

第三步:集成 GitHub Pages

1.注册 Github 账号

这个没什么好说的,早晚需要,去 http://github.com 注册吧。

2.在 GitHub 上创建一个代码参考,项目名称命名规则为 username.github.io,username必须与用户名称一致。

  1. 域名指向(可选)
    如果你有自己的域名可用,可以在这时就配置好,毕竟解析起来需要一段时间,不如在我们搭建博客的时候让它开始,这样我们搭建完成后,基本上就可以直接用自有域名访问了。

我的是在万网申请的,原价一年100多点现在有活动便宜的几块的都有,想要个性域名的可以去注册一个。

  • 如果你用的是顶级域名,比如 wangruofeng007.com, 请创建两个 A 记录 (A Record) 分别指向 192.30.252.153192.30.252.154.

  • 如果你使用二级域名,比如 blog.wangruofeng007.com, 请将该域名的 CNAME 指向 [your_username].github.io, 把其中的 [your_username]换成你自己在 Github 上的用户名。

  • 如果你暂时没有域名,这一步可以暂时不用管

Ps. 在创建过程中最好不要添加忽略文件README文件。因为我们要把本地的 git 仓库同步到 GitHub 远程仓库中。如果再远程仓库中添加了其他文件,需要我们执行 pull 操作。除非你能非常熟练的使用 git ,否则不建议你制造不必要的麻烦。
接下来将本地代码仓库同步到 GitHub 上,执行终端指令:

1
$ rake setup_github_pages

它会要求你绑定远程仓库的地址,此时只需要输入即可:

1
$ git@github.com:username/username.github.com.git

这样就会将 Octopress 生成的静态站点与 GitHub 进行绑定了。

之后我们创建第一篇文章:

1
rake new_post["title"]

然后会有一个名为 yyyy-mm-dd-Post-Title.markdown 的文件在 octopress/source/_posts 目录下生成,其中 yyyy-mm-dd 是你当时的日期。然后执行以下命令:

1
2
cd source/_posts/
vim yyyy-mm-dd-Post-Title.markdown

即可用 vim 编辑器编辑的刚才的文章了,好吧我知道你作为这篇文章的读者并不是一个能熟练使用 vim 的人,那么请在命令行输入 q!退出这个编辑器。如果你不想假装是个黑客的话,其实发布文章并不需要这么麻烦。

我们直接打开 octopress/source/_posts 文件夹,找到刚才生成的文件,用你喜欢的 Markdown 编辑器(免费的我推荐 Mou或者Atom)或者文本编辑器打开,对文章内容进行编辑。

打开文件后,你会发现文章开头有这么一段信息:

1
2
3
4
5
6
7
---  
layout: post
title: "Post Title"
date: yyyy-mm-dd hh:mm:ss
comments: true
categories: ""
---

这其实是这篇文章的元数据:layout 暂时不要理会;title 是这篇文章显示在最终网页上的标题;date 部分是详细的文件生成时间,如 2014-01-28 03:35:00;comment 部分表示是否允许评论,目前显示是允许,如果想关闭评论,请改为 false;categories 指这篇文章的分类目录,请在后面引号中输入,不用引号也可以,多个分类用空格隔开,如果没有该目录,则会自动生成。请不要删除这段信息,在这段信息下面开始你的文章内容

这件事情给我们的启发是,以后发布文章,其实并不需要使用终端命令行生成文件。可以直接将自己写好的文章放到这个文件夹下面,当然请按照 yyyy-mm-dd-Post-Title.markdown 这样的文件格式命名,同时记得在文章前面添加元数据信息。这种做法生成的文章与上面的方法无异

生成的新文章在source/_post/目录下,文件名构成为时间和标题的拼音。我们可以用Markdown编辑器对文章进行修改。
之后生成静态站点,终端执行指令:

1
2
# 生成静态站点
$ rake generate

如果你想预览本地的站点,可以执行终端指令:

1
2
# 预览静态站点
$ rake preview

此时,可以使用浏览器打开localhost:4000 查看效果。如果没有问题可以将静态站点同步到 GitHub 远程仓库中,终端执行指令:

1
2
# 同步内容
$ rake deploy

你会发现我们的静态站点已经被 push 到 GitHub仓库的 master 分支上。稍等几分钟,访问 username.github.io (或者 username.github.com),就会发现你的个人博客站已创建成功了。
如果你还想给自己的本地资源文件(如Markdown文件等内容)也同步到 GitHub 中,可以执行以下指令:

1
2
3
$ git add .
$ git commit -m "comment"
$ git push origin source

这样我们的资源文件就会同步到 GitHub 的 source 分支了。

使用自己的域名(可选)

如果你有自己的域名,并且想指向这个新博客的话,请首先确保执行了第三步的域名指向(可选)的内容。如果没有执行,可以随时执行。
然后执行下面的命令,注意把 your-domain.com 换成你自己的域名。

1
echo 'your-domain.com' >> source/CNAME

这句话的意思是在source分支下创建一个CNAME的文件并且将your-domain.com写入文件

然后再次执行以下命令:

1
2
rake generate
rake deploy

或者二合一

1
rake gen_deploy

这样你就可以使用自己的域名了。域名解析需要一段时间,如果没有马上生效,请不要着急。如果长时间没有生效,请确保完整执行了 域名指向(可选)使用自己的域名(可选)的内容。

现在我们完成了个人博客的初级搭建,足够满足我们的基本需求。

错误处理

出现下面的错误:

1
2
Pushing generated _deploy website Permission denied (publickey).
fatal: Could not read from remote repository.

这个错误是因为缺少SSH keys ,只有拥有这个key才有权限push到远程仓库,通过这种方式实现权限安全控制。
解决方案:Generating SSH keys

出现下面错误:

1
2
3
4
5
6
7
8
## Pushing generated _deploy website
To git@github.com:wangruofeng/wangruofeng.github.io.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:wangruofeng/wangruofeng.github.io.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

原因,是修改了远程仓库,导致本地本地版本落后于远程仓库版本。
最佳解决方案:

1
2
3
4
cd octopress/_deploy
git pull origin master
cd ..
rake deploy

参考链接:rake-gen-deploy-rejected-in-octopress

参考资料:

备注:欢迎转载,但请一定注明出处! http://blog.wangruofeng007.com