1.ViewController不释放的原因:
- viewController中有Timer.
- block中有self. 要改为弱引用的self,如:__weak typeof(self) weakself = self;
2.问题:重新载入tableview数据,后tableview里的cell都消失了
原因是在cell中刷新tableview的时候最好在在主线程调用,如:
//在cell中setModel中的代码
dispatch_async(dispatch_get_main_queue(), ^{ //必须加这个在主线程调用,要不界面会消失
//通知聊天界面刷新列表
[[NSNotificationCenter defaultCenter] postNotificationName:kMessageListChanged object:nil];
//[weakself.delegate toReloadMessageList];
});
3.防止应用崩溃的简单方法:
这个简单的方法是使用try catch语句,原先会崩溃的代码放在try中就不会崩溃了。
@try {
NSMutableArray *array = @[@"a", @"b"]; //没有使用mutableCopy,返回的是不可变数组
//由于array其实是不可变数组,这里会抛出异常,如果没有放在try中会导致程序崩溃。
array[1] = @"abc";
} @catch (NSException *exception) {
NSLog(@"myexception: %@ inFunction:%s", exception, __func__);
} @finally {
//不论是否有catch到异常,都会执行finally中的代码 这也挺用,比如可以替代goto。避免因为returen错过了一定要执行的代码
}
那么没有崩溃我们怎么发现被try隐藏的问题呢?可以在catch中打出日志方便查找问题,或者在断点中添加All Exceptions,当有异常抛出时就会进入断点。
4. iOS中的过滤器NSPredicate:
ios中也有过滤器可以用来过滤数据模型对象的数组或集合。这使我们不需要自己用for循环和if来过滤tableview的数据。让过滤数据和在数据库中用sql语句一样方便。
谓词(NSPredicate)是Objective-C中针对数据集合的一种逻辑筛选条件。用法如下:
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age > 10"];
NSArray *newArray = [objectArray filteredArrayUsingPredicate:predicate];
条件表达式的左边是一个对象的属性键值(键路径keypass)。中间是逻辑运算符,它们与SQL语句中的语法基本相对应,除了最基本的逻辑运算符:>、==、<=、&&等之外,还有逻辑词IN、CONTAINS、like等。
5.如何在UIViewController中得知应用回到了前台:
在AppDelegate有应用生命周期的回调方法,可在UIViewController要怎么得知呢?可以使用添加通知监听的方法,如:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
6. id类型修饰符的应用。
id的说明:使用id类型将对象的类型推迟到运行时才确定,由赋给它的对象类型决定对象指针的类型。另外,类型确定推迟到运行时之后,可以通过NSObject的isKindOfClass方法动态判断对象最后的类型(动态类型识别)。也就是说,id修饰的对象为动态类型对象,其他在编译器指明类型的为静态类型对象。
id的用法:在不知道dict中的类型是NSString还是NSNumber类型时,可以用id类型的对象来接收,此对象也可以调用NSString对象的方法(貌似可调用任意类型的方法)。为了防止意外情况,可以判断类型再调用方法,也可以把代码放到try catch里。
7. 用AFNetworking网络请求,如何post JSON数据。
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = TimeOutSecond; //超时时间
//以下两句话是让body里传的是json格式
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSString *strToken = [LoginDataModel sharedLoginDataModel].token;
[manager.requestSerializer setValue:strToken forHTTPHeaderField:@"token"];
[manager POST:strUrl parameters:bean progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
除了设置Content-Type,还要设置为json的序列化。这样传过去的直接是json数据,而不需要有参数的key。
8. 录音中断的现象和解决方法。
现象是并感觉不到中断,录音结束的回调函数并不会提前结束,只是对比保存的录音文件的时长和录音过程的时长会发现,实际并没有录完整。原因是[AVAudioSession sharedInstance]的setCategory方法会中断录音。也就是说录音时不能播放声音。比如录音时收到消息就不能播放提示音,而应该跳过。
录制语音前需要对录音设备属性Category设置如下:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:nil];
或[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
由于程序逻辑可能出现问题,在录音过程中重新设置Category属性,比如设置如下:
[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
这个设置会取消设备录音功能,从而导致录音中断
9. 全局变量的用法。
在.m文件中定义,不用加extern。不要在.h中定义要不编译时会报重复定义的错误。
其他地方用到时在变量的声明前加extern。
10. MJExterntion用法之在模型中包含数组模型。
需要重写类函数mj_objectClassInArray,说明对应的某个Array属性里是什么类对象。
//改变字典的key对应的属性名
+(NSDictionary *)mj_replacedKeyFromPropertyName{
return @{@"fId":@"id"};
}
//说明数组中包含的模型是什么类,OaHandleModel是类,handleLogList是模型中的某个属性名。
+ (NSDictionary *)mj_objectClassInArray{
return @{@"handleLogList":@"OaHandleModel"};
}
11.不需要授权就能获取的有趣设备信息。
UIDevice *device = [UIDevice currentDevice];
device.batteryMonitoringEnabled = YES; //需要打开才能获取到设备电量
device.name; //设备名称,一般默认会是“谁的iPhone”
device.batteryLevel; //设备电量
device.identifierForVendor; //可以用做设备ID虽然卸载重装后会变化
12.新增SceneDelegate之后,对ios13.0以下版本的适配。(swift)
ios13.0之后,原来在AppDelegate中的window属性,转移到了SceneDelegate。要适配ios13.0以下的版本,又不想使用Storyboard,可以在AppDelegate自己添加window属性并初始化它。代码如下
// 在AppDelegate.swift中
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// let systemVersion = UIDevice.current.systemVersion
// let version = systemVersion.components(separatedBy: ".")
// if (Int(version[0])! < 13){}
if #available(iOS 13, *) {
}else {
let vc = MyTabController()
//let nav = UINavigationController(rootViewController: vc);
//创建window 如果是用默认的storyboard,下面的代码可以不写
window = UIWindow.init()
window?.frame = UIScreen.main.bounds
window?.makeKeyAndVisible()
window?.rootViewController = vc //UIStoryboard.init(name: "Main", bundle: nil).instantiateInitialViewController()
//UIApplication.shared.keyWindow?.rootViewController = vc
//let rootVC = UIApplication.shared.delegate as! AppDelegate
//rootVC.window?.rootViewController = vc
}
return true
}
13. 出现[__NSSingleEntryDictionaryI length]: unrecognized selector sent to instance的情况。
//当nameList不是NSString数组而是NSDictionary数组时会出现错误:'-[__NSSingleEntryDictionaryI length]: unrecognized selector sent to instance 0x283ce9580'
for (NSString *strName in nameList) {
GradeButton *btn = [self createButton];
[btn setTitle:strName forState:UIControlStateNormal];
}
//可能是因为setTitle时有用到获取字符串长度的方法。
14.将代码同步到主线程执行的三种方法如下:
// 1.NSThread
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
- (void)updateUI {
// UI更新代码
self.alert.text = @"Hello!";
}
// 2.NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// UI更新代码
self.alert.text = @"Hello!";
}];
// 3.GCD
dispatch_async(dispatch_get_main_queue(), ^{
// UI更新代码
self.alert.text = @"Hello!";
});
15.runLoop在实际开发中涉及到的地方。
日常开发中,与 runLoop 接触得最近可能就是通过 NSTimer 了。一个 Timer 一次只能加入到一个 RunLoop 中。我们日常使用的时候,通常就是加入到当前的 runLoop 的 default mode 中,而 ScrollView 在用户滑动时,主线程 RunLoop 会转到 UITrackingRunLoopMode 。而这个时候, Timer 就不会运行。
有如下两种解决方案:
第一种: 设置RunLoop Mode,例如NSTimer,我们指定它运行于 NSRunLoopCommonModes ,这是一个Mode的集合。注册到这个 Mode 下后,无论当前 runLoop 运行哪个 mode ,事件都能得到执行。
第二种: 另一种解决Timer的方法是,我们在另外一个线程执行和处理 Timer 事件,然后在主线程更新UI。
16.导致崩溃的异常编码
异常编码,就在异常信息里。一些被系统杀掉的情况,可以通过异常编码来分析。维基百科里列出了 44 种异常编码,但常见的就是如下三种:
0x8badf00d,表示 App 在一定时间内无响应而被 watchdog 杀掉的情况。
0xdeadfa11,表示 App 被用户强制退出。
0xc00010ff,表示 App 因为运行造成设备温度太高而被杀掉。
17.Objective-C与C和C++混编的注意事项
oc中用到c++部分时,要记得把扩展名改为.mm,即使不直接在文件中写C++,只是引入C++的头文件也要改成.mm,否则C++文件类的代码会报错,类似没有C++的编译环境。
.m 和.mm 的区别是告诉编译器在编译时要加的一些参数。.mm也可以命名成.m,手动加编译参数。如果在c ++文件中使用c函数。您应该使用extern "c"{}。在.h文件中
#ifdef __cplusplus
extern "C" {
#endif
swrve_currency_given(parameter1, parameter2, parameter3);// a c function
#ifdef __cplusplus
}
#endif
extern“ C”旨在由C ++编译器识别,并通知编译器所注明的功能已(或将要)以C样式进行编译。
如果要链接到已编译为C代码的库。使用
extern "C" {
#include "c_only_header.h"
}
18.实现可以取消的延时定时器
使用GCD的dispatch_after无法取消,可以使用[self performSelector: withObject: afterDelay: ]
需要取消时调用[NSObject cancelPreviousPerformRequestsWithTarget:self] 来取消
19.图片转换函数
// return image as PNG. May return nil if image has no CGImageRef or invalid bitmap format
NSData *data = UIImagePNGRepresentation(image);
// return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)
UIImageJPEGRepresentation(UIImage, 0.8);