Core Animation把view内容存成bitmap传给GPU,通过尽量使用缓存的bitmap,并修改传入GPU的bounds/position/transform/alpha等属性来画图。这就比view的drawRect:高效得多,drawRect:要占用CPU的主线程来画图。

animation状态由layer trees表示,layer tree与view tree层次结构相同:

  • model layer tree:存着animation的target values
  • presentation tree: 存着animation的in-flight values
  • render tree: 管着实际animation,Core Animation私有

presentationLayer当图层第一次在屏幕显示时创建,在此之前是nil

要修改view的底层layer,可重载+ (Class)layerClass;

UIView封装了CALayer,添加了布局和事件处理功能,但有些CALayer的功能没有暴露出来:

  • 阴影、圆角、带颜色的边框
  • 3D变换
  • 非矩形区域
  • 图层蒙版
  • 多级非线性动画

给layer.contents赋值:layer.contents = (__bridge id)image.CGImage;

给layer.mask设置一个mask图层,对应mask图层opacity == 1的部分会被保留下来,其他部分根据opacity变得透明

view的masksToBounds会把超出边界的阴影切掉。为有阴影,可以在外面套一层view,内层view使用mask,外层view使用阴影。

view.transform是CGAffineTransform类型(对应layer.affineTransform),在二维平面的旋转、缩放、平移、切变都是仿射变换(即在变换后平行线仍平行)

通过设置layer.sublayerTransform(CATransform3D类型)的m34元素为-1.0/d可对子图层应用透视效果。d代表想象中视角相机和屏幕的距离,以像素为单位,通常500~1000就好。这样,sublayer.transform(CATransform3D类型)就只要CATransform3DMakeRotation即可,不用再设置m34透视。

在animation时设置transform.rotationtransform.scale等虚拟属性,Core Animation会自动根据CAValueFunction计算的值来更新transform属性

duration是动画一次迭代的时间;若配合迭代次数repeatCount,则动画总时间是duration * repeatCount;若配合动画总时间repeatDuration,则迭代次数是repeatDuration / duration

即使你不显式地用[CATransaction begin]开始一次事务,任何一次runloop循环中属性的改变都会被集中到一个新的CATransaction,然后做一次默认0.25秒的动画。

按Home键退出app时,系统会在layer上调removeAllAnimations

参考

经典的MVC (Model-View-Controller) 里Model负责数据,View负责界面,Controller负责两者之间的交互。但仔细想想,View和Controller虽然是不同的组件,却几乎总是一对一配套使用。在MVVM (Model-View-ViewModel) 中,就把MVC的View和Controller概念合并成一个View/Controller,然后Model-View间多加一层ViewModel。

在ViewModel可以将Model数据转换为View呈现所需数据(如将NSDate格式化成字符串),可以保存View状态,可以做用户输入的验证,可以发收网络请求等。

ViewModel的属性变化通过KVO (Key-Value Observing) 通知Controller,可用facebook的KVOController库。

ps: [photosArray addObjectsFromArray:photos]不会发KVO通知,要用[[self mutableArrayValueForKeyPath:@"photosArray"] addObjectsFromArray:photos];

参考:

touch handling时先hitTest:withEvent:找出hitView:

1
2
3
4
5
6
7
8
9
10
- (UIView *)hitTest:withEvent: {
if /* pointInside:withEvent:, eg. point is in our bounds */ {
for /* each subview, in reverse order (eg. from top to bottom) */ {
hitView = /* recursive call hitTest:withEvent: on subview */
if (hitView != nil) return hitView;
}
return self;
}
return nil;
}

Responder Chain

然后UIWindow就sendEvent:给这个view。hitView遵循responderChain:

  • 若hitView实现了touchesBegan:,不管有没实现其他3个方法:调hitView的touchesBegan:方法,再调hitView的其他3个方法(如果实现了的话)
  • 若hitView没实现touchesBegan:,但实现了其他3个方法:沿chain找到第一个响应touchesBegan:的ancestorView,其他3个方法在沿着chain从[hitView -> ancestorView]的各个view上依次调用。

responderChain基本就是沿着superview转发touch。如果这个superview是viewController.view,那实际上是将touch交给viewController处理。例如,parentView -> parent’s viewController -> window -> application -> applicationDelegate。

但我们不能手动将touch传给nextResponder(下面的写法不太work):

1
2
3
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesBegan:touches withEvent:event];
}

Gesture Recognizer

gestureRecognizer不依赖responderChain。从[hitView -> window]链上的所有gestureRecognizers都可以处理touch。touchesBegan:touchesMoved:会同时发给hitView和[hitView -> window]链上的所有gestureRecognizers。在touchesEnded阶段,如果沿链有gestureRecognizer识别成功就给hitView发touchesCancelled:(默认gestureRecognizer.cancelsTouchesInView == YES),如果识别失败就给hitView发touchesEnded:withEvent:

gestureRecognizer默认不能与其他gestureRecognizers同时识别成功,可以重载delegate方法:gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:

gestureRecognizer的默认属性:

  • cancelsTouchesInView (default YES),识别成功时向touch.view发touchesCancelled:
  • delaysTouchesBegan (default NO),touchesBegan:会同时发往touch.view,若设为YES则得等自己识别失败才把touchesBegan:发往touch.view
  • delaysTouchesEnded (default YES),例如,这样就不会把双击识别成两次单击

UIScrollView

scrollView不管有没实现touchesBegan:等4个方法,都会自己处理touch不会向responderChain转发。这很可能是因为scrollView默认实现了touchesBegan:方法。

webView即使实现了touchesBegan:等4个方法也不会调用到,touch都被隐藏嵌套的_UIWebViewScrollView: UIWebScrollView : UIScrollView)吞掉了,交给干活的子view,如UIWebBrowserView: UIWebDocumentView : UIWebTiledView : UIView)处理。由于webView内部scrollView会吞掉touch,甚至在touchMoved几次后把touch.view变成nil。要对将传给webView的touch做其他处理,可重载UIWindow的sendEvent:,先截获感兴趣的touch做处理,再[super sendEvent:event];走正常流程将touch传给webView。

默认scrollView.delaysContentTouches == YES,即scrollView有个内装定时器的gestureRecognizer,设置有gestureRecognizer.delaysTouchesBegan = YES,要等这小定时器超时gestureRecognizer识别失败才给view转发touchesBegan:消息。当快速滚动scrollView时,panGestureRecognizer立即识别成功,识别成功的gestureRecognizer默认cancelsTouchesInView。由于touchesBegan:消息还没发出,不再发送touchesCancelled:,直接把touchesBegan:消息丢掉。

Others

若target小于44pt不好点中,应重载target的pointInside:withEvent:来扩展可点击区域CGRectInset(self.bounds, -expansion, -expansion)


参考:

wwdc2014#226

  • self-sizing cell for tableView/collectionView

wwdc2014#232

  • Aggregate Data Source
  • 对loading content过程引入state machine

wwdc2014#235

  • 用scrollView实现:下拉出现的view
  • 实现:在不同view间拖放对象

wwdc2014#236

  • Presentation Layer Hit Test
1
2
3
4
5
override func hitTest(point: CGPoint, withEvent event: UIEvent!) -> UIView! {
let superviewPoint = convertPoint(point, toView:superview)
let point = layer.presentationLayer.convertPoint(superviewPoint, fromLayer:superview.layer)
return super.hitTest(point, withEvent:event)
}

wwdc2013#217

  • 实现:内嵌的小scrollView拖动距离超过distance后拖动外部的大scrollView,像iOS锁屏时拖动通知条项反而拖过来九宫密码页

    内嵌scrollView的宽要设为2 * (pageWidth + distance)。若设为 2 * pageWidth,由于被distance占用了一段拖动距离,当内嵌scrollView拖到头时没法把外部scrollView全拖过来。

    设置外部scrollView的contentOffset.x时,内嵌scrollView因为是它的子view也会移动,这样就导致移动距离加倍,需要反方向translate回去

  • 实现:在拖动放手后,内外两个scrollView以不同速率回到同一点

    在内嵌scrollView的scrollViewDidScroll:里按速率比调节外部scrollView的contentOffset.x

  • 实现:快速滑动浏览列表项时的弹簧效果:

    UIDynamic可方便配合UICollectionViewFlowLayout子类

wwdc2012#223

  • 用UIPageViewController实现photo browsing

wwdc2012#228

  • 讲AutoLayout怎么debug

wwdc2012#232

  • 用Instruments调试AutoLayout错误

参考

UIScrollView

  • 2009 - Mastering iPhone Scroll Views: basic
  • 2010 - Designing Apps with Scroll Views: photo browsing, tiling
  • 2011 - Advanced Scroll View Techniques: infinite scrolling, stationary views, interaction with gestures
  • 2012#223 - Enhancing User Experience with Scroll Views: photo browsing 2.0, scrolling with OpenGL, custom deceleration
  • 2013#217 - Exploring Scroll Views in iOS 7: nested scroll views, integration with dynamics
  • 2014#235 - Advanced ScrollViews and Touch Handling Techniques

AutoLayout

AutoLayout基于描述性的constraints:

1
item1.attribute1 = multipler x item2.attribute2 + constant
1
2
3
4
5
6
7
+ (id)constraintWithItem:(id)item1
attribute:(NSLayoutAttribute)attribute1
relatedBy:(NSLayoutAttribute)relation
toItem:(id)item2
attribute:(NSLayoutAttribute)attribute2
multiplier:(CGFloat)multiplier
constant:(CGFloat)constant;

要将constraint加到item1和item2的最近公共祖先 (Least Common Ancestor) 中:

1
- (void)addConstraint:(NSLayoutConstraint *)constraint;

AutoLayout先自底向上updateConstraints,再自顶向下layoutSubviews。layoutSubviews时调view的setCenter:setBounds:

constraint的priority默认1000(required constraint),当priority<1000时表示optional constraint,数值越高优先级越高。

Intrinsic Content Size & Compression Resistance & Content Hugging:

debug

  • ambigulous: multiple layouts satisfy all constraints equally well
  • unsatisfiability: no layout can satisfy all required constraints

查看autolayout情况:

1
(lldb) po [view _autolayoutTrace]

是否ambigulous:

1
[view hasAmbiguousLayout]

换成ambigulous的另一个layout:

1
[view exerciseAmbiguityInLayout]

显示紫色半透明的布局错误提示层:

1
[window visualizeConstraints:@[]]

可设置NSUserDefaults的NSConstraintBasedLayoutVisualizeMutallyExclusiveConstraint为YES,则当布局出错时自动调用[window visualizeConstraints:]

什么constraints导致view的当前大小:

1
[view constraintsAffectingLayoutForAxis:NSLayoutConstraintOrientationHorizontal/Vertical]

转换自autoresizing mask的constraints:h=-&- v=&--h=/v=分别表示horizontal/vertical方向,-表示fixed size,&表示flexible size,3个符号依次表示margin, dimension, margin


Size Classes

iOS 8起,为替代UIInterfaceOrientationUIUserInterfaceIdiom引入了Size Classes

参见:Traits Describe the Size Class and Scale of an Interface

There are two types of size classes in iOS 8: regular and compact. A regular size class denotes either a large amount of screen space, such as on an iPad, or a commonly adopted paradigm that provides the illusion of a large amount of screen space, such as scrolling on an iPhone.

With the amount of screen space available, the iPad has a regular size class in the vertical and horizontal directions in both portrait and landscape orientations.

The size classes for iPhones differ based on the kind of device and its orientation. In portrait, the screen has a compact size class horizontally and a regular size class vertically. This corresponds to the common usage paradigm of scrolling vertically for more information. When iPhones are in landscape, their size classes vary. Most iPhones have a compact size class both horizontally and vertically

default size classes

  • iPad landscape&portait: V Regular | H Regular
  • iPhone portait: V Regular | H Compact
  • iPhone landscape: V Compact | H Compact

Traits

布局相关的属性,如 horizontalSizeClass / verticalSizeClass / userInterfaceIdiom / displayScale,都集中在对象的traitCollection中。

支持布局的对象,如 UIScreen / UIWindow / UIViewController / UIView / UIPresentationController,都实现了UITraitEnvironment协议,可以返回当前的traitCollection。UIViewController / UIView 也可以重载traitCollectionDidChange:来监听traitCollection的改变。

traitCollection中未指定的trait就是Unspecified的。若traiCollection A中所有已指定的trait都与traitCollection B中的相等,则说A包含于B。两个traitCollection还能合并,X和Unspecified合并成X,值有冲突的(如Compact和Regular)保留合并进来的那个值。

