1、属性默认关键词是那些,NSString常用什么修饰,为什么?
对应基本数据类型默认关键字是:atomic, readwrite, assign。
对于普通的 Objective-C 对象默认关键字是:atomic, readwrite, strong。
NSString用copy修饰。为了安全,一般情况下,我们都不希望字串的值跟着字符串变化,所以我们一般用copy来设置string的属性。
@property (strong,nonatomic) NSString *rStr;
@property (copy, nonatomic) NSString *cStr;
- (void)test{
NSMutableString *mStr = [NSMutableString stringWithFormat:@"abc"];
self.rStr = mStr;
self.cStr = mStr;
NSLog(@"mStr:%p,%p", mStr,&mStr);
NSLog(@"strongStr:%p,%p", _rStr, &_rStr);
NSLog(@"copyStr:%p,%p", _cStr, &_cStr);
}
假如,mStr对象的地址为0x11,也就是0x11是@“abc”的首地址,mStr变量自身在内存中的地址为0x123;
当把mStr赋值给strong的rStr时,rStr对象的地址为0x11,rStr变量自身在内存中的地址为0x124;rStr与mStr指向同样的地址,他们指向的是同一个对象@“abc”,这个对象的地址为0x11,所以他们的值是一样的。
当把mStr赋值给copy的cStr时,cStr对象的地址为0x22,cStr变量自身在内存中的地址0x125;cStr与mStr指向的地址是不一样的,他们指向的是不同的对象,所以copy是深复制,一个新的对象,这个对象的地址为0x22,值为@“abc”。
如果现在改变mStr的值:
//注意mStr如果这里是不可变字符串,那么这里无法改变,是浅拷贝。会崩溃
[mStr appendString:@"de"];
NSLog(@"strongStr:%@", _rStr);
NSLog(@"copyStr:%@", _cStr);
结果:
使用strong的字串rStr的值:@"abcde",
而使用copy的字串cStr的值:@"abc",
如果是不可变字符串
NSString * str = @"abc";
self.rStr = str;
self.cStr = str;
str = @"66666666";
NSLog(@"%@===%@",self.rStr,self.cStr);//打印结果都是abc
2、nullable、nonnull 、NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 的含义和用途?
__nullable指代对象可以为NULL或者为NIL
__nonnull指代对象不能为null
当我们不遵循这一规则时,编译器就会给出警告。
事实上,在任何可以使用const关键字的地方都可以使用__nullable和__nonnull,不过这两个关键字仅限于使用在指针类型上。而在方法的声明中,我们还可以使用不带下划线的nullable和nonnull,如下所示:
- (nullable id)itemWithName:(NSString * nonnull)name
在属性声明中,也增加了两个相应的特性,因此上例中的items属性可以如下声明:
@property (nonatomic, copy, nonnull) NSArray * items;
当然也可以用以下这种方式:
@property (nonatomic, copy) NSArray * __nonnull items;
推荐使用nonnull这种方式,这样可以让属性声明看起来更清晰。
如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。如下代码所示:
NS_ASSUME_NONNULL_BEGIN
@interface TestNullabilityClass ()
@property (nonatomic, copy) NSArray * items;
- (id)itemWithName:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END
3、BAD_ACCESS在什么情况下出现?
访问了悬垂指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环。
4、以下代码输出什么?
- (void)deadLockCase1 {
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
}
控制台输出:1 ,后面就崩溃了。
5、isKindOfClass和isMemberOfClass的区别?selector的作用?
相同点:
都是NSObject的比较Class的方法.
不同点:
isKindOfClass:确定一个对象是否是一个类的成员,或者是派生自该类的成员。或者是继承自某类。
isMemberOfClass:确定一个对象是否是当前类的成员.
selector:通过方法名,获取在内存中的函数的入口地址。
6、使用NSOperationQueue的addOperationWithBlock要考虑循环引用吗,为什么?
7、为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern"C"{
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
答案:
#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif
这一段是用来防止头文件重复引用,vs可以使用#pragma once。但是推荐使用宏定义来防止重复包含,其跨平台,兼容性好。
如果是C++文件,以C的方式编译,并执行{}内的指令。其目的是为了兼容C代码,常出现在动态链接库的代码中。
#ifdef _cplusplus //这句表示如果是c++文件
extern"C"{ //用extern "C"把一段代码包起来
#endif
#ifdef _cplusplus
}
#endif
8、HTTP七层协议
TCP协议对应于传输层
网络七层协议由下往上分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。其中物理层、数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象;传输层、会话层、表示层和应用层则被称作主机层,是用户所面向和关心的内容。
HTTP协议对应于应用层,TCP协议对应于传输层,IP协议对应于网络层,HTTP协议是基于TCP连接的,三者本质上没有可比性。 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是它的一组接口。
9、Objective-C 消息发送与转发机制原理
消息发送和转发流程可以概括为:
消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;
另外:(Objective-C动态性的根源在方法的调用是通过message来实现的,一次发送message的过程就是一次方法的调用过程。发送message只需要指定对象和SEL,Runtime的objc_msgSend会根据在信息在对象isa指针指向的Class中寻找该SEL对应的IMP,从而完成方法的调用。)
消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。
消息查找过程IMP
(1)缓存查找
(2)继续在类的继承体系中查找
10、底层解析weak的实现原理?
weak 实现原理的概括
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
11、Block 底层原理总结,有几种类型的Block,Block的循环引用原理?
(1)Block可以简单总结:
block本质上也是一个OC对象,它内部也有个isa指针;
block是封装了函数调用以及函数调用环境的OC对象.
(2)Block 有三种类型:
NSGlobalBlock 全局区的Block
NSStackBlock 栈区的Block
NSMallocBlock 堆区的Block
(3)Block的循环引用原理?
对象、变量、Block的相互持有
那么如何解决这个问题呢?
通常我们ARC环境下面的解决办法是通过__weak指针来解决这个问题,通过上面讲的Block里面的变量是通过访问的外部变量是否是strong或weak指针来进行内部对象进行相应修饰的,所以如果访问的外部对象是weak指针时,他们的引用关系就会如下图:
12、YTCar *car 实现对象car的手动内存管理模式下的set方法
//是一个不断去掉旧值赋新值的过程
- (void)setCar:(YTCar *)car{
if (_car != car) { //判断新旧值是否相等
//release掉旧值
[_car release];
//retain新值
_car = [car retain];
}
}
13、NSThread、 GCD、NSOperation的区别?
1)NSThread
优点:NSThread 比其他两个轻量级。
缺点:需要自己管理线程的生命周期,线程同步。
线程同步对数据的加锁会有一定的系统开销。
3)GCD
替代NSThread等线程,自动管理生命周期。
2)NSOperation
优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。
基于GCD底层,使用起来更加面向对象。比GCD多了一些简单实用的功能。
14、autorelease 自动释放池的释放时机,autoreleasepool的实现原理?
(1)runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration!
(2)当一个autorelease pool被drain 的时候,会对pool里的每一个对象发送一个release消息;
(3)每一个线程(包括主线程)都有一个AutoreleasePool栈。当一个新的池子被创建的时候,push进栈,当池子被释放内存时,pop出栈。对象调用autorelease方法进入栈顶池子中。当线程结束的时候,会自动地销毁所有跟它有关联的池子。
使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
在普通for循环和for in 循环中没有,当for循环中便利产生大量autorelease变量时,就需要手动加局部AutoreleasePool。
autoreleasepool的实现原理?
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。
AutoreleasePool底层实现原理
autoreleasepool的实现原理
15、OC中如何判断两个对象完全相同?
(1)isEqual和hash
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![self class] == [object class]) {
return NO;
}
//此处还需判断对象中的各个属性是否相同
...
//若所有属性都相同则返回
return YES;
}
首先判断两个指针是否相等,若相等,则均指向同一对象,所以受测的对象也必定相等。接下来判断两对象所属的类,若属于同一类。
hash:比较得时候最好是先计算其哈希码,再进行比较。
(2)==
比较的是两个对象的指针本身,有时候返回的结果并不是我们想要的结果。
(3)isEqualToString
用于判断两个字符串是否相等的方法,当然还有isEqualToArray: isEqualToDictionary:
(4)NSSet中可变类的等同性比较【数组去重】
OC 判断两个对象是否相等
16、retain一个NSTime类型成员变量会有什么问题?
使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时。
_timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(startCounting) userInfo:nil
repeats:YES];
类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。
解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这个计时器时,就调用
[_timer invalidate];
_timer = nil;
17、Category的使用及原理?与Runtime有关吗?
18、数组去重(顺序不会变,去重后数组为listAry,并找出重复的元素(listAryCF)?
/**
数组去重,顺序不会变
@param array 传入的数组
@return 得到去重后的可变数组
*/
+(NSMutableArray *)arrayDataDeleteChongFuWithArray:(NSArray *)array{
NSMutableArray *listAry = [[NSMutableArray alloc]init];//去重后的数组
// NSMutableArray *listAryCF = [[NSMutableArray alloc]init];//检出重复的元素,放入数组
for (NSString *str in array) {
// containsObject 判断数组是否包含某个元素
if (![listAry containsObject:str]) {
[listAry addObject:str];
}else{
//附加
//重复元素加入新数组
// [listAryCF addObject:str];
// NSLog(@"重复的元素:%@",str);
}
}
return listAry;
}
19、【重】监听一组异步任务是否都执行结束,如果都执行结束就能够得到统一的通知.
// 监听一组异步任务是否执行结束,如果执行结束就能够得到统一的通知.
// 创建默认优先级的全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
// 创建调度组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"下载图片A");
});
dispatch_group_async(group, queue, ^{
NSLog(@"下载图片B");
});
dispatch_group_async(group, queue, ^{
NSLog(@"下载图片C");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"以上异步任务都处理下载完成图片了");
});
20、masonry为什么不会造成循环引用的问题?
答案:虽然block持有self,但是self并没有持有block,显然block跟self并没有相互持有,所以不会循环引用。
[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.otherView.mas_centerY);
}];
并不是 block 就一定会造成循环引用,是不是循环引用要看是不是相互持有强引用。block 里用到了 self,那 block 会保持一个 self 的引用,但是 self 并没有直接或者间接持有 block,所以不会造成循环引用。
block中持有了self,但是self.view并没有持有这个block,因为看到Masonry的源码是这样的:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
21、以下每行代码执行后,person对象的retain count分别是多少?
Person *person = [[Person alloc] init];
[person retain];
[person release];
[person release];
答案
Person *person = [[Person alloc] init]; =1
[person retain]; +1 = 2
[person release]; -1 = 1
[person release]; -1 = 0
22、如何用GCD同步若干个异步调用(根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
// 当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片
});
23、Objc中向一个nil对象发送消息会怎样?
oc中向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。也不会崩溃。
24、简单描述客户端的缓存机制?(Describe the cache mechanism of the client?)
缓存分为:内存数据缓存、数据库缓存、文件缓存。
每次想获取数据的时候:
1)先检测内存中有无缓存;
2)再检测本地数据缓存(数据库、文件);
3)最终发送网络请求;
4)将网络数据进行缓存(内存、数据库、文件),以便下次读取。
25.1、下面线程输出顺序?
dispatch_queue_t queue = dispatch_queue_create("com.taikang.com", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"1------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3------%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"4------%@",[NSThread currentThread]);
});
NSLog(@"5------%@",[NSThread currentThread]);
答案:输出顺序:1 2 3 5 4
输出顺序:1 2 3 5 4
25.2、执行这个方法需要几秒?打印顺序是啥?
- (void)testGCD
{
dispatch_queue_t queue = dispatch_queue_create("test", NULL);
dispatch_async(queue, ^(void){
NSLog(@"1");
sleep(1);
});
dispatch_async(queue, ^(void){
NSLog(@"2");
sleep(1);
});
dispatch_sync(queue, ^(void){
NSLog(@"3");
sleep(1);
});
}
答案:调用该方法要3秒,因为sleep是休眠,打印顺序:123。
26、给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度?
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
测试输入:abcabcdefbbkcghghjkla
-(void)stringMaxLength{
// NSString *str = @"abcabcbb";
NSString *str = @"abcabcdefbbkcghghjkla";
NSInteger maxLenth = 0;//最大长度
NSString *targetStr = @"";//最大长度对应的字符串
for (int i=0; i<str.length; i++) {
for (int j=1; j<str.length+1-i; j++){
NSString *substr = [str substringWithRange:NSMakeRange(i, j)];
BOOL isRepeatStr = NO;
NSMutableArray *tempArr = [NSMutableArray array];
for (int k=0; k<substr.length; k++) {
NSString *subsubStr = [substr substringWithRange:NSMakeRange(k, 1)];
if ([tempArr containsObject:subsubStr]) {
isRepeatStr = YES;
break;
}else{
[tempArr addObject:subsubStr];
}
}
if (isRepeatStr == NO) {
if (substr.length > maxLenth) {
maxLenth = substr.length;
targetStr = substr;
}
}
}
}
NSLog(@"最大长度无重复字符串长度为:%ld == 字符串为:%@",(long)maxLenth,targetStr);
}
//输出结果为:abcdef
输出结果为:abcdef
27、 iOS runloop与线程的关系
runloop是每一个线程一直运行的一个对象,它主要用来负责响应需要处理的各种事件和消息。每一个线程都有且仅有一个runloop与其对应,没有线程,就没有runloop。
在所有线程中,只有主线程的runloop是默认启动的,main函数会设置一个NSRunLoop对象。而其他的线程runloop默认是没有启动的,可以通过[NSRunLoop currentRunLoop]来启动。
当线程的 RunLoop 开启后,线程就会在执行完任务后,处于休眠状态,随时等待接受新的任务,而不是退出。
28、什么是isa指针?
isa是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
29、Block 为什么用 Copy 修饰?
对于这个问题,得区分 MRC 环境 和 ARC 环境;当Block 引用了普通外部变量时,都是创建在栈区的;对于分配在栈区的对象,我们很容易会在释放之后继续调用,导致程序奔溃,所以我们使用的时候需要将栈区的对象移到堆区,来延长该对象的生命周期。
对于 MRC 环境,使用 Copy 修饰 Block,会将栈区的 Block 拷贝到堆区。
对于 ARC 环境,使用 Strong、Copy 修饰 Block,都会将栈区的 Block 拷贝到堆区。
所以,Block 不是一定要用 Copy 来修饰的,在 ARC 环境下面 Strong 和 Copy 修饰效果是一样的。
30、解释static、self、super关键字的作用?
static:函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值.
在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问.
在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明.
在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。
self:当前消息的接收者。
super:向父类发送消息。
31、在一个对象的方法里面:self.name= “object”;和 name =”object” 有什么不同吗?
答:self.name =”object”:会调用对象的setName()方法;
name = “object”:会直接把object赋值给当前对象的name属性。
32、这段代码有什么问题吗?
-(void)setAge:(int)newAge{
self.age = newAge;
}
答案
死循环,应该修改为:_age = newAge;
33、这段代码有什么问题,如何修改?
for (int i = 0; i < someLargeNumber; i++) {
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@“%@”, string);
}
答案
打印结果:string = abcxyz
大次数循环会出现内存泄露,加入自动释放池@autoreleasepool{};在变量使用结束后立刻释放。
for (int i = 0; i < someLargeNumber; i++) {
@antoreleasepool {
NSString *string = @”Abc”;
string = [string lowercaseString];//字母全部转小写
string = [string stringByAppendingString:@"xyz"];//字符串追加
NSLog(@“%@”, string);
}
}
35、直接修改成员变量或属性会触发KVO吗?
答案:都不会执行。观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行KVO的回调方法,例如是否执行了setter方法、或者是否使用了KVC赋值。如果赋值没有通过setter方法或者KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这时是不会触发kvo机制,更加不会调用回调方法的。
所以使用KVO机制的前提是遵循 KVO 的属性设置方式来变更属性值。
36、Block是如何捕获Object-C的对象的?
答案:block里捕获的是该变量指向的内存地址,而不是直接把当前的对象的地址。
可能是指针拷贝。Block会对外部的变量进行一次"临时“的”拷贝“.产生一个新的指针,对象地址不变。
37、当数据库中的某项数据为null时,通过FMDB取出的数据为?
答案:nil
38、下面代码打印什么?
//在NSObject+Test.h中
#import <Foundation/Foundation.h>
@interface NSObject (Test)
-(void)test;
+(void)test1;
@end
@interface ClassA : NSObject
@end
//在NSObject+Test.m中
#import "NSObject+Test.h"
@implementation NSObject (Test)
-(void)test{
NSLog(@"aaa");
}
+(void)test1{
NSLog(@"bbb");
}
@end
@implementation ClassA
@end
答案:在调用的控制器里调用打印如下
[NSObject test1];//打印bbb
[ClassA test1];//打印bbb
[[[NSObject alloc]init] test];//打印aaa
[[[ClassA alloc]init] test];//打印aaa
39、对于宏定义:#define MIN(A,B) (A) < (B) ? (A) : (B)
float a = 1.0;
float b = MIN(a++, 1.5);
NSLog(@"a===%f,b==%f",a,b);
答案: a===3.000000,b==2.000000
因为运算符优先级从高到底:() ++ < ? :
然后a++是先赋值在加1,++a是先加1在赋值。
在(a++) < (1.5) ? (a++) : (1.5)中,第一次:
算式里第一个a为1,第二个a为2。
(1) < (1.5) ? (a++) : (1.5)//第一个赋完值后才开始加1,此时a为2,拿着去后面用。
(1) < (1.5) ? (2) : (1.5) 所以在三目运算时第二个a为2。所以最终b为2。
最终因为a被++了两次,所以a为3。
40、Protocol(协议)可以添加@property属性吗? Category可以添加@property属性吗?
答案
(1)在protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性。
(2)category使用@property也是只会生成setter和getter方法的声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数:
obj_setAssociatedObject
obj_getAssociatedObject
41、在MRC下,如下代码:
NSString * a = @"abc";
NSString * b = [a retain];
NSString * c = [b copy];
NSString * d = [c mutableCopy];
NSString * e = [d copy];
请写出a、b、c、d、e的引用计数各是多少?
答案
2、2、2、3、3
42、下面代码输出什么?
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
BOOL boolFlag = NO;
[userDefaults setObject:@(boolFlag) forKey:@"boolFlag"];//这里储存的NSNumber类型
//解释
id sss = [userDefaults objectForKey:@"boolFlag"];//sss值打印为:0 是NSNumber类型
BOOL yyyy = sss;//yyyy值打印为YES(NSNumber类型转BOOL类型)
//补充:BOOL值进行转换时,其基准是判断对象是否存在,如果对象存在的时候即为1,不存在则为0; 而 NSNumber 恰好就是一个对象,所以即使它为0的情况下,在编译器的眼里依然视为对象存在,被转换过成1。如果在这里要想将0转换成 NO,必须先将 NSNumber 类型的IntegerValue取出来,然后赋测能得到1。
if ([userDefaults objectForKey:@"boolFlag"]) {//有值就走这里==0
BOOL eqByPass = [userDefaults objectForKey:@"boolFlag"];//为YES
if (eqByPass) {//
NSLog(@"A");
}else{
NSLog(@"B");
}
}else{
BOOL eqByPass = [userDefaults objectForKey:@"boolFlag"];
if (eqByPass) {
NSLog(@"C");
}else{
NSLog(@"D");
}
}
答案
输出A
43、以下代码打印顺序?
//执行顺序
- (void)syncMain{
dispatch_queue_t queue = dispatch_queue_create("serial", nil);
dispatch_async(queue, ^(void){
NSLog(@"1");
});
dispatch_sync(queue, ^(void){
NSLog(@"2");
});
dispatch_async(queue, ^(void){
NSLog(@"3");
dispatch_sync(queue, ^(void){
NSLog(@"4");
});
});
}
答案
1、2、3 ,走完3的时候就崩溃了,不会打印4,线程互斥。
44、如何手动触发一个value的KVO?
答案
自动触发场景:在注册 KVO 之前设置一个初始值,注册之后,设置一个不一样的值,这样就可以触发了。
手动触发:
键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey:和 didChangevlueForKey:。在一个被观察属性发生改变之前,willChangeValueForKey: 一定会被调用,这就会记录旧的值。而当改变发生后,observeValueForKey:ofObject:change:context: 和 didChangeValueForKey:也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。
.m文件
//手动触发 value 的 KVO ,最后两行代码缺一不可
@property (nonatomic, strong) NSDate *now;
- (void)viewDidLoad{
[super viewDidLoad];
_now = [NSDate date];
[self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"1");
[self willChangeValueForKey:@"now"];//手动触发self.now的KVO,必写。
NSLog(@"2");
[self didChangeValueForKey:@"now"];//手动触发self.now的KVO,必写。
NSLog(@"4");
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"3");
}
//打印顺序是:1 2 3 4。
45、下面代码有什么问题?如果有请指出?
-(NSString *)getString{
return (__bridge NSString*)CFStringCreateWithCString(NULL, "h", kCFStringEncodingUTF8);
}
答案
NSLog(@"得到字符串:%@",[self getString]);//打印:h
没毛病啊,老铁。
46、下面代码里 [(__bridge id)obj print] 输出什么?(2019)
//MNPerson.h
@interface MNPerson : NSObject
@property(nonatomic,copy)NSString * name;
-(void)print;
@end
//MNPerson.m
#import "MNPerson.h"
@implementation MNPerson
-(void)print{
// self.name = @"1111";
NSLog(@"self.name = %@",self.name);
}
@end
//在ToolsEntController控制器的viewDidLoad里打印
NSLog(@"得到字符串:%@",[self getString]);//打印:h
id cls = [MNPerson class];
void * obj = &cls;
[(__bridge id)obj print];
答案
[(__bridge id)obj print]打印:self.name = <ToolsEntController: 0x7fb1a5508d50>
如果name有值(如值为1111)就打印:self.name = 1111
47、iOS重写单例,防止通过alloc]init或new、copy的方式开辟新空间。(20200831)
#import "NetWorkTools.h"
@implementation NetWorkTools
static id _instanceType = nil;
//自定义类方法
+(instancetype)sharadNetWorkTools{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instanceType = [[self alloc]init];
});
return _instanceType;
}
//重写父类方法,防止通过alloc]init或new的方式开辟新空间
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instanceType = [super allocWithZone:zone];
});
return _instanceType;
}
//防止对象copy操作
-(id)copyWithZone:(NSZone *)zone{
return _instanceType;
}
@end
48、iOS App推送流程,APNS-苹果服务器。(20200831)
ios 设备 app在安装完成后,要完成消息的推送,需要以下步骤:
(1) app 将设备的 UUID(苹果设备的唯一标识) 和 app 的 bundled(项目的唯一标识)发送到 APNS(苹果服务器),请求 deviceToken。
(2)APNS 在收到 app 发送的请求后,会将加密后的 deviceToken 发送给 app。
(3)app 拿到 deviceToken 后,将 deviceToken 传到运营商服务器上,比如极光。这样以来,运营商就获取了推送权限。
(4)当有消息需要推送给某一位用户时,运营商就可以通过客户端对应的 deviceToken 选择推送对象。将推送消息和 deviceToken 一并发送给 APNS。
(5)APNS 根据发送过来的 deviceToken ,将推送消息发送到指定的设备上,完成消息的推送工作。