KVO的原理

翻译自Mike Ash大神的blog:How Key-Value Oberserving Works

KVO是什么?

尽管大多数读者可能已经知道KVO,但是这里做一个快速总结:KVO是构成Cocoa Bindings基础的技术;当对象的属性变化时,它可以通知对象。
一个对象观察另一个对象的key。当被观察的对象改变那个key的值时,观察者就会得到通知。非常简单,对不对?微妙所在是通常情况下,KVO不需要在被观察者的类中添加代码。

iOS Core Animation:Advanced Techniques 笔记

1、每个UIView都有一个CALayer类型的属性layer

2、iOS有2种平行体系UIView和CALayer的原因是:区分开责任,同事避免重复的代码。例如iOS和Mac OS X

3、在UIView中没有暴露出CALayer的功能有:

①、阴影、圆角、有颜色的边框。
②、3D旋转和位置变换
③、非矩形边框
④、内容蒙板
⑤、多步骤、非线性的动画

4、对应UIView中的contentMode,CALayer中的是contentsGravity。

5、如果不需要后备图片,就不需要浪费内存和cpu时间去生成它。所以苹果建议如果不打算做任何普通的绘制,就不要在类中留下空的drawRect方法。

6、CALayer没有响应链的概念,所以它不能用来处理触摸事件、手势事件。但是,它有2个方法来帮助实现触摸事件,分别是containsPoint:和hitTest

当调用hitTest方法时,测试的顺序是严格按照layer树中layer的顺序。zPosition属性可以用来影响layer层在屏幕上的显示顺序,但不会影响触摸事件的处理顺序。

7、当属性在动画block外面变更的时候,UIView通过对属性行为返回nil禁用隐式动画。

UIView关联图层禁用了隐式动画。唯一在这个图层进行动画的方法是运用UIView动画方法(而不是依赖CATransaction),继承UIView,覆盖-actionForLayer:-forKey:方法,或者创建一个显式动画。

对于单独图层,可以通过实现-actionForLayer:-forKey:图层代理方法,或者提供一个actions词典。

Effective Objective-C 2.0 笔记6

六、Blocks和GCD

①、block是在用栈上的内存来定义的。意思是只有在它的定义的作用域内,block才是有效的。

例如,下面的代码就是错误的:

1
2
3
4
5
6
7
8
9
10
11
void(^block)();
if(/*some condition*/) {
block = ^{
NSLog(@"Block A");
};
} else {
block = ^{
NSLog(@"Block B");
};
}
block();

两个block在if-else域内都是用栈内存定义的。当对每个block进行申请内存操作,就会重写覆盖在作用域结尾处申请的内存。所以,只有在每个if-else模块内,block才能确保是正确的。这个代码可以编译成功,但是在运行时,可能会出现错误。因为无法确定执行的是哪个block。
为了解决这个问题,可以向block发送copy消息,block就会从栈(stack)复制到堆(heap)上。此时,block就可以在它定义的作用域外面使用。block也会变成为一个引用计数的对象。所以上面的代码例子,修改一下,就变为正确了。

1
2
3
4
5
6
7
8
9
10
11
void(^block)();
if(/*some condition*/) {
block = [^{
NSLog(@"Block A");
} copy];
} else {
block = [^{
NSLog(@"Block B");
} copy];
}
block();

②、performSelector家族的方法有潜在的内存管理方面的危险。因为无法确定什么方法被执行,ARC编译器就无法插入正确的内存管理方法。另外,在返回类型和可传递的参数个数方面,也是个限制。

Effective Objective-C 2.0 笔记5

五、内存管理

①、当一个对象调用autorelease时,它就会在稍后自行释放,通常是在2个事件loop之间。

②、在ARC下,仍然需要手动清理非Objective-C对象。例如:CoreFoundation对象或者堆上malloc申请的对象。

③、不需要调用[super dealloc]。

在生成和运行.cxx_destruct方法的过程中,系统自动调用了super dealloc。所以在ARC下,delloc方法可能是这个样子:

1
2
3
4
- (void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocatedMemoryBlob);
}

