KVO的原理
翻译自Mike Ash大神的blog:How Key-Value Oberserving Works
KVO是什么?
尽管大多数读者可能已经知道KVO,但是这里做一个快速总结:KVO是构成Cocoa Bindings基础的技术;当对象的属性变化时,它可以通知对象。
一个对象观察另一个对象的key。当被观察的对象改变那个key的值时,观察者就会得到通知。非常简单,对不对?微妙所在是通常情况下,KVO不需要在被观察者的类中添加代码。
尽管大多数读者可能已经知道KVO,但是这里做一个快速总结:KVO是构成Cocoa Bindings基础的技术;当对象的属性变化时,它可以通知对象。
一个对象观察另一个对象的key。当被观察的对象改变那个key的值时,观察者就会得到通知。非常简单,对不对?微妙所在是通常情况下,KVO不需要在被观察者的类中添加代码。
当调用hitTest方法时,测试的顺序是严格按照layer树中layer的顺序。zPosition属性可以用来影响layer层在屏幕上的显示顺序,但不会影响触摸事件的处理顺序。
UIView关联图层禁用了隐式动画。唯一在这个图层进行动画的方法是运用UIView动画方法(而不是依赖CATransaction),继承UIView,覆盖-actionForLayer:-forKey:方法,或者创建一个显式动画。
对于单独图层,可以通过实现-actionForLayer:-forKey:图层代理方法,或者提供一个actions词典。
例如,下面的代码就是错误的:1
2
3
4
5
6
7
8
9
10
11void(^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
11void(^block)();
if(/*some condition*/) {
block = [^{
NSLog(@"Block A");
} copy];
} else {
block = [^{
NSLog(@"Block B");
} copy];
}
block();
在生成和运行.cxx_destruct方法的过程中,系统自动调用了super dealloc。所以在ARC下,delloc方法可能是这个样子:1
2
3
4- (void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocatedMemoryBlob);
}
因为ARC不会对异常的代码进行内存管理。除非增加大量样板代码来处理这个情况,但是这样没有异常发生又会降低运行时的性能,同时也会显著的增加应用的代码量。
首先,看个例子:1
2NSString *string = @"Some string";
NSLog(@"string retainCount = %lu",[string retainCount]);
1 | string retainCount = 18446744073709551615 |
这个数字是2^64-1。因为NSString是单例对象。string是一个编译时常量。这种情况下,编译器会制作一个特殊对象,在应用的二进制文件中替换NSString的数据。
NSNumber也是类似。用标签指针(tagged pointer)来区分各种类型的数值。在这种模式下,没有NSNumber对象。指针本身的值包含所有关于数字的消息。运行时在消息分发过程中发现一个标签指针,它会装作像一个NSNumber对象一样来操作指针的数值。
这样的话,就可以在内部进行读写数据操作。但是同时,KVC还是可以设置属性的。因为,即使没有在.h文件中,KVC还是会直接查找有没有setXXX方法。
ARC下,异常不是默认安全的。如果在作用域中发生异常,那么后面因该被释放的对象不会被释放。同理,在非ARC下,如果异常在对象释放前发生,那么这个对象永远不会被释放。
[NSMutableArray copy] => NSArray;
[NSArray mutableCopy] => NSMutableArray
1 | - (NSString *)name { |
其中每一步消息接收者都有一次机会来处理消息。越往后,所花费的开销就越大。
最近看了一遍《Effective Objective-C 2.0:52 Specific Ways to Improve Your iOS and OS X Programs》英文版,感觉受益匪浅,学到了很多以前不了解或者不清楚的知识点。因为英文略让人捉急的原因,所以打算再看一遍这本书,同时看的过程中也做一些笔记。
这样的原因是可以限制其他使用当前类的导入范围。例如: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文件了。同时,也可以尽量的避免互相引用。
如果只在当前类中使用,只需在.m文件中定义static const AClass kVarible = a。如果需要暴露给其他类使用(例如,通知的名字),.h文件 extern NSString const AStringConst;
.m文件 NSString const AStringConst = @“a”
__unused宏(事实上是attribute((unused))这个GCC的编译器属性,更多请参考mattt大神的博客)告诉编译器“如果我没用到这个变量,不要警告我”。
?:是C中唯一一个三目运算符,用来替代简单的if-else语句,同时也是可以两元使用:1
2NSString *string = inputString ?: @"default";
NSString *string = inputString ? inputString : @"default"; // 等价
1 | // 允许代码使用UICollectionView如同它可以在iOS SDK 5使用一样。 |
若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行
66=64(GCD线程池的最大值) + 主线程 + 其他随机非GCD线程。来源
在锁屏和解锁的时候,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 | static void displayStatusChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { |
1 | #define nil __DARWIN_NULL |
nil用于表示空的实例对象
1 | #define Nil __DARWIN_NULL |
Nil用于表示空类对象
在Objective-C的1
[self performSelector:@selector(updateStatus) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];
在Swift中是没有performSelector方法的。替代方法有2种
1 | NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("updateStatus"), userInfo: nil, repeats: false) |
1 | dispatch_after(1, dispatch_get_main_queue(), { |
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) |
在项目中遇到一个需求:做一个二维码扫描的界面。要求是背景黑色透明度80%,中间有个白色透明框
1 | __weak ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url]; |
近期开发过程中,遇到一个“[UIDeviceRGBColor CGColor]: message sent to deallocated instance xxx”的bug,google了很长时间也没找到原因所在。后来,发现 点击此处,这里面所遇到的bug和我的类似。其中,这段话
[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.
在开发过程中,可能需要引入第三方的静态库,有时候会出现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)
最近工程中,需要使用httpserver,就用了CocoaHTTPServer。代码的整合非常容易。但是运行后用浏览器连接服务器时,程序会 crash。
最后发现是在 HTTPConnection::filePathForURI 函数中 config 的 documentroot 是空的,调试好久。然后,通过研究和搜索,发现最新版的CocoaHTTPServer用的是ARC。解决办法就是:选择项目 target,Build Phases -> Compile Sources 中,给所有 CocoaHTTPServer 的代码添加 Compiler Flags : -fobjc-arc 即可。
做项目时,用到HMAC_SHA1算法。代码实现,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static 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/
xcode中UILabel是默认垂直居中的。UILabel拥有textAlignment属性,可以用NSTextAlignmentLeft, NSTextAlignmentRight, NSTextAlignmentCenter三种设置水平方向的文字对齐方式。那怎么做,可以是文字顶部对齐呢?下面有两种方法:
遇到这么一个蛋疼的问题: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系统广播地址的问题,经过一番google,获得一个很好的解决方法,如下:
1 | #include <sys/types.h> |
需要在xcode该工程里配置
加入CFNetwork.framework、SystemConfiguration.framework
Hello World