参考

1
#import "objc/runtime.h"

Runtime术语

objc_msgSend的方法定义:

1
id objc_msgSend(id self, SEL op, ...);

SEL

1
typedef struct objc_selector *SEL;

SEL即selector,本质是方法名的常量字符串,所以同名方法就是同一个selector

id

1
typedef struct objc_object *id;

而objc_object为:

1
struct objc_object { Class isa; };

Class

1
typedef struct objc_class *Class;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;

int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}

Ivar

1
typedef struct objc_ivar *Ivar;
1
2
3
4
5
6
7
8
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}

Method

Method代表类中某个方法:

1
typedef struct objc_method *Method;
1
2
3
4
5
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
* 方法名SEL本质是个常量字符串 * 方法类型是个char指针,存储着方法的参数和返回值类型编码 * 方法实现IMP是个函数指针

IMP

1
typedef id (*IMP)(id, SEL, ...);

该函数指针指向的方法与objc_msgSend函数类型相同

Cache

1
typedef struct objc_cache *Cache
1
2
3
4
5
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};

Runtime会把被调用的方法存到Cache中,为方法调用进行性能优化。当实例对象接收到一个消息时先在Cache中查找,若未命中再去isa指向的类的方法列表中遍历查找。

Property

1
2
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t; //这个更常用

可以通过class_copyPropertyListprotocol_copyPropertyList方法来获取类和协议中的属性,返回类型是指向objc_property_t数组的指针:

1
2
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
可以用property_getName函数来查找属性名称:
1
const char *property_getName(objc_property_t property)
可以用class_getPropertyprotocol_getProperty通过给出的名称来在类和协议中获取属性的引用:
1
2
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
可以用property_getAttributes函数来获得属性的名称和@encode类型字符串:
1
const char *property_getAttributes(objc_property_t property)
把上面的代码放一起,就能从一个类中获取它的属性:
1
2
3
4
5
6
7
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}


消息传递

objc_msgSend的流程

  1. 检测selector可不可以忽略,比如Mac开发中开启GC就会忽略retain/release调用
  2. 检测target是不是nil,ObjC的会忽略向nil发的消息
  3. 在target的Class中根据selector查找IMP
    1. 先从cache里面找,找得到就跳到对应的函数去执行
    2. cache里找不到就去找Class的方法列表
    3. 还找不到,就沿着继承链找超类的方法列表,直到NSObject类为止
    4. 实在找不到,进入动态方法解析和消息转发机制

方法的隐藏参数

每个方法都有两个隐藏参数:self为接收者对象,_cmd为方法本身的selector

获取方法地址

NSObject中有个methodForSelector:实例方法,可以用来获取selector对应的IMP:

1
2
3
void (*setter)(id, SEL, BOOL);
setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for (int i = 0; i < 1000; i++) setter(targetList[i], @selector(setFilled:), YES);
这种做法很少用,除非需要持续大量重复调用某方法,才节省些消息传递开销

动态方法解析

@dynamic修饰属性:

1
@dynamic propertyName;
编译器不会为我们生成setPropertyName:和propertyName方法。当Runtime在Cache和继承链的所有方法分发列表中都找不到方法时,会调用resolveInstanceMethod:resolveClassMethod:来给程序员一次动态添加方法实现的机会,需要用class_addMethod函数向某类添加方法实现:
1
2
3
4
5
6
7
8
9
10
11
12
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
上述例子为MyClass添加实例方法resolveThisMethodDynamically,其中 “v@:” 表示返回值和参数,参见Type Encoding

动态方法解析在消息转发机制之前执行,要让消息进入转发机制,就让resolveInstanceMethod:返回NO

消息转发

重定向

在消息转发之前,还有一次偷梁换柱的机会,通过重载forwardingTargetForSelector:可以替换消息的接收者:

1
2
3
4
5
6
- (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}

转发

在对象无法正常响应消息时才会调用forwardInvocation:,重载这个方法来定义任何你想要的转发逻辑:

1
2
3
4
5
6
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
forwardInvocation:之前,会先调用methodSignatureForSelector:来生成这里的anInvocation参数,所以在重写forwardInvocation:之前还要重写methodSignatureForSelector:,否则会因找不到signature而抛异常

