-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Objective-C 语言 #39
Comments
KVC 和 KVO1. KVO(Key-Value Observing)1.1 使用 KVO 时常见的两种崩溃场景:(1) Instance was deallocated while key value observers were still registered with it
原因:
(2) Cannot remove an observer <Person 0x102fdd920> for the key path "page" from <Book 0x17022f3c0> because it is not registered as an observer.原因:Person 类没有调用 1.2 KVO 的原理@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)willChangeValueForKey:(NSString *)key {
}
@end
////////////////////////////////////////////////
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Sark *sark = [Sark new];
Sark *sarkB = [Sark new];
//> breakpoint 1
[sark addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
//> breakpoint 2
sark.name = @"萨萨萨";
[sark removeObserver:self forKeyPath:@"name"];
//> breakpoint 3
NSLog(@"结束");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { }
在三个断点处分别打出 Sark 的 class 和 isa(或者直接展开 debug area 中的变量): // breakpoint 1
(lldb) po sark.class
Sark
(lldb) po object_getClass(sark)
Sark
// breakpoint 2
(lldb) po sark.class
Sark
(lldb) po object_getClass(sark)
NSKVONotifying_Sark
// breakpoint 3
(lldb) po sark.class
Sark
(lldb) po object_getClass(sark)
Sark 下面是苹果官方文档中对 KVO 实现细节的介绍:
下面我们通过符号断点 创建新类和修改 isa 的过程主要在 Foundation`_NSKVONotifyingCreateInfoWithOriginalClass:
...
0x7fff207b40ea <+59>: callq 0x7fff20949f14 ; symbol stub for: class_getName
...
0x7fff207b410c <+93>: leaq 0x1badbd(%rip), %rsi ; _NSKVONotifyingCreateInfoWithOriginalClass.notifyingClassNamePrefix
...
0x7fff207b4137 <+136>: callq 0x7fff2094a3ee ; symbol stub for: objc_allocateClassPair
...
0x7fff207b414b <+156>: callq 0x7fff2094a4b4 ; symbol stub for: objc_registerClassPair
...
0x7fff207b415b <+172>: callq 0x7fff2094a526 ; symbol stub for: object_getIndexedIvars
...
0x7fff207b41c1 <+274>: cmpq $-0x1, 0x6640b667(%rip) ; _NSKVONotifyingCreateInfoWithOriginalClass.onceToken + 7
...
0x7fff207b41d2 <+291>: movq 0x663f1237(%rip), %rsi ; "willChangeValueForKey:"
0x7fff207b41d9 <+298>: callq 0x7fff20949f0e ; symbol stub for: class_getMethodImplementation
0x7fff207b41de <+303>: movb $0x1, %cl
0x7fff207b41e0 <+305>: cmpq 0x6640b651(%rip), %rax ; _NSKVONotifyingCreateInfoWithOriginalClass.NSObjectWillChange
...
0x7fff207b41ec <+317>: movq 0x663f1225(%rip), %rsi ; "didChangeValueForKey:"
0x7fff207b41f3 <+324>: callq 0x7fff20949f0e ; symbol stub for: class_getMethodImplementation
0x7fff207b41f8 <+329>: cmpq 0x6640b641(%rip), %rax ; _NSKVONotifyingCreateInfoWithOriginalClass.NSObjectDidChange
...
0x7fff207b4206 <+343>: movq 0x663f1223(%rip), %rsi ; "_isKVOA"
0x7fff207b420d <+350>: leaq 0x1ef(%rip), %rdx ; NSKVOIsAutonotifying
...
0x7fff207b4219 <+362>: callq 0x7fff207b4372 ; NSKVONotifyingSetMethodImplementation
0x7fff207b421e <+367>: movq 0x663ee23b(%rip), %rsi ; "dealloc"
0x7fff207b4225 <+374>: leaq 0x1df(%rip), %rdx ; NSKVODeallocate
...
0x7fff207b4231 <+386>: callq 0x7fff207b4372 ; NSKVONotifyingSetMethodImplementation
0x7fff207b4236 <+391>: movq 0x663f11fb(%rip), %rsi ; "class"
0x7fff207b423d <+398>: leaq 0x41e(%rip), %rdx ; NSKVOClass
...
0x7fff207b4249 <+410>: callq 0x7fff207b4372 ; NSKVONotifyingSetMethodImplementation 总结下来,KVO 的实现原理就是:
如果你自己实现了一个 Sark 的子类
参考: 2. KVC (Key-Value Coding)2.1
|
Objective-C 语言
问题:当子类需要使用父类的一个私有属性(方法)时,需要把这个属性(方法)放到父类的header中,但暴露给子类的同时暴露给了外部调用者,如何解决? 建立一个私有header,使用类扩展定义父类需要暴露给子类的属性(方法),然后在各自的.m文件中引用。 参考 |
Objective-C 中的 try-catch |
指定初始化方法见 #57 |
Objective-C 属性1. Objective-C 属性声明和合成
2. Objective-C 属性的结构
3. Objective-C class properties
参考: |
Tag Pointer1. 为什么要使用 Tag Pointer?当从32位机器迁移到64位机器中后,对象的指针浪费了更多的内存。 (1)我们先看看原有的对象为什么会浪费内存:假设我们要存储一个NSNumber对象,其值是一个整数。正常情况下,如果这个整数只是一个NSInteger的普通变量,那么它所占用的内存是与CPU的位数有关,在32位CPU下占4个字节,在64位CPU下是占8个字节的。而指针类型的大小通常也是与CPU位数相关,一个指针所占用的内存在32位CPU下为4个字节,在64位CPU下也是8个字节。 所以一个普通的iOS程序,如果没有Tagged Pointer对象,从32位机器迁移到64位机器中后,虽然逻辑没有任何变化,但这种NSNumber、NSDate一类的对象所占用的内存会翻倍。 (2)效率上的问题:为了存储和访问一个NSNumber对象,我们需要在堆上为其分配内存,另外还要维护它的引用计数,管理它的生命期。这些都给程序增加了额外的逻辑,造成运行效率上的损失。 2. Tag Pointer 是什么?由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20多亿(注:2^31=2147483648,另外1位作为符号位),对于绝大多数情况都是可以处理的。 我们可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。所以,引入了Tagged Pointer对象之后,64位CPU下NSNumber的内存图变成了以下这样: Tagged Pointer有一个简单的应用,那就是NSNumber。它使用60位来存储数值。最低位置1。剩下3位为NSNumber的标志。在这个例子中,就可以存储任何所需内存小于60位的数值。 示例:
结果如下:
对于前 4 个 NSNumber,除去最后的数字最末尾的2以及最开头的0xb,其它数字刚好表示了相应NSNumber的值。而 bigNumber 的地址更像是一个普通的指针地址。我们可以推断,当8字节可以承载用于表示的数值时,系统就会以Tagged Pointer的方式生成指针,如果8字节承载不了时,则又用以前的方式来生成普通的指针。 3. Tagged Pointer 是一个能够提升性能、节省内存的有趣的技术,其特点如下:
4. Tagged Pointer 所带来的问题因为 Tagged Pointer 并不是真正的对象,而是一个伪对象,所以你如果完全把它当成对象来使,可能会让它露马脚。所有对象都有 isa 指针(实际上最新的 runtime 源码实现已经改了),而 Tagged Pointer 其实是没有的,因为它不是真正的对象。 因为不是真正的对象,所以如果你直接通过 所以我们应该换成相应的方法或者函数调用,如 参考:
|
在 Objective-C 对象内部应该采用何种方式存取实例变量?1. 设置变量值时最好通过 setter 方法来访问实例变量,获取变量值时直接访问实例变量原因:
2.
|
|
Objective-C 中的保留字参考 |
#import vs. @Class vs. @import1. #import 和 @Class 之间的区别1.1 #import 会包含这个类的所有信息,包括实体变量和方法,而 @Class 只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑。 1.2 在编译效率方面考虑,如果你有100个头文件都 #import 了同一个头文件,或者这些文件是依次引用的,如 A–>B, B–>C, C–>D 这样的引用关系。当最开始的那个头文件有变化的话,后面所有引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。而是用 @Class则不会。 1.3 如果有循环依赖关系,如:A–>B, B–>A这样的相互依赖关系,如果使用 #import 来相互包含,那么就会出现编译错误,如果使用 @Class 在两个类的头文件中相互声明,则不会有编译错误出现。 2. 分别在什么时候使用 #import 和 @Class在头文件中,一般只需要知道被引用的类的名称就可以了。 不需要知道其内部的实体变量和方法,所以在头文件中一般使用 @Class 来声明这个名称是类的名称。 而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用 #import 来包含这个被引用类的头文件。 3.
|
|
相关文档
延伸阅读
The text was updated successfully, but these errors were encountered: