iOS笔记

1.@dynamic

关键字是用来明确告诉编译器禁止自动合成属性变量的存取方法和加下划线的默认变量名。默认情况如果不用@dynamic关键字,那么编译器会自动合成那些没有定义的存取方法,而那些程序员已经定义的存取方法不会再去合成,即程序员定义的存取方法优先级高。例如,如果此时程序员自定义了setter方法,那么编译器只会自动合成getter 方法,而不会再去合成已经定义的setter方法。

2.多态

(polymorphism)在面向对象语言中指同一个接口可以有多种不同的实现方式,Objective-c 中的多态则是不同对象对同一消息的不同相应方式,子类通过重写父类的方法来改变同一消息的实现,体现多态性。另外,c++中的多态主要是通过virtual关键字(虚函数、抽象类等)来实现,具体来说指允许父类的指针指向子类对象,使其成为一个更泛化】容纳度更高的父类对象,这样的父类对象就可以根据实际是哪种子类对象来调用父类同一个接口的不同子类实现。

3. UITableView

UITableView 复用实现中的cell的复用是由两个数组实现的,一个是visiableCells数组,保存当前屏幕可兼得cell,另一个reusabletableCells数组用来保存那些可复用的cell

4.delegate

在objective-c中通过protocol来进行实现,指的是一个对象在某些特定时刻通知其他类的对象去做一些任务,但不需要获取的那些对象的指针,两者共同来完成一件事,实现不同对象之间的通信。

5.UITabbar

在iOS13中层次发生改变,无法通过设置shadowimage去掉上面的线**,解决方法如下:
for swift:

if #available(iOS 13, *) {
        let appearance = self.tabBar.standardAppearance.copy()
        appearance.backgroundImage = UIImage()
        appearance.shadowImage = UIImage()
        appearance.shadowColor = .clear
        self.tabBar.standardAppearance = appearance
    } else {
        self.tabBar.shadowImage = UIImage()
        self.tabBar.backgroundImage = UIImage()
    }

for oc:

if (@available(iOS 13.0, *)) {
    UITabBarAppearance* appearance =  self.tabBar.standardAppearance.copy;
    appearance.backgroundImage = [UIImage new];
    appearance.shadowImage = [UIImage new];
    appearance.shadowColor = [UIColor clearColor];
    // Title adjustment
    appearance.stackedLayoutAppearance.normal.titlePositionAdjustment = UIOffsetMake(0, -12);
    self.tabBar.standardAppearance = appearance;
} else {
    self.tabBar.shadowImage = [UIImage new];
    self.tabBar.backgroundImage = [UIImage new];
}

6.ios13 控制器模态跳转动画默认为卡片样式

UIViewController *vc = [[UIViewController alloc]init];
[vc setModalPresentationStyle: UIModalPresentationFullScreen];
[self presentViewController:vc animated:YES completion:nil];

7.Dark Mode 颜色主题相关 UIColor 适配

UIColor *dynamicColor =  [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * provider) {
 //使用 provider 判断,有时会出问题
    if(keyWindow.isDark){
        return darkColor;
    }
    return lightColor;
 }];

8.APNS 用于java 服务器p12证书生成,(主要为合成阶段)

1.将.cer转化为pem:
openssl x509 -inform der -in aps_development.cer -out PushChatCert.pem
2.将p12转化为pem:
openssl pkcs12 -nocerts -out PushChatKey.pem -in Push.p12
3.将1、2的pem合并为一个pem:
cat PushChatCert.pem PushChatKey.pem > ck.pem
4.将c中pem转化为p12:
openssl pkcs12 -export -in ck.pem -out pushcer.p12

9.Podfile 中无输出信息时

post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    if config.name.include?("QapDistribution")
      config.build_settings['GCC_OPTIMIZATION_LEVEL'] = '0'
    end
  end
end

10.在Xcode 中从动态库剥离不需要的框架:

TARGETS —> Build Phases —> Run Script 添加shell:

APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
  FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
  FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"    
  echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"

   EXTRACTED_ARCHS=()

  for ARCH in $ARCHS
  do
    echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
    lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
    EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done

echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"

echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
done

11.查看静态库支持的架构(armv7、armv7s 、arm64、x86_64)

lipo -info openh264.a

静态库的合并:
lipo -create lib1-path lib2-path -output combine-lib
例如:lipo -create /Users/xu/Desktop/lib_ armv7/libpjlib.a /Users/xu/Desktop/lib_ arm64/libpjlib.a -output /Users/xu/Desktop/lib_all/libpjlib.a

12.UITableView性能优化,超实⽤用

1. Cell重⽤用

1.1.数据源⽅方法优化

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

在可⻅见的⻚页⾯面会重复绘制⻚页⾯面,每次刷新显示都会去创建新的Cell,⾮非常耗费性 能。
解决⽅方案:⾸首先创建⼀一个静态变量量reuseID(代理理⽅方法返回Cell会调⽤用很多次,防⽌止 重复创建,static保证只会被创建⼀一次,提⾼高性能),然后,从缓存池中取相应 identifier的Cell并更更新数据,如果没有,才开始alloc新的Cell,并⽤用identifier标识 Cell。每个Cell都会注册⼀一个identifier(重⽤用标识符)放⼊入缓存池,当需要调⽤用的时 候就直接从缓存池⾥里里找对应的id,当不不需要时就放⼊入缓存池等待调⽤用。(移出屏幕的 Cell才会放⼊入缓存池中,并不不会被release)所以在数据源⽅方法中做出如下优化:
// 调⽤用次数太多,static 保证只创建⼀一次reuseID,提⾼高性能
static NSString *reuseID = “reuseCellID”;
// 缓存池中取已经创建的cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID];

1.2.缓存池的实现

当Cell要alloc时,UITableView会在堆中开辟⼀一段内存以供Cell缓存之⽤用。Cell的重 ⽤用通过identifier标识不不同类型的Cell,由此可以推断出,缓存池外层可能是⼀一个可变 字典,通过key来取出内部的Cell,⽽而缓存池为存储不不同⾼高度、不不同类型(包含图⽚片、 Label等)的Cell,可以推断出缓存池的字典内部可能是⼀一个可变数组,⽤用来存放不不同 类型的Cell,缓存池中只会保存已经被移出屏幕的不不同类型的Cell。

1.3.缓存池获取可重⽤用Cell两个⽅方法的区别

-(nullable __kindof UITableViewCell *)dequeueReusableCellWithIdentifier: (NSString *)identifier;

这个⽅方法会查询可重⽤用Cell,如果注册了了原型Cell,能够查询到,否则,返回nil;⽽而 且需要判断if(cell == nil),才会创建Cell,不不推荐

-(__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(6_0);

使⽤用这个⽅方法之前,必须通过xib(storyboard)或是Class(纯代码)注册可重⽤用 Cell,⽽而且这个⽅方法⼀一定会返回⼀一个Cell
注册Cell

- (void)registerNib:(nullable UINib *)nib forCellReuseIdentifier:(NSString
*)identifier NS_AVAILABLE_IOS(5_0);
- (void)registerClass:(nullable Class)cellClass forCellReuseIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(6_0);

好处:如果缓冲区 Cell 不不存在,会使⽤用原型 Cell 实例例化⼀一个新的 Cell,不不需要再判 断,同时代码结构更更清晰。

2. 定义⼀一种(尽量量少)类型的Cell及善⽤用hidden隐藏(显示)subviews

2.1.⼀一种类型的Cell

分析Cell结构,尽可能的将 相同内容的抽取到⼀一种样式Cell中,前⾯面已经提到了了
Cell的重⽤用机制,这样就能保证UITbaleView要显示多少内容,真正创建出的Cell可能 只⽐比屏幕显示的Cell多⼀一点。虽然Cell的’体积’可能会⼤大点,但是因为Cell的数量量不不会 很多,完全可以接受的。好处:

  • 减少代码量量,减少Nib⽂文件的数量量,统⼀一⼀一个Nib⽂文件定义Cell,容易易修改、维护
  • 基于Cell的重⽤用,真正运⾏行行时铺满屏幕所需的Cell数量量⼤大致是固定的,设为N个。所 以如果如果只有⼀一种Cell,那就是只有N个Cell的实例例;但是如果有M种Cell,那么运 ⾏行行时最多可能会是“M x N = MN”个Cell的实例例,虽然可能并不不会占⽤用太多内存,但是 能少点不不是更更好吗。

2.2.善⽤用hidden隐藏(显示)subviews

只定义⼀一种Cell,那该如何显示不不同类型的内容呢?答案就是,把所有不不同类型的 view都定义好,放在cell⾥里里⾯面,通过hidden显示、隐藏,来显示不不同类型的内容。毕 竟,在⽤用户快速滑动中,只是单纯的显示、隐藏subview⽐比实时创建要快得多。

3. 提前计算并缓存Cell的⾼高度

在iOS中,不不设UITableViewCell的预估⾏行行⾼高的情况下,会优先调
⽤用”tableView:heightForRowAtIndexPath:”⽅方法,获取每个Cell的即将显示的⾼高度, 从⽽而确定UITableView的布局,实际就是要获取contentSize(UITableView继承⾃自 UIScrollView,只有获取滚动区域,才能实现滚动),然后才调 ⽤用”tableView:cellForRowAtIndexPath”,获取每个Cell,进⾏行行赋值。如果项⽬目中模块有 10000个Cell需要显示,可想⽽而知...
解决⽅方案:我个⼈人认为,可以创建⼀一个frame模型,提前计算每个Cell的⾼高度。参考 其中⼀一篇博客的时候,在解决这个问题的时候,可以将计算Cell的⾼高度放⼊入数据模 型,但这与MVC设计模式可能稍微有点冲突,这个时候我就想到MVVM这种设计模 式,这个时候才能稍微有点MVVM这种设计模式的优点(其实还是很不不理理解的),可 以讲计算Cell⾼高度放⼊入ViewModel(视图模型)中,让Model(数据模型)只负责处 理理数据。

4.异步绘制(⾃自定义Cell绘制)

遇到⽐比较复杂的界⾯面的时候,如复杂点的图⽂文混排,上⾯面的那种优化⾏行行⾼高的⽅方式可
能就不不能满⾜足要求了了,当然了了,由于我的开发经验尚短,说实话,还没遇到要将⾃自定 义的Cell重新绘制

5.滑动时,按需加载

开发的过程中,⾃自定义Cell的种类千奇百怪,但Cell本来就是⽤用来显示数据的,不不说

100%带有图⽚片,也差不不多,这个时候就要考虑,下滑的过程中可能会有点卡顿,尤其 ⽹网络不不好的时候,异步加载图⽚片是个程序员都会想到,但是如果给每个循环对象都加 上异步加载,开启的线程太多,⼀一样会卡顿,我记得好像线程条数⼀一般3-5条,最多 也就6条吧。这个时候利利⽤用UIScrollViewDelegate两个代理理⽅方法就能很好地解决这个问 题。

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate: (BOOL)decelerate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

6.缓存View

当Cell中的部分View是⾮非常独⽴立的,并且不不便便于重⽤用的,⽽而且“体积”⾮非常⼩小,在内 存可控的前提下,我们完全可以将这些view缓存起来。当然也是缓存在模型中。

7.避免⼤大量量的图⽚片缩放、颜⾊色渐变等

尽量量显示“⼤大⼩小刚好合适的图⽚片资源”

8.避免同步的从⽹网络、⽂文件获取数据

Cell内实现的内容来⾃自web,使⽤用异步加载,缓 存请求结果

9.渲染

9.1.减少subviews的个数和层级

⼦子控件的层级越深,渲染到屏幕上所需要的计算量量就越⼤大;如多⽤用drawRect绘制元素,替代⽤用view显示

9.2.少⽤用subviews的透明图层

对于不不透明的View,设置opaque为YES,这样在绘制该View时,就不不需要考虑 被View覆盖的其他内容(尽量量设置Cell的view为opaque,避免GPU对Cell下⾯面的内容 也进⾏行行绘制)

9.3.避免CALayer特效(shadowPath) 给Cell中View加阴影会引起性能问题

如下⾯面代码会导致滚动时有明显的卡顿:

view.layer.shadowColor = color.CGColor; 
view.layer.shadowOffset = offset; 
view.layer.shadowOpacity = 1; 
view.layer.shadowRadius = radius;

13.本地端口抓包:

执行命令:

sudo tcpdump -i any -n -X port 4567

14.使用clang将OC代码转为C++

 xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m 

15.ffmpeg 需要添加的依赖库

libbz2.1.0.dylib、libiconv.2.dylib 、AudioToolBox.framework
找不到 dylib的方法: 点击add other 快捷键CMD+Shift+G输入/usr/lib

16.Xcode11 xcrun altool上传.ipa至iTunes connect:

验证账号密码:
xcrun altool --validate-app -f 包体绝对路径.ipa -t iOS -u 用户名 -p 密码
xcrun altool --validate-app -f ipa_path -t iOS -u 用户名-p 密码

上传:
xcrun altool --upload-app -f 包体绝对路径.ipa -t iOS -u 用户名 -p 密码

17.视图层级更改

bringSubviewToFront: 将一个子视图视图显示到最前面父视图前面

sendSubviewToBack: 把子视图推到父视图后面

exchangeSubviewAtIndex: withSubviewAtIndex: 将视图按index进行切换

18.上传代码到GitHub

已有帐号在github

1.cd 到要上传的文件

2.git init

3.git add .(一般分享这个文件的所有代码,如果指定文件,需要吧“.”改为文件名,该步骤只是选择要加入仓库的文件)

4.git commit -m “提交信息”

5.git remote add origin +仓库地址(eg :git remote add origin https://github.com/jianchaoxu/breakupArray.git),如果origin存在,执行git remote rm origin 再添加

6.git pull —rebase origin master 更新远程到本地

7.git push origin master (第一次添加使用git push -u origin master)

8.git pull

19.ios 13 应用可能无法获取正确的devicetoken

在 iOS 13 环境下,由于苹果更改了基础框架的 API,导致旧版本的 Objc SDK(<= 11.6.6)无法通过文档中的下列方式上传有效的 device token
以前的写法

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    //d639f822 ad332e12 d8a63f7e ab2a1b19 eae6d582 8c8c964c 8178ef97 54175021
    NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
    DLog(@"deviceToken token = %@",token);
}

iOS 13 下输出为:
{length=32,bytes=0x38c6d917f4d89e8625d79735ea797e89...5b10fa0cee26309a}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    //ios 13 {length=32,bytes=0x38c6d917f4d89e8625d79735ea797e89...5b10fa0cee26309a}
    NSUInteger datalength = deviceToken.length;
    if (datalength > 0) {
        const unsigned char *dataBuffer = deviceToken.bytes;
        NSMutableString *hexstring = [NSMutableString stringWithCapacity:(datalength * 2)];
        for (int i = 0; i" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
    DLog(@"deviceToken token = %@",token);
     */
}

20.fmdb 常用语法

常用关键字
select、insert、update、delete、from、create、where、desc、order、by、group、table、alter、view、index
数据类型
integer : 整型值
real : 浮点值
text : 文本字符串
blob : 二进制数据(比如文件)
表的创建
"CREATE TABLE IF NOT EXISTS %@ ('%@' INTEGER PRIMARY KEY AUTOINCREMENT, '%@' TEXT NOT NULL,'%@' TEXT NOT NULL, '%@' TEXT NOT NULL, '%@' TEXT NOT NULL)"
删除
条件删除:"DELETE FROM %@ WHERE photographId ='%@'"
drop table 表名 ;
drop table if exists 表名 ;
查询某个时间前的前几条数据
"SELECT * FROM %@ WHERE submitTime<'%@' ORDER BY submitTime DESC LIMIT 50"
更新数据(update)
格式
update 表名 set 字段1 = 字段1的值, 字段2 = 字段2的值, …
计算记录的数量
格式
select count (字段) from 表名 ;
select count ( * ) from 表名 ;
示例
select count (age) from t_student ;
select count ( * ) from t_student where score >= 60;
排序
查询出来的结果可以用order by进行排序
select * from t_student order by 字段 ;
select * from t_student order by age ;
默认是按照升序排序(由小到大),也可以变为降序(由大到小)
select * from t_student order by age desc ;  //降序
select * from t_student order by age asc ;   // 升序(默认)
也可以用多个字段进行排序
select * from t_student order by age asc, height desc ;
先按照年龄排序(升序),年龄相等就按照身高排序(降序)
limit
使用limit可以精确地控制查询结果的数量,比如每次只查询10条数据
格式
select * from 表名 limit 数值1, 数值2 ;
示例
select * from t_student limit 4, 8 ;
可以理解为:跳过最前面4条语句,然后取8条记录
limit常用来做分页查询,比如每页固定显示5条数据,那么应该这样取数据

第1页:limit 0, 5
第2页:limit 5, 5
第3页:limit 10, 5
…
第n页:limit 5*(n-1), 5
select * from t_student limit 7 ;这条语句的作用相当于select * from t_student limit 0, 7 ;表示取最前面的7条记录
约束
1.简单约束
建表时可以给特定的字段设置一些约束条件,常见的约束有
not null :规定字段的值不能为null
unique :规定字段的值必须唯一
default :指定字段的默认值
(建议:尽量给字段设定严格的约束,以保证数据的规范性)

示例
create table t_student (id integer, name text not null unique, age integer not null default 1) ;
name字段不能为null,并且唯一
age字段不能为null,并且默认为1

2.主键约束

(1)简单说明
如果t_student表中就name和age两个字段,而且有些记录的name和age字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据
良好的数据库编程规范应该要保证每条记录的唯一性,为此,增加了主键约束
也就是说,每张表都必须有一个主键,用来标识记录的唯一性
(2)什么是主键?
主键(Primary Key,简称PK)用来唯一地标识某一条记录
例如t_student可以增加一个id字段作为主键,相当于人的身份证
主键可以是一个字段或多个字段
(3)主键的设计原则
主键应当是对用户没有意义的 
永远也不要更新主键
主键不应包含动态变化的数据
主键应当由计算机自动生成
(4)主键的声明
在创表的时候用primary key声明一个主键
create table t_student (id integer primary key, name text, age integer) ;
integer类型的id作为t_student表的主键
主键字段
只要声明为primary key,就说明是一个主键字段
主键字段默认就包含了not null 和 unique 两个约束
说明:如果想要让主键自动增长(必须是integer类型),应该增加autoincrement
create table t_student (id integer primary key autoincrement, name text, age integer) ;
3.外键约束
利用外键约束可以用来建立表与表之间的联系
外键的一般情况是:一张表的某个字段,引用着另一张表的主键字段
新建一个外键
create table t_student (id integer primary key autoincrement, name text, age integer, class_id integer, constraint fk_student_class foreign key (class_id) references t_class (id));
t_student表中有一个叫做fk_t_student_class_id_t_class_id的外键
这个外键的作用是用t_student表中的class_id字段引用t_class表的id字段
4.表连接查询
表连接查询:需要联合多张表才能查到想要的数据
表连接的类型
内连接:inner join 或者 join  (显示的是左右表都有完整字段值的记录)
左外连接:left outer join (保证左表数据的完整性) 
示例
查询0316iOS班的所有学生
select s.name,s.age from t_student s, t_class c where s.class_id = c.id and c.name = ‘0316iOS’;
文章来源:https://blog.csdn.net/ciwei_numberone/article/details/48005317
插入
"INSERT INTO %@ ('%@', '%@', '%@', '%@') values ('%@', '%@', '%@', '%@')"
当要插入较多数据时,相比每条数据来之前频繁的open close db节省大量时间:
 FMDatabase *db = [FMDatabase databaseWithPath:[DataManager documentWithName:kEbemateData]];
    if ([db open]) {
        [db beginTransaction];
        BOOL isrollback = NO;
        @try {
            for (NSDictionary *dic in arr) {
                NSString *subtime = dic[@"submitTime"];
                NSString *photoid = dic[@"photographId"];
                NSString *picurl = dic[@"picUrl"];
                NSString *mac = dic[@"mac"];
                
                NSString *check_query = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ = '%@'",tbname,@"photographId",photoid];
                FMResultSet *sz = [db executeQuery:check_query];
                int count = 0;
                while ([sz next]) {
                    count = [sz intForColumn:@"photographId"];
                }
                if (count == 0) {
                    NSString *insert_query = [NSString stringWithFormat:@"INSERT INTO %@ ('%@', '%@', '%@', '%@') values ('%@', '%@', '%@', '%@')",tbname,@"submitTime",@"photographId",@"picUrl",@"mac",subtime,photoid,picurl,mac];
                    
                    BOOL res = [db executeUpdate:insert_query];
                    if (!res) {
                        DLog(@"album 录入失败");
                    }
                }
            }
        } @catch (NSException *exception) {
            isrollback = YES;
            [db rollback];
        } @finally {
            if (!isrollback) {
                [db commit];
            }
        }
        [db close];
    }

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