转发可以实现类似多继承的效果,把消息转发给另一个对象,就好像借用或“继承”了另一个对象的方法

替代者对象

尽管转发能模拟多继承,但respondsToSelector:isKindOfClass:等方法只会考虑继承链,不会考虑转发链。

如果处于某种意图需要模拟继承,得重载respondsToSelector:isKindOfClass:instancesRespondToSelector:来加入转发逻辑:

1
2
3
4
5
6
7
8
9
10
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector])
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
如果使用了协议,也得重载conformsToProtocol:

同样地,转发还得重载methodSignatureForSelector:,比如一个对象为给它的替代者对象转发消息,需要实现:

1
2
3
4
5
6
7
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}


Assiciate Objects

给category添加变量涉及下面三个函数:

1
2
3
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
其中用来作key的是个内存地址,可直接用某个selector:
1
2
3
4
// NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
1
2
3
4
5
6
7
8
9
10
// NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;

- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, @selector(associatedObject));
}


几个细节

[super class] == [self class]

[self class]会转成

1
id objc_msgSend(id self, SEL op, ...)
向self发消息,从self的方法列表开始,沿继承链向上找@selector(class)的实现。由于没重载过这个方法,最终调用NSObject中的实现。

[super class]会转成

1
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

1
2
3
4
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};

其中receiver就是self,super_class就是实际超类。同样向self发消息,但从超类的方法列表开始,沿继承链向上找@selector(class)的实现。由于没重载过这个方法,最终调用NSObject中的实现。

Object & Class & MetaClass

Class也是个对象,Class的类叫MetaClass。有几个特点:

  • MetaClass的superclass继承链,和对应的原本Class的superclass继承链类似;只有根类特殊:NSObject的superclass为nil,MetaClass-of-NSObject的superclass为NSObject
  • Class的isa指向自己的MetaClass,所有MetaClass的isa指向MetaClass-of-NSObject

Category的加载

Category中的方法在加载时被加入对应的方法列表中:实例方法加入Class的方法列表, 类方法加入MetaClass的方法列表

如果有多个Category,则按加载的逆序把方法加入最终方法列表。比如某类O,按序加载了Category A和B,则最终方法列表是:[B中的方法, A中的方法, O中的方法]


参考

证书颁发机构(CA) 和 客户端的证书验证

背景:

  • 非对称加密:有一对公钥秘钥,公钥加密的数据只能通过密钥解密,密钥加密的数据只能通过公钥解密
  • 客户端含有CA机构的根证书R,根证书R里含:CA机构的公钥pk
  • 服务端含有CA机构颁发的证书A,证书A里含:证书内容F,用CA机构的私钥sk加密F所得的证书加密内容Fs

过程:

客户端与服务端建立https连接时,服务端将证书A返回给客户端;客户端用根证书R里的公钥pk去解密证书A里的证书加密内容Fs,若能还原得F则证书验证通过。

整个流程大致是:

1
2
|--- CA机构 ---|--- 客户端 ---|
F->CA私钥加密->Fs->CA公钥解密->F

SSL Pinning

客户端直接保存服务端的证书,建立https连接时客户端直接对比服务端返回的和自己保存的两个证书是否一致

不需要CA机构了(CA机构颁发证书较贵),可以自己颁发证书

适用于非浏览器应用,比如CS架构的app。因为浏览器跟很多未知服务器打交道,无法保存每个服务器的证书

中间人虽然可以从客户端取出证书伪装成服务器并通过证书验证,但后续的数据通讯客户端会用证书中的公钥加密,中间人没有私钥无法解密

SSL的握手过程

  • 客户端和服务端一共同步三个随机数以生成最后的session key。前两个随机数client random和server random明文传输,第三个随机数pre-master key用服务器的公密钥加解密(非对称加密)

  • 握手完成后的通信是普通的http协议,只是都用session key加解密(对称加密)

  • 服务器公钥是放在数字证书中传给客户端的,这是为了保证公钥不被篡改。只要证书是可信的,公钥就是可信的


参考

block

  • block分stack/heap/global:block通常是其创建时所在作用域的栈变量,出了作用域这块内存可能被释放可能不释放;通过copy后block变成堆变量,使用引用计数;未引用任何外部变量和参数的block是全局变量,copy无用也不dealloc。

  • block的参数列表为空时是接受_可变参数_,void (^block)();void (^block)(void);不同。

    参见:ObjC非主流代码技巧

  • block中避免循环引用self:先在block外:

    1
    __weak __typeof(self) weakSelf = self;
    然后在block中:

    1
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    注意:block中的_ivar会隐式引用self(即self->_ivar),要改成weakSelf->_ivar或weakSelf.ivar。更优雅的方法是使用ReactiveCocoa的libextobjc中的@weakify(self)@strongify(self)

  • 在block中要小心使用NSAssert,因为NSAssert宏展开中使用了self很可能导致循环引用,可用NSCassert

selector

  • performSelector延迟调用可用dispatch_after()替代,如:

    1
    2
    3
    4
    5
    // [self performSelector:@selector(doSomething) withObject:nil afterDelay:5.0];
    dispatch_time time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^(void){
    [self doSomething];
    });

  • performSelector调用的方法至多接收两个id参数,要调用其他签名形式的方法可使用NSInvocation实现。比如,为调用类似- (void)drawPoint:(CGPoint)point;形式的方法,可写工具类:

    1
    2
    3
    4
    5
    6
    7
    8
    + (void)performSelector:(SEL)selector atTarget:(id)target withArgument:(void *)argument {
    NSMethodSignature *signature = [target methodSignatureForSelector:selector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = target;
    invocation.selector = selector;
    [invocation setArgument:argument atIndex:2];
    [invocation invoke];
    }
    参见:NSInvocation更详细用法

  • 当object收到未知消息时会调用- (void)forwardInvocation:(NSInvocation *)anInvocation,因此有机会转发消息。

    参见:Message Forwarding

预处理指令

  • 为确保文件被引用时开启ARC,添加

    1
    2
    3
    #if ! __has_feature(objc_arc)
    #error This class is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
    #endif

  • #pragmasupress warning:

    1
    2
    3
    4
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wxxx"
    // ...
    #pragma clang diagnostic pop

    • category中方法重载的warning:-Wobjc-protocol-method-implementation

    • 调用deprecated方法的warning:-Wdeprecated-declarations

    pragma的其他用法见Pragmas Aren’t Just For Marks

其他

  • 在category中使用Method Swizzle,要在+load()中swizzle,在+initialize()中调用会因category的initialize()和原类中被重载的initialize()的两次调用互相抵消。

  • 混用ObjectiveC和C++

  • NSMutableArray的底层实现__NSArrayM是个循环数组,所以在首尾两端插入删除无需移动元素,最坏情况在中间插入删除要移动n/2个元素。插入元素时若空间不足,则空间按1.625倍增长,空间增长后即使删除元素空间也不再变小。见Exposing NSMutableArray

  • 给类添加类似array的index存取功能,实现

    1
    2
    - (id)objectAtIndexedSubscript:(NSUInteger)idx;
    - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;

    添加类似dict的key存取功能,实现

    1
    2
    - (id)objectAtKeyedSubscript:(id <NSCopying>)key;
    - (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;

  • release build中去掉NSLog,在Prefix.pch中添加:

    1
    2
    3
    #ifdef NDEBUG
    #define NSLog(...) /* suppress NSLog for release mode */
    #endif

  • non-retaining array/set for delegates:

    1
    2
    3
    4
    CFArrayCallBacks callbacks = { 0, NULL, NULL, CFCopyDescription, CFEqual };
    NSMutableArray *noRetainArray = (NSMutableArray *)CFArrayCreateMutable(NULL, 0, &callbacks);

    NSMutableSet *noRetainSet = (NSMutableSet *)CFSetCreateMutable(NULL, 0, NULL);

  • 倒序数组:

    1
    array.reverseObjectEnumerator.allObjects`

  • 快速遍历含不同类型x值的集合:

    1
    2
    id<NSFastEnumeration> collection = values;
    for (id object in collection) { … }

  • 作为dict键值的object要遵循<NSCopying>,如果不遵循又要做键值,使用

    1
    [NSValue valueWithNonretainedObject:object]]

  • 使用block遍历collection:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Array
    NSArray *array = /* ... */;
    [array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    }];
    // Dictioinary
    NSDictionary *dict = /* ... */;
    [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
    }];
    // Set
    NSSet *set = /* ... */;
    [set enumerateObjectsUsingBlock:^(id object, BOOL *stop) {
    }];

    此外还有带options的版本,如:enumerateObjectsWithOptions:usingBlock:

    通过options可指定是否并行迭代NSEnumerationConcurrent,是否反向迭代NSEnumerationReverse

  • 使用NSURL.h、NSPathUtilities.h、NSURLComponents操作url

  • 使用NSByteCountFormatter美化下载字节计数:[formatter stringFromByteCount:byteCount]

  • 只在某.m中使用的常量:

    1
    2
    // .m
    static const NSString *kStringConstant = @"VALUE";`
    全局常量:

    1
    2
    3
    4
    // .h
    extern NSString *const EOCStringConstant;
    // .m
    NSString *const EOCStringConstant = @"VALUE";

  • 使用宏NS_ENUMNS_OPTIONS来定义指定类型的枚举

  • 使用NSCache而不是NSDictionary来cache,可在系统内存不足时自动清理

  • switch语句处理枚举值时不要实现default语句,这样当添加新的枚举值后,编译器会警告你没有处理完所有枚举值

python中的yield将function变成generator(一种iterator),用于多次调用生成一系列值(一次调用生成系列值中的一个)。yield作用类似于return,但只是放弃(yield)执行权,函数内部状态(变量和执行情况)保持不变。

参考:‘yield’ and Generators Explained

注:ruby中的yield类似拉环,拉出函数附带的block来执行。


若在.sh文件里执行python脚本报ImportError错,而在shell里直接执行没问题,要设置在.sh里export PYTHONPATH,如:

1
export PYTHONPATH=/usr/local/lib/python2.7/site-packages:$PYTHONPATH

Unicode

type()查看变量是’unicode‘还是’str’,unicode(str, 'utf-8')返回str的utf-8表示

注音字母的Unicode有两种等价形式

  • 标着注音的字母(即composed形式): 又分canonical的NFC和compatable的NFKC
  • 字母和注音(即decomposed形式): 又分canonical的NFD和compatable的NFKD

unicodedata库来normalize

1
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-
import unicodedata

data = u'naïve café'
normal = unicodedata.normalize('NFKD', data).encode('ASCII', 'ignore')
print normal

# prints "naive cafe"

scrapy

  • recursively crawling a website with Python and Scrapy

  • 页面解析用pyquery(内部使用lxml)

  • 解析js:

    1
    2
    3
    4
    5
    from selenium import webdriver
    browser = webdriver.PhantomJS()
    browser.get(url)
    print browser.page_source
    browser.quit

  • downloader下载url,spider解析Item,pipeline对Item做后续处理

twisted

  • ios抓包用charles

  • 监听iPhone上的https请求,要先在iPhone上用Safari访问安装证书

  • Error Messages should be of the form:

      [Noun] Can't x because y. [Something is true. Try a thing.]

CocoaPods

  • 安装:sudo gem install cocoapods

  • 使用:在项目目录下建个Podfile,内容如

      platform :ios, '7.0'
      inhibit_all_warnings! #禁止pods里的warnings
      pod 'Reachability',  '~> 3.1.1'

    然后pod install,打开生成的.xcworkspace来使用

    参考:管理包依赖管理本地包Podfile格式

静态库

  • 创建静态库,参见tutorial

    • 若静态库中用到c++而父项目中没用到,则要在父项目的Other Linker Flags中添加-lc++(静态库使用libc++)或-lstdc++(静态库使用libstdc++),参见StackOverflow
    • 静态库只是.o文件的集合,用到的framework都得在父项目中重新link一遍
  • 将静态库的SL.xcodeproj拖进父项目作为子项目的步骤:

    1. 父项目的Target DepedenciesLink Binary With Libraries中添加libSL.a
    2. 父项目的Other Linker Flags中添加-ObjC > -ObjC causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.
      参见QA1490
    3. 父项目的Header Search Paths中添加静态库头文件所在的目录,记得选中recursive
    4. 父项目的Link Binary With Libraries中添加子项目用到的库

    参见:Sub-Projects in Xcode

  • ios上的framework只是对静态库及头文件的封装,动态库因security问题在appstore上被禁使用,所以ios上的framework就是static framework