@synthesize testString = _testString;
@dynamic testString; _testString //Use of undeclared identifier '_testString'
@interface TestObject () //虽然这个属性已是只读性质,也要写上具体的语义,以此代表初始化方法在设置这些值时所用方法 @property(copy,readonly) NSString *testString; @end @implementation TestObject - initWithString:(NSString *)string { self = [super init]; if (self) { //用初始化方法设置好属性值以后,就不要再改变了,此时属性应设为“只读” _testString = [string copy]; } return self; } @end
NSString *textA = @"textA"; NSString *textAnother = [NSString stringWithFormat:@"textA"]; NSLog(@"%d",textA == textAnother);// 0 NSLog(@"%d",[textA isEqual:textAnother]);// 1 NSLog(@"%d",[textA isEqualToString:textAnother]);// 1
@interface TestObject : NSObject @property NSString *testString; @end @implementation TestObject - (BOOL)isEqual:(id)object { if (self == object) return YES; if ([self class] != [object class]) return NO; TestObject *otherObject = (TestObject *)object; if (![self.testString isEqualToString:otherObject.testString]) { return NO; } return YES; } -(NSUInteger)hash { //在没有性能问题下,hash 方法能够直接返回一个数 return 1227; } @end
在继承体系中判断等同性,还需判断是不是其子类
相同的对象必须具备相同的哈希码,可是相同哈希码的对象却未必相同
程序员
- (BOOL)isEqualToTestObject:(TestObject *)testobject { if (self == testobject) { return YES; } if (![self.testString isEqualToString:testobject.testString]) { return NO; } return YES; } - (BOOL)isEqual:(id)object { if ([self class] == [object class]) { return [self isEqualToTestObject:(TestObject *)object]; }else { return [super isEqual:object]; } }
有时候无需将全部数据逐个比较,只根据其中部分数据便可判明两者是否相等。objective-c
比方说一个模型类的实例是根据数据库的数据建立而来,那么其中可能会含有一个惟一标识符(unique identifier),在数据库中用做主键。这时候,咱们就能够根据标识符来断定等同性,尤为是此属性声明为 readonly 时更应该如此。只要标识符相等,就能够说明这两个对象是由相同数据源建立,据此判定,其余数据也相等。
固然,只有类的编写者才知道那个关键属性是什么。数据库
要点:不要盲目的逐个检测每条属性,而是应该按照具体需求制定检测方案编程
在 OC 中,对象收到消息以后,究竟该调用哪一个方法彻底于运行期决定,甚至能够在运行时改变,这些特性使 OC 成为一门真正的动态语言。
给对象发送消息能够这样写:缓存
id value = [obj messageName:parameter]安全
obj 叫作接收者,messageName 叫作 selector,selector 和参数合起来称为消息
编译器看到此消息后,将其转换为一条标准的 C 语言函数调用网络
void objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)session
第一个参数表明接收者,第二个表明 selector(SEL是selector类型)
这是个“参数个数可变的函数”,”…“ 表明后续参数,就是消息中的参数
数据结构
//Sends a message with a simple return value to the superclass of an instance of a class. objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...) //Sends a message with a data-structure return value to an instance of a class. objc_msgSend_stret(id _Nullable self, SEL _Nonnull op, ...) //Sends a message with a data-structure return value to the superclass of an instance of a class. objc_msgSendSuper_stret(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
<return_type> Class_selector(id self, SEL _cmd, ...)
每一个类都有一张表格,selector 的名称就是查表时所用的 key
多线程
原型的样子和 objc_msgSend 很像,并且函数的最后一项操做是调用另外一个函数并且不会将其返回值另做他用,就能够利用”尾调用优化“技术,令”跳至方法实现“变得简单。
尾调用技术:编译器会生成跳转至另外一函数所需的指令码,并且不会向调用堆栈中推入新的”栈帧“
要点:发给某对象的所有消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码
- 由于在运行期能够继续向类中添加方法,因此编译器在编译期还没法确知类中是否有某个方法的具体实现。 - 当对象接收到没法解读的消息,就会触发“消息转发机制”,程序员能够经由此过程告诉对象如何处理未知消息
消息转发分为两大阶段
//当未知的 selector 是实例方法时的调用 + (BOOL)resolveInstanceMethod:(SEL)sel ; //当未知的 selector 是类方法时的调用 + (BOOL)resolveClassMethod:(SEL)sel;
使用这种办法的前提是:相关方法的实现代码已经写好,只等着运行时加入类里面
//第一步:询问能不能把未知的消息转给其余接收者处理 - (id)forwardingTargetForSelector:(SEL)aSelector ;
若当前接收者能找到备援接收者,则将其返回,若找不到,则返回 nil
-若是返回一个对象,则运行期系统把消息转给那个对象,因而消息转发结束
若是返回 nil,执行第二步👇
//第二步:把消息相关的细节封装在 NSInvocation对象中,再给接收者最后一次机会 - (void)forwardInvocation:(NSInvocation *)anInvocation
能够在触发消息前,先以某种方式改变消息内容,好比追加一个参数,或改换 selector,等等
若是实现此方法时,发现某调用操做不该由本类处理,则调用超类的调用方法。若是继承体系的类都不处理此调用请求,那就最后调用 NSObject 类的方法,那么该方法会执行👇方法
//抛出异常,代表 selector 未能处理 - (void)doesNotRecognizeSelector:(SEL)aSelector;
+ (BOOL)resolveInstanceMethod:(SEL)sel { NSString *selString = NSStringFromSelector(sel); if ([selString hasPrefix:@"set"]) { //最后一个参数表示待添加方法的类型编码(type encoding) class_addMethod([self class], sel, (IMP)autoDictionarySetter, "v@:@"); }else { class_addMethod([self class], sel, (IMP)autoDictionaryGetter, "@@:"); } return [super resolveInstanceMethod:sel]; }
注
SEL 是方法编号,SEL类型经过 @selector() 提取
IMP 是一个函数指针,保存了方法的地址
type encoding:
v 表明 void
: 表明 (method selector)SEL
@ 表明 object(whether statically typed or typed id);
在 setter 实现的时候给一个字典添加键值对,getter 从字典获取值,这样的实现就在 iOS 的 CoreAnimation 框架中的 CALayer 类里面。CALayer 是一种“兼容于键值编码的”容器类,能向其中随意添加属性,而后以键值对的方式访问。
NSString *pointerVariable = @"Some string";
pointerVariable 是存放内存地址的变量,而 NSString 自身的数据就存于那个地址中
//对于通用的对象类型 id ,其自己已是指针了,能够这样写 id genericTypeString = "id String"
上面两种不一样的定义方式的区别在于,若是声明时指定了类型,在实例上调用没有的方法时,编译器会发出警告。而 id 类型,编译器默认它能响应全部本项目中存在的方法。
自此,咱们知道的了对象就是一个含有指向 Class 类的 isa 指针的结构体,Class 也是一个对象,它其中也有一个 isa 指针。那么类对象的 isa 指针指向哪里,类对象的父类又是什么呢?看下图
这样 OC 中类和对象的关系就清楚了。对象的 isa 指针指向类对象,类对象的 isa 指针指向 metaClass,译为元类。元类的 isa 指针指向根元类
由此能够看出来 Apple 设计类对象的用意就是为了存储对象的信息,好比对象方法,对象属性,遵照的协议,对象的类的父类,等等,而类对象的相关信息被存储在元类中。
- initWithString:(NSString *)string { if (self = [super init]) { if (string == nil) { self = nil; }else { //Initialize instance } } return self; }
NSError 的用法更加灵活,由于经过此对象,咱们能够获知错误的具体信息。NSError 对象里封装了三条消息:
Error code(错误码,其类型为整数)
独有的错误代码,用来指明在某个范围内具体发生了何种错误。某个特定范围内可能会发生一系列相关错误,这些错误状况一般采用 enum 来定义。例如:当 http 请求出错时,把 HTTP 状态码设为错误码.
@{NSLocalizedDescriptionKey : @"Image data is nil"}
/* Sent as the last message related to a specific task. Error may be * nil, which implies that no error occurred and this task is complete. */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error;
这个委托方法未必非得实现不可,是否是必须处理此错误,可交由用户来处理。
NSError 的另外一种常见用法:经由方法的“输出参数”返回给调用者。好比在 MJExtension 中:
/** * 经过字典来建立一个模型 * @param keyValues 字典 * @return 新建的对象 */ + (instancetype)objectWithKeyValues:(id)keyValues; + (instancetype)objectWithKeyValues:(id)keyValues error:(NSError **)error;
入参是一个指针,指向另外一个指针,那个指针指向 NSError 对象。使用以下方式获取错误信息。
NSError *error = nil; MyModel *model = [MyModel objectWithKeyValues:responseObject error:&error]; if (error) { //获取 error 信息 }
实际上,在使用 ARC 时,编译器会把方法签名中的 NSError ** 转换成 NSError *__autoreleasing *,也就是说,指针所指的对象会在方法执行完毕后自动释放。
MJ 经过如下代码把 NSError 对象传递到“输出参数”中:
// 构建错误 #define MJBuildError(error, msg) \ if (error) *error = [NSError errorWithDomain:msg code:250 userInfo:nil];
-这段代码以 *error 语法为 error 参数”解引用“,也就是说,error 所指的那个指针如今要指向一个新的 NSError 对象了。在解引用前,必须确保 error 不是 nil,由于空指针解引用会致使“段错误”并使程序奔溃。
Objective-C 语言使用引用计数来管理内存,也就是说,每一个对象都有一个能够递增递减的计数器,若是想要保留对象,就递增,用完了以后就递减,当计数为 0 时,对象就销毁。要写出优秀的 OC 代码,除了知道这些,还必须知道其中的原理。
- (void)retain;//使递增 - (void)release;//使递减 - (void)autorelease;//稍后清理“自动释放池”时再进行递减
查看引用计数的方法是 retainCount,不过这个方法不是很准确,苹果官方也不推荐
应用程序在生命期内会建立不少对象,这些对象相互联系着。例如表示我的信息的对象会引用表示名字的字符串对象。对象若是持有其余对象的强引用,那么前者就拥有后者。也就是说,对象想让它所引用的对象继续存活,可将其“保留”。等用完了以后再释放。
下图表示了一个对象从保留到释放的过程
按图能够想象,有一些其余对象想要保持 B 或 C 对象存活,而引用程序中又会有另一些对象想让这些对象存活。若是按“引用树”回溯,那么最终会发现有一个“根对象”。在 iOS 中,就是 UIApplication 对象。是在应用程序启动时建立的对象。
当对象的引用计数为 0 ,对象所占内存”解除分配“以后,就被放回”可用内存池“。此时再去调用该对象,可能会有不一样状况发生:若是此时内存对象已经作了他用,就会引发程序奔溃;若是此时对象内存未被复写,就可能正常运行。因而可知,由过早释放对象而致使的 bug 很难调试。
- (void)setFoo:(id)foo { [foo retain]; [_foo release]; _foo = foo; }
此方法保留新值,释放旧值,而后更新变量指向新值。顺序很重要。若是先释放旧值,那此对象就被回收。后续操做就都没有意义了。
块是一种可在 C 、C++、OC 代码中使用的“词法闭包”,它很是有用,能够把一段代码像对象同样传递。在定义“块”的范围内,它能够访问到其中全部的变量。
说“块”必须离不开说多线程。苹果公司设计的多线程编程的核心就是“块”(block)和“大中枢派发”(GCD),这虽然是两种不一样的技术,但他们是一并引入的。
GCD 提供了对线程的抽象,而这种抽象基于“派发队列”。开发者可将块排入队列中,由 GCD 处理全部调度事宜。
int adder = 8; //定义一个变量名为 myBlock 的块 //语法结构:return_type (^blockName) (parameters) int (^addBlock)(int num) = ^(int num){ //在块内部可使用外部变量 return num+ adder; }; int addEight = addBlock(5);
这里有个疑问,块里面的实例变量在没有 __block 修饰的状况下却也是能够改变值的,为何?
块自己是对象,因此他也有内存空间。
void (^myBlock)(); if (/* some condition*/) { myBlock = ^{}; }else { myBlock = ^{}; } myBlock();
定义在 if 和 else 里面的两个块都分配在栈内存中。编译器会给每一个块分配好栈内存,可是等离开了相应的范围以后,编译器就可能把分配给块的内存覆写掉。这样写出来的代码时而正确时而错误。
为解决此问题,可给块对象发送 copy 消息以拷贝之。这样就把块对象从栈内存移到了堆内存中,拷贝后的块,就能够在定义它的范围以外使用了。并且,一旦复制到堆上,块对象就成了具备引用计数的对象了。后续的复制操做就只是递增引用计数了。
明白了这一点,咱们只要给代码加上两个 copy 调用就安全了。
void (^myBlock)(); if (/* some condition*/) { myBlock = [^{} copy]; }else { myBlock = [^{} copy]; } myBlock();
void (^myBlock)() = ^{ NSLog(@"This is a block"); };
因为运行该块的全部信息都能在编译的时候肯定,因此可把他作成全局块。
这种块不会捕捉任何状态(好比外围的变量等),能够声明在全局内存中,不须要在每次用到的时候于栈中建立。这种块实际上至关于单例。