④、在ARC下,系统对于retain、release、autorelease方法进行了优化,使它们不进行Objective-C的消息分发。

⑤、在ARC下,使用try catch是很危险的行为。

因为ARC不会对异常的代码进行内存管理。除非增加大量样板代码来处理这个情况,但是这样没有异常发生又会降低运行时的性能,同时也会显著的增加应用的代码量。

⑥、一个对象引用它不拥有的对象的例子:代理模式。

⑦、autoreleas pool可以嵌套。当新增一个autorelease对象时,是加到最内层的。

⑧、autorelease pool是在栈中申请的,当对一个对象发送autorelease消息时,它就被加到autorelease栈的栈顶。

⑨、不用对NSString、NSNumber进行retainCount操作。

首先,看个例子:

1
2
NSString *string = @"Some string";
NSLog(@"string retainCount = %lu",[string retainCount]);

1
string retainCount = 18446744073709551615

这个数字是2^64-1。因为NSString是单例对象。string是一个编译时常量。这种情况下,编译器会制作一个特殊对象,在应用的二进制文件中替换NSString的数据。

NSNumber也是类似。用标签指针(tagged pointer)来区分各种类型的数值。在这种模式下,没有NSNumber对象。指针本身的值包含所有关于数字的消息。运行时在消息分发过程中发现一个标签指针,它会装作像一个NSNumber对象一样来操作指针的数值。

Effective Objective-C 2.0 笔记4

四、协议和类别

①、delegate要用weak修饰,因为可以避免互相引用。

②、利用category可以使继承关系变为可控制的段。

③、扩展里可以添加私有方法、私有属性。

④、在扩展里添加类的实例变量。

⑤、利用分类来提供一个匿名对象。它可以生成一个没有名字的inline(内联)类。

⑥、NSDictionary中键值key是copy的,而值value是retain的。

Effective Objective-C 2.0 笔记3

三、接口和API设计

①、处理一些从网络服务获得的数据时,对应的Model类中对外可见的属性要设为只读,只暴露需要暴露的属性。

②、一种处理方式:在.h文件中,属性设置为readonly;.m文件中,对应的属性设置为readwrite。

这样的话,就可以在内部进行读写数据操作。但是同时,KVC还是可以设置属性的。因为,即使没有在.h文件中,KVC还是会直接查找有没有setXXX方法。

③、为可变集合提供方法,而不是把可变集合作为一个直接暴露属性。

④、不要是用下划线作为方法前缀,因为下划线是系统保留字。

⑤、为什么不提倡使用异常(Exception)?

ARC下,异常不是默认安全的。如果在作用域中发生异常,那么后面因该被释放的对象不会被释放。同理,在非ARC下,如果异常在对象释放前发生,那么这个对象永远不会被释放。

⑥、实现copying协议,需要实现单例方法- (id)copyWithZone:(NSZone *)zone

[NSMutableArray copy] => NSArray;

[NSArray mutableCopy] => NSMutableArray

⑦、NSCopying协议中的复制都是浅复制。

Effective Objective-C 2.0 笔记2

二、对象、消息和运行时

①、直接访问实例变量还是使用属性方法访问?

(1)、直接访问实例变量速度快,因为不经过Objective-C的方法分发系统,而是直接用的变量在内存中的存储地址。

(2)、同时,直接访问会忽视属性property的内存管理语法。例如,如果一个属性定义为copy,直接访问就会忽视copy,不会产生一个复制。

(3)、直接访问实例变量,不会出发KVO通知。因为KVO是改写了别观察属性的set方法,在变化前和变化后,发送通知。具体文章可以猛击这里。一个妥协的解决方案是,读数据时采用直接访问方式,写数据时采用属性方法setXXX方式。这样,既可以保证读取速度,又能保证写数据时的内存管理策略的正确性。

(4)、在initializers和dealloc等方法中,用直接访问的方式来读、写实例变量。

(5)、当属性是懒加载模式时,需要用属性方法getXXX来读取。

1
2
3
4
5
6
- (NSString *)name {
if (!_name) {
_name = @"测试名字";
}
return _name;
}

②、对象相等,必须有相同的hash值。拥有相同hash值的对象,不必相等。

③、类簇模式是抽象工厂模式的一种。

④、消息转发

(1)、编译器会把方法转成一个标准的C函数void objc_msgSend(id self,SEL cmd,…),其中第一个参数是接收者,第二个参数是selector,即方法。

(2)、方法调用的过程:首先,会在类的方法列表中查找方法。如果找不到,则从继承链上的父类方法中查找。如果还找不到,则进行消息转发。

(3)、objc_msgSend会缓存已经用过的方法,以方便以后快速调用。即使如此缓存,还是比静态绑定慢。实际情况中,消息分发系统不是应用速度的瓶颈所在。

(4)、消息转发示意图



其中每一步消息接收者都有一次机会来处理消息。越往后,所花费的开销就越大。

(5)、从集合类中取出元素的时候,会用到Objective-C的内省方法。因为取出的元素不是强类型,通常是id类型。在编译时,对象的类型是未知的,就需要使用内省方法。比较对象时,要用内省方法,而不是直接比较。因为对象可能实现了消息转发。例如,NSProxy。

Effective Objective-C 2.0 笔记1

最近看了一遍《Effective Objective-C 2.0:52 Specific Ways to Improve Your iOS and OS X Programs》英文版,感觉受益匪浅,学到了很多以前不了解或者不清楚的知识点。因为英文略让人捉急的原因,所以打算再看一遍这本书,同时看的过程中也做一些笔记。

一、概况

①、Objective-C对象都是在堆上申请的,而不是栈上。

②、不在.h文件中导入,而是在.m文件中导入头文件。

这样的原因是可以限制其他使用当前类的导入范围。例如:

1
2
3
4
5
6
7
// EOCPerson.h
#import <Foundation/Foundation.h>

@class EOCEmployer;
//EOCPerson.m
#import “EOCPerson.h"
#import “EOCEmployer.h"

这样,其他需要导入EOCPerson.h的类,就不需要导入EOCEmployer.h文件了。同时,也可以尽量的避免互相引用。

③、用类型常量代替#define来定义常量,因为前者会带有类型信息。

如果只在当前类中使用,只需在.m文件中定义static const AClass kVarible = a。如果需要暴露给其他类使用(例如,通知的名字),.h文件 extern NSString const AStringConst;
.m文件 NSString
const AStringConst = @“a”

最近积累的几个Objective-C知识点

1、__unused修饰符

    __unused宏(事实上是attribute((unused))这个GCC的编译器属性,更多请参考mattt大神的博客)告诉编译器“如果我没用到这个变量,不要警告我”。

2、三元表达式的简写

?:是C中唯一一个三目运算符,用来替代简单的if-else语句,同时也是可以两元使用:

1
2
NSString *string = inputString ?: @"default"; 
NSString *string = inputString ? inputString : @"default"; // 等价

3、如果block的参数列表为空的话,相当于可变参数(不是void)。

4、@compatibility_alias:允许现有类有不同的名称做别名。比如PSTCollectionView使用@compatibility_alias来显著提高对UICollectionView的向后兼容的直接替换的使用体验。(具体可参考NSHipster的文章)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 允许代码使用UICollectionView如同它可以在iOS SDK 5使用一样。
// http://developer.apple.com/legacy/mac/library/#documentation/DeveloperTools/gcc-3.3/gcc/compatibility_005falias.html
#if __IPHONE_OS_VERSION_MAX_ALLOWED < 60000
@compatibility_alias UICollectionViewController PSTCollectionViewController;
@compatibility_alias UICollectionView PSTCollectionView;
@compatibility_alias UICollectionReusableView PSTCollectionReusableView;
@compatibility_alias UICollectionViewCell PSTCollectionViewCell;
@compatibility_alias UICollectionViewLayout PSTCollectionViewLayout;
@compatibility_alias UICollectionViewFlowLayout PSTCollectionViewFlowLayout;
@compatibility_alias UICollectionViewLayoutAttributes PSTCollectionViewLayoutAttributes;
@protocol UICollectionViewDataSource <PSTCollectionViewDataSource> @end

@protocol UICollectionViewDelegate <PSTCollectionViewDelegate> @end
#endif

5、attribute((constructor))

    若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行

6、看到QQ群里讨论,GCD最多能创建多少线程

    66=64(GCD线程池的最大值) + 主线程 + 其他随机非GCD线程。来源

7、在Darwin层建立Notification监听的方法

    在锁屏和解锁的时候,iOS系统会发送通知,经过搜索,得知大概有下面3种通知:

(1)、com.apple.iokit.hid.displayStatus

锁屏后通知会发出消息,在屏幕变亮后,没有滑动解锁,系统也会发出该通知。

(2)、com.apple.springboard.lockstate

在系统锁屏和滑动解锁后,会发出该通知

(3)、com.apple.springboard.lockcomplete
锁屏后,发出该通知。这个通知总会在(2)通知后发出。

判断屏幕是否有锁的方法

1
2

CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, displayStatusChanged, CFSTR("com.apple.iokit.hid.displayStatus"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);

1
2
3
4
5
6
7
8
static void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
NSLog(@"event received!");
uint64_t state;
int token;
notify_register_check("com.apple.iokit.hid.displayStatus", &token);
notify_get_state(token, &state);
notify_cancel(token);
}

3、宏定义

1
#define nil __DARWIN_NULL

nil用于表示空的实例对象

1
#define Nil __DARWIN_NULL

Nil用于表示空类对象

Swift学习笔记

一、

    在Objective-C的

1
[self performSelector:@selector(updateStatus)   onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];

在Swift中是没有performSelector方法的。替代方法有2种

1、设置Timer定时器

1
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("updateStatus"), userInfo: nil, repeats: false)

2、利用GCD定时器

1
2
3
dispatch_after(1, dispatch_get_main_queue(), {
// your function here
})

二、

1
text.boundingRectWithSize(CGSizeMake(w, CGFloat.max), options: NSStringDrawingOptions.TruncatesLastVisibleLine | NSStringDrawingOptions.UsesLineFragmentOrigin | NSStringDrawingOptions.UsesFontLeading, attributes: attribute, context: nil)

    这段正常的代码在iOS8.1、Xcode6.1下会报错。搜了一下发现这个。意思是说当吧target设为OS X 10.10时,就正常。如果设为iOS8 SDK,就会警告异常。NSStringDrawingOptions被Swift作为enum:Int类型,而不是struct:RawOptionSet,这是Apple的bug。PS:我第一次向Apple报告iOS SDK的bug。

    同时,这里给出了一个解决方法。就是在Objective-C中写好多个option的连接方法,然后在Swift中调用该方法。

1
2
3
4
+ (NSStringDrawingOptions)combine:(NSStringDrawingOptions)option1 with:(NSStringDrawingOptions)option2
{
return option1 | option2;
}

1
let boundingRect = "string".boundingRectWithSize(size, options: StringDrawingOptions.combine(.UsesLineFragmentOrigin, with: .UsesFontLeading), attributes:nil, context:nil)

记录碰到的几个问题

1、在ASIFormDataRequest的方法中,使用block的一个问题

1
2
3
4
       __weak ASIFormDataRequest  *request = [ASIFormDataRequest requestWithURL:url];
[request setCompletionBlock:^{
[request xxMethod];
}

[UIDeviceRGBColor CGColor]: message sent to deallocated instance xxx

    近期开发过程中,遇到一个“[UIDeviceRGBColor CGColor]: message sent to deallocated instance xxx”的bug,google了很长时间也没找到原因所在。后来,发现 点击此处,这里面所遇到的bug和我的类似。其中,这段话


When setting buttonColor = bc without retaining, buttonColor will become a dangling pointer when the current autorelease pool flushes (assuming it’s not retained elsewhere).

[self setNeedsDisplay] will invoke drawRect: later and at that point, buttonColor may already have been deallocated which will crash your app when referring to it.


说明问题所在:当设置完color后,是自动释放的(autorelease)。下次,调用 [self setNeedsDisplay]后,调用drawRect 方法时,里面的color已经被释放了。所以我的解决办法是在设置 self.color = color 后,紧接着
[self.color retain]。

参考地址:http://www.91r.net/ask/11318138.html

xcode中引入两个静态库冲突'duplicate symbol'的解决方法

    在开发过程中,可能需要引入第三方的静态库,有时候会出现duplicate symbol的错误。例如:duplicate symbol OBJC_METACLASS$_Reachability in:/你的xcode路径/DerivedData/iMove-cwhkpttlcvtnivfvakvpiakhvoak/Build/Products/Debug-iphoneos/libPods.a(Reachability.o)
/你的项目路径/libVParser.a(Reachability.o)

ld: 208 duplicate symbols for architecture armv7

clang: error: linker command failed with exit code 1 (use -v to see invocation)

我的代码历史可视化

       最近,我看到一个非常好玩的东西Gource

       Gource可以将代码版本控制系统里面的日志全部可视化, 所谓可视化就是可以看见每个成员在系统里面提交代码的行为。 Gource目前支持git/hg/svn,cvs通过一个简单的脚本也可以被Gource支持。
       我将我的项目生成可视化效果截图:

CocoaHTTPServer出现Crash的解决方法

        最近工程中,需要使用httpserver,就用了CocoaHTTPServer。代码的整合非常容易。但是运行后用浏览器连接服务器时,程序会 crash。

        最后发现是在 HTTPConnection::filePathForURI 函数中 config 的 documentroot 是空的,调试好久。然后,通过研究和搜索,发现最新版的CocoaHTTPServer用的是ARC。解决办法就是:选择项目 target,Build Phases -> Compile Sources 中,给所有 CocoaHTTPServer 的代码添加 Compiler Flags : -fobjc-arc 即可。

HMAC_SHA1算法的Objective-C实现

做项目时,用到HMAC_SHA1算法。代码实现,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static NSString *HMAC_SHA1 = @"HmacSHA1";
static NSString *APPKEY = @"123456";//这是key
- (NSString *)generateSignature:(NSString *)data
{
const char *cKey = [APPKEY cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];

unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];

CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);

NSData *HMAC = [[[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)] autorelease];

NSString *hash = [HMAC base64Encoding];

return hash;
}

这里用到NSData类别:https://github.com/WideTag/WideNoise-iOS/blob/master/WideNoise/Categories/NSData%2BBase64Encoding.m/

设置UILabel文字顶部对齐

      xcode中UILabel是默认垂直居中的。UILabel拥有textAlignment属性,可以用NSTextAlignmentLeft, NSTextAlignmentRight, NSTextAlignmentCenter三种设置水平方向的文字对齐方式。那怎么做,可以是文字顶部对齐呢?下面有两种方法:

dyld:lazy symbol bingding failed:Symbol not found 问题的解决

遇到这么一个蛋疼的问题:dyld: lazy symbol binding failed: Symbol not found: _objc_setProperty_nonatomic
Referenced from: /Users/imove-1/Library/Application Support/iPhone Simulator/5.1/Applications/24E4730A-9DB0-44F7-97E0-2D08F4AEC9DD/iMove.app/iMove

Expected in: /Applications/Xcode.app/Contents/Developer/
Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator5.1.sdk/System/Library/
Frameworks/Foundation.framework/Foundation

经过一番研究,发现出错原因:项目中,我是用cocoapods管理第三方包的。其中第三方包SwipeView的iOS Deployment Target 默认为iOS 6.1,改成4.3就可以了。

iOS获得广播地址

最近开发过程中,遇到需要获得iOS系统广播地址的问题,经过一番google,获得一个很好的解决方法,如下:

1、

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
#include <sys/types.h>
#include <sys/socket.h>
#include <ifaddrs.h>
#include <arpa/inet.h>
- (NSString *)getBroadcastAddress
{
NSString *broadCastAddress = @"";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0) {
temp_addr = interfaces;
while(temp_addr != NULL)
{
// check if interface is en0 which is the wifi connection on the iPhone
if(temp_addr->ifa_addr->sa_family == AF_INET)
{
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"])
{
broadCastAddress = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_dstaddr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
freeifaddrs(interfaces);
return broadCastAddress;
}

2、

需要在xcode该工程里配置

加入CFNetwork.framework、SystemConfiguration.framework