2020iOS开发笔记

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,当有异常抛出时就会进入断点。


截屏2020-07-31 下午1.50.37.png

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++混编的注意事项

  1. oc中用到c++部分时,要记得把扩展名改为.mm,即使不直接在文件中写C++,只是引入C++的头文件也要改成.mm,否则C++文件类的代码会报错,类似没有C++的编译环境。
    .m 和.mm 的区别是告诉编译器在编译时要加的一些参数。.mm也可以命名成.m,手动加编译参数。

  2. 如果在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);

你可能感兴趣的:(2020iOS开发笔记)