一、 instancetype和id的区别
1、instancetype在类型表示上,跟id一样,可以表示任何对象类型
2、instancetype只能用在返回值类型上,不能像id一样用在参数类型上
3、instancetype比id多一个好处:编译器会检测instancetype的真实类型
注:作为返回值时,凡是用id的地方,都建议换成instancetype
二、属性的本质
@property = ivar + getter + setter;
实例变量+ setter方法+ setter方法,也就是说使用@property 系统会自动生成成员变量、setter和getter方法;
三、@synthesize 和 @dynamic 分别有什么作用
- @property 有两个对应的词,分别是 @synthesize和 @dynamic。如果@synthesize 和 @dynamic 都没写,默认的就是 @syntheszie var = _var;
- @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter 方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
四、atomic
atomic的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的,在多线程中,atomic只保证getter、setter方法安全,并不保证其它操作,例如字符串拼接,数组移除元素等,并没有执行getter和setter方法,顾不是绝对安全的。
五、Block本质
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block是封装函数及其上下文的OC对象
- auto变量block访问方式是值传递,auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;
- static变量block访问方式是指针传递,static变量一直保存在内存中,指针访问即可;
block不需要对全局变量捕获,都是直接采用取值的
- block里访问self会被捕获:self是当调用block函数的参数,参数是局部变量,self指向调用者
- block里访问成员变量会被捕获:成员变量的访问其实是self->xx,先捕获self,再通过self访问里面的成员变量
block三种类型:
1、__NSGlobalBlock __ 在数据区 :没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段;调用copy操作后,什么也不做
2、__NSMallocBlock __ 在堆区 : [__NSStackBlock __ copy]操作就变成了__NSMallocBlock __ ;复制效果是:引用计数增加,副本存储位置是堆
3、__NSStackBlock __ 在栈区 : 访问了auto变量的block是__NSStackBlock __ ;调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆
在ARC环境下,编译器会根据一下几种情况自动将栈上的block复制到堆上:
1、block作为函数返回值时
2、将block赋值给__strong指针时
3、block作为Cocoa API中方法名含有usingBlock的方法参数时
4、block作为GCD API的方法参数时
当block内部访问了对象类型的auto变量时:
如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用。
栈block:
a) 如果block是在栈上,将不会对auto变量产生强引用
b) 栈上的block随时会被销毁,也没必要去强引用其他对象
堆block:
1.如果block被拷贝到堆上:
a) 会调用block内部的copy函数
b) copy函数内部会调用_Block_object_assign函数
c) _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
2.如果block从堆上移除
a) 会调用block内部的dispose函数
b) dispose函数内部会调用_Block_object_dispose函数
c) _Block_object_dispose函数会自动释放引用的auto变量(release)
__block 修饰符作用:
- __block可以用于解决block内部无法修改auto变量值的问题
- __block不能修饰全局变量、静态变量(static)
- 编译器会将__block变量包装成一个对象
- __block修改变量:age->__forwarding->age
- __Block_byref_age_0结构体内部地址和外部变量age是同一地址
六、NSCache和NSMutableDictionary的相同点与区别:
相同点:
NSCache和NSMutableDictionary功能用法基本是相同的。
区别:
NSCache是线程安全的,NSMutableDictionary线程不安全
NSCache线程是安全的,Mutable开发的类一般都是线程不安全的
当内存不足时NSCache会自动释放内存(所以从缓存中取数据的时候总要判断是否为空)
NSCache可以指定缓存的限额,当缓存超出限额自动释放内存
缓存限额:
缓存数量
@property NSUInteger countLimit;
缓存成本
@property NSUInteger totalCostLimit;
苹果给NSCache封装了更多的方法和属性,比NSMutableDictionary的功能要强大很多
七、runtime通过selector找到对应的IMP地址的两种方式
方式一:
类方法(假设有一个类 A)
class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
实例方法
class_getMethodImplementation([A class],@selector(methodName));
方式二:
类方法
Method class_getClassMethod(Class cls, SEL name)
实例方法
Method class_getInstanceMethod(Class cls, SEL name)
最后调用IMP method_getImplementation(Method m) 获取IMP地址
八、避免设置圆角引起离屏渲染的优化
方式一:使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
//开始对imageView进行画图
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用贝塞尔曲线画出一个圆形图
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
//结束画图
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
方式二:使用绘图技术
- (UIImage *)circleImage {
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
// 获得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一个圆
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪
CGContextClip(ctx);
// 将图片画上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
return image;
}
九、使用ShadowPath指定layer阴影效果路径避免离屏渲染
imageView.layer.shadowColor=[UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;
十、关于自定义View的初始化方法
通常我们会创建私有方法createUI方法来创建当前自定义View所需要的子View。那上述所说的createUI应该放在自定义View的哪个方法中呢?
1、init?
2、initWithFrame?
3、还是为了考虑外部创建自定义View的方式不同,在init与initWithFrame方法中均调用createUI方法?
- (instancetype)init {
if (self = [super init]) {
[self createUI];
}
return self;
}
- (void)createUI {
[self addSubview:self.testView];
}
- (UIView *)testView {
if (!_testView) {
_testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
_testView.backgroundColor = [UIColor redColor];
}
return _testView;
}
createUI方法最好在initWithFrame中调用,外部使用init或initWithFrame均可以正常执行createUI方法。不要在自定义View中同时重写init与initWithFrame并执行相同视图布局代码。会导致布局代码(createUI)执行多次。
原因:
1、动态查找到CustomView的init方法
2、调用[super init]方法
3、super init方法内部执行的的是[super initWithFrame:CGRectZero]
4、若super发现CustomView实现了initWithFrame方法
5、转而执行self(CustomView)的initWithFrame方法
6、最后在执行init的其余部分
关于addSubview
我们接着问题一自定义View的初始化方法来说,如果同时在init与initWithFrame中同时调用了createUI方法,createUI方法执行了多次,也就是说重复多次添加了self.testView。那是否会重复添加多个View层呢?并不会,重复多次添加同一个View并不会产生多层级的情况。
原因:
View有且仅有一个父视图,如果新的父视图与原父视图不一样,会将View在原视图中移除,添加到新视图上。因此同一父视图重复添加同一个View并不会产生多层级。
结论:
若父视图重复添加同一子视图,并不会产生多层级情况。因为此例中testView是以懒加载的形式创建,所以self每次添加的均为同一个View,但如果在createUI中以UIView *testView = [UIView alloc] initWithFrame的形式创建,那就会创建出多层级的View。自定义View的子视图最好以懒加载形式创建,可避免因其他书写不当导致的异常
关于layoutSubviews
1、自定义视图的init方法并不会调用layoutSubviews
2、苹果声明不要直接调用layoutSubviews方法,如果需要更新,应该调用setNeedsLayout方法,视图会在下一次绘制后更新。如果需要立即更新视图,需要执行layoutIfNeeded方法
3、因为layoutSubviews调用比较频繁,因此若无特殊需求(文档所述为执行精确的子视图布局时可使用),不用重写layoutSubviews方法。
十一、 ATS
1、iOS9中新增App Transport Security(简称ATS)特性, 让原来请求时候用到的HTTP,全部都转向TLS1.2协议进行传输。
2、这意味着所有的HTTP协议都强制使用了HTTPS协议进行传输。
3、如果我们在iOS9下直接进行HTTP请求是会报错。系统会告诉我们不能直接使用HTTP进行请求,需要在Info.plist中控制ATS的配置。
"NSAppTransportSecurity"是ATS配置的根节点,配置了节点表示告诉系统要走自定义的ATS设置。
"NSAllowsAritraryLoads"节点控制是否禁用ATS特性,设置YES就是禁用ATS功能。
4、有两种解决方法,一种是修改配置信息继续使用以前的设置。
另一种解决方法是所有的请求都基于基于"TLS 1.2"版本协议。(该方法需要严格遵守官方的规定,如选用的加密算法、证书等)
ATS默认的条件
1、服务器TLS版本至少是1.2版本
2、连接加密只允许几种先进的加密
3、证书必须使用SHA256或者更好的哈希算法进行签名,要么是2048位或者更长的RSA密钥,要么就是256位或更长的ECC密钥。
AFSecurityPolicy,内部有三个重要的属性,如下:
AFSSLPinningMode SSLPinningMode; //该属性标明了AFSecurityPolicy是以何种方式来验证
BOOL allowInvalidCertificates; //是否允许不信任的证书通过验证,默认为NO
BOOL validatesDomainName; //是否验证主机名,默认为YES
"AFSSLPinningMode"枚举类型有三个值,分别是AFSSLPinningModeNone、AFSSLPinningModePublicKey、AFSSLPinningModeCertificate。
"AFSSLPinningModeNone"代表了AFSecurityPolicy不做更严格的验证,"只要是系统信任的证书"就可以通过验证,不过,它受到allowInvalidCertificates和validatesDomainName的影响;
"AFSSLPinningModePublicKey"是通过"比较证书当中公钥(PublicKey)部分"来进行验证,通过SecTrustCopyPublicKey方法获取本地证书和服务器证书,然后进行比较,如果有一个相同,则通过验证,此方式主要适用于自建证书搭建的HTTPS服务器和需要较高安全要求的验证;
"AFSSLPinningModeCertificate"则是直接将本地的证书设置为信任的根证书,然后来进行判断,并且比较本地证书的内容和服务器证书内容是否相同,来进行二次判断,此方式适用于较高安全要求的验证。
如果HTTPS服务器满足ATS默认的条件,而且SSL证书是通过权威的CA机构认证过的,那么什么都不用做。如果上面的条件中有任何一个不成立,那么都只能修改ATS配置。
十二、isinf()、isnan()
isfinite()测试某个浮点数是不是有限的数
isfinite(float x);
isfinite(double x);
isfinite(long double x);
isinf()测试某个浮点数是否是无限大
isinf(float x);
isinf(double x);
isinf(long double x);
isnan()测试某个浮点数是否是 非数字
isnan(float x);
isnan(double x);
isnan(long double x);
isnormal()测试某个浮点数是否被规格化
isnormal(float x);
isnormal(double x);
isnormal(long double x);
signbit()测试某个浮点数是否为负数
signbit(float x);
signbit(double x);
signbit(long double x);
十三、FOUNDATION_STATIC_INLINE
FOUNDATION_STATIC_INLINE为内联函数的宏定义。
#define FOUNDATION_STATIC_INLINE static __inline__
内联函数完全可以取代表达式形式的宏定义。
为什么要用内联函数:
效率来看
函数之间调用,是内存地址之间的调用、当函数调用完毕之后还会返回原来函数执行的地址。函数调用将会有时间开销。
内联函数在汇编中没有call语句。取消了函数的参数压栈
相比表达式形式的宏定义
因为inline内联函数也是函数、不需要预编译。
调用时候会首先检查它的参数的类型、保证调用正确。
可以使用所在类的保护成员及私有成员。
需要注意
内联函数中尽量不要使用诸如循环语句等大量代码、可能会导致编译器放弃内联动作。
内联函数的定义须在调用之前。
十四、ps和pt转换
- px:相对长度单位。像素(Pixel)。(PS字体)
- pt:绝对长度单位。点(Point)。(iOS字体)
pt=(px/96)*72。
十五、控制台日志输出不全
定义日志输出宏:
#define DLog(FORMAT, ...) printf("***********自定义日志全部输出***********:\n调用方法: %s \n所在行数:[Line %d] \n%s \n ",__PRETTY_FUNCTION__, __LINE__,[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
十六、监听来电接听状态
导入头文件:
#import
#import
强引用:
@property (nonatomic,strong) CTCallCenter *callCenter;
创建实例监听状态:
self.callCenter = [[CTCallCenter alloc] init];
self.callCenter.callEventHandler=^(CTCall* call){
if (call.callState == CTCallStateDisconnected){
NSLog(@"挂断电话Call has been disconnected");
}else if (call.callState == CTCallStateConnected){
NSLog(@"电话通了Call has just been connected");
}else if(call.callState == CTCallStateIncoming){
NSLog(@"来电话了Call is incoming");
}else if (call.callState ==CTCallStateDialing){
NSLog(@"正在播电话call is dialing");
}
else{
NSLog(@"Nothing is done");
}
};
十七、设置返回按钮文字消失
方式一:
此方法是设置文字为clearColor,会影响其他想要正常显示的文字一并看不到,所以不实用
if (@available(iOS 11.0, *)) {
// 设置返回按键文字消失
[[UIBarButtonItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName: [UIColor clearColor]} forState:UIControlStateNormal];
}
方式二:
设置文字偏移量
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(-1000, 0) forBarMetrics:UIBarMetricsDefault];
十八、语音播报功能
系统原生方式法,例如支付到账语音提醒
导入头文件
#import
#pragma mark 语音播报
-(void)voicePlay{
AVSpeechSynthesizer *av = [[AVSpeechSynthesizer alloc]init];
AVSpeechSynthesisVoice *voice = [AVSpeechSynthesisVoice voiceWithLanguage:@"zh-CN"];
AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:@"收款100元"];
utterance.rate = AVSpeechUtteranceDefaultSpeechRate;
utterance.voice = voice;
[av speakUtterance:utterance];
}