基础语法
关键字
问题
1.欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
2.对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指定为 const;
3.在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
4.对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量;
5.对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不为“左值”。
#define在预处理阶段进行简单的替换,const在编译阶段使用
#define不做类型检查,仅仅展开替换,const有数据类型,会执行类型检查
#define不分配内存,仅仅展开替换,const会分配内存
#define不能调试,const可以调试
#define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段,只有一份copy,效率更高
#definde可以定义一些简单的函数,const不可以
作者:启发禅悟
链接:https://www.jianshu.com/p/be03f141a9c5
3,请用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)_U_LONG
#define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
如果你在你的表达式中用到_U_LONG(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
作者:启发禅悟
链接:https://www.jianshu.com/p/fa8c996d48e4
4,写一个”标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
这个测试是为下面的目的而设的:
标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比 if-then-else 更优化的代码,了解这个用法是很重要的。
懂得在宏中小心地把参数用括号括起来
我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
结果是:
((*p++) <= (b) ? (*p++) : (b))
这个表达式会产生副作用,指针p会作两次++自增操作。
作者:启发禅悟
链接:https://www.jianshu.com/p/8263aa619576
函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。
作者:启发禅悟
链接:https://www.jianshu.com/p/05444d9266d7
6,请谈谈#include与#import的区别、#import与@class 的区别
#include和#import 其效果相同,都是导入类中定义的行为(方法);
#import 不会引起交叉编译,确保头文件只会被导入一次;
@class 表明只定义了类的名称,而具体类的行为是未知的,一般用于.h 文件
@class比#import编译效率更高。此外@class和#import的主要区别在于解决引用死锁的问题。
作者:启发禅悟
链接:https://www.jianshu.com/p/e67441a08994
属性访问
1,@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
@property 的本质
@property = ivar + getter + setter;
“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:
编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:
@property = getter + setter;
ivar、getter、setter 是如何生成并添加到这个类中的?
自动合成( autosynthesis)
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName与 _lastName。也可以在类的实现代码里通过@synthesize语法来指定实例变量的名字.
我为了搞清属性是怎么实现的,曾经反编译过相关的代码,他大致生成了五个东西
OBJC_IVAR_$类名$属性名称:该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
setter 与 getter 方法对应的实现函数
ivar_list :成员变量列表
method_list :方法列表
prop_list:属性列表
作者:启发禅悟
链接:https://www.jianshu.com/p/e67441a08994
2,用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
作者:启发禅悟
链接:https://www.jianshu.com/p/08348d1e1106Block
Block
1,什么是block
在block内部使用外部指针且会造成循环引用情况下,需要用__weak修饰外部指针__weak typeof(self) weakSelf = self;
在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下__strong typeof(self) strongSelf = weakSelf;
如果需要在block内部改变外部变量的话,需要在用__block修饰外部变量
weakSelf的定义应该放在block外面。
我不太清楚你这个weakSelf指向的self具体是什么?当前的ViewController?
那么在这个ViewController被销毁前,很大程度上你是可以取得weakself的值的。但你可以想象下,如果在延时函数触发前,你的ViewController已经被关闭,那么你就不能取得weakSelf的值了。
创建一个Student类
// Student.h
#import
@interface Student : NSObject
@property(nonatomic, strong) NSString *name;
@end
// Student.m
#import "Student.h"
@implementation Student
@end
随便在一个函数中调用,例如下面的函数中:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *student = [[Student alloc] init];
student.name = @"Tom";
__weak typeof(Student) *weakSelf = student;
void(^block)()=^{
NSLog(@"Student Name = %@",weakSelf.name);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Student Name(Delay) = %@",weakSelf.name);
});
};
block();
}
输出
2017-02-23 10:55:03.876 TestBlock[1904:336958] Student Name = Tom
2017-02-23 10:55:05.876 TestBlock[1904:337002] Student Name(Delay) = (null)
可以理解,dispatch_after函数在viewDidLoad函数结束后执行,此时student对象已经被销毁,所以weakSelf所引用的内容已经不存在,所以取得不到Student Name。
因此对于Block内部的延时函数,为了保证延时之后Block所引用的对象还存在,需要用__strongSelf引用。上面的代码修改为:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Student *student = [[Student alloc] init];
student.name = @"Tom";
__weak typeof(Student) *weakSelf = student;
void(^block)()=^{
__strong typeof(Student) *strongSelf = weakSelf;
NSLog(@"Student Name = %@",strongSelf.name);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Student Name(Delay) = %@",strongSelf.name);
});
};
block();
}
输出为
2017-02-23 10:56:31.181 TestBlock[1942:347739] Student Name = Tom
2017-02-23 10:56:33.182 TestBlock[1942:348001] Student Name(Delay) = Tom
3,使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑:
所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz; }];
这些情况不需要考虑“引用循环”。
但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
类似的:
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
self --> _observer --> block --> self 显然这也是一个循环引用。
KVC/KVO
KVC,即是指 [NSKeyValueCoding], 一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。
一个对象拥有某些属性。比如说,一个 Person 对象有一个 name 和一个 address 属性。以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 address 的 key。 key 只是一个字符串,它对应的值可以是任意类型的对象。从最基础的层次上看,KVC 有两个方法:一个是设置 key 的值,另一个是获取 key 的值。如下面的例子:
void changeName(Person *p, NSString *newName)
{
// using the KVC accessor (getter) method
NSString *originalName = [p valueForKey:@"name"];
// using the KVC accessor (setter) method.
[p setValue:newName forKey:@"name"];
NSLog(@"Changed %@'s name to: %@", originalName, newName);
}
现在,如果 Person 有另外一个 key 配偶(spouse),spouse 的 key 值是另一个 Person 对象,用 KVC 可以这样写:
void logMarriage(Person *p)
{
// just using the accessor again, same as example above
NSString *personsName = [p valueForKey:@"name"];
// this line is different, because it is using
// a "key path" instead of a normal "key"
NSString *spousesName = [p valueForKeyPath:@"spouse.name"];
NSLog(@"%@ is happily married to %@", personsName, spousesName);
}
key 与 key path 要区分开来,key 可以从一个对象中获取值,而 key path 可以将多个 key 用点号 “.” 分割连接起来,比如:
[p valueForKeyPath:@"spouse.name"];
相当于这样……
[[p valueForKey:@"spouse"] valueForKey:@"name"];
Key-Value Observing (KVO)
Key-Value Observing (KVO) 建立在 KVC 之上,它能够观察一个对象的 KVC key path 值的变化。举个例子,用代码观察一个 person 对象的 address 变化,以下是实现的三个方法:
watchPersonForChangeOfAddress: 实现观察
observeValueForKeyPath:ofObject:change:context: 在被观察的 key path 的值变化时调用。
dealloc 停止观察
static NSString *const KVO_CONTEXT_ADDRESS_CHANGED = @"KVO_CONTEXT_ADDRESS_CHANGED"
@implementation PersonWatcher
-(void) watchPersonForChangeOfAddress:(Person *)p
{
// this begins the observing
[p addObserver:self
forKeyPath:@"address"
options:0
context:KVO_CONTEXT_ADDRESS_CHANGED];
// keep a record of all the people being observed,
// because we need to stop observing them in dealloc
[m_observedPeople addObject:p];
}
// whenever an observed key path changes, this method will be called
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// use the context to make sure this is a change in the address,
// because we may also be observing other things
if(context == KVO_CONTEXT_ADDRESS_CHANGED) {
NSString *name = [object valueForKey:@"name"];
NSString *address = [object valueForKey:@"address"];
NSLog(@"%@ has a new address: %@", name, address);
}
}
-(void) dealloc;
{
// must stop observing everything before this object is
// deallocated, otherwise it will cause crashes
for(Person *p in m_observedPeople){
[p removeObserver:self forKeyPath:@"address"];
}
[m_observedPeople release];
m_observedPeople = nil;
[super dealloc];
}
-(id) init;
{
if(self = [super init]){
m_observedPeople = [NSMutableArray new];
}
return self;
}
@end
这就是 KVO 的作用,它通过 key path 观察对象的值,当值发生变化的时候会收到通知。
Notification
1,谈谈NSNotification 和 KVO 的使用场景?
两者都是观察者模式,不同的是,KVO是被观察者直接发送消息给观察者,是对象间的交互,而通知则是观察者和被观察者通过通知中心对象之间进行交互,即消息由被观察者发送到通知中心对象,再由中心对象发给观察者,两者之间并不进行直接的交互
NSNotification 通知中心
苹果提供的一种消息机制, 观察者只要向消息中心注册, 即可接受其他对象发送来的消息,消息发送者和消息接受者两者可以互相一无所知,完全解耦。NSNotification可以应用于任意时间和任何对象,观察者可以有多个, 这也正是他跟delegate的区别.
使用步骤:
注册一个观察者
给通知中心发送一个消息
清除观察者
应用场景:
控制器与一个或多个任意的对象进行通信(监控)
UIDevice通知
键盘通知
KVO
Key-Value Observing,是Foundation框架提供的一种机制,使用KVO,可以方便地对指定对象的某个属性进行观察,当属性发生变化时,进行通知.
实现步骤
由被观察的对象调用方法, 添加观察者
一旦被观察的属性发生改变, 系统会调用这个方法
解除观察者身份
网友评论