文章

iOS 基础总结

1. iOS icon 生成网址推荐. 可以一键生成所有格式的 icon 图   https://icon.wuruihong.com/

2. iOS 出包流程. 新建一个 Xcode 工程为例

1
2
3
4
5
6
7
1. 打开 Xcode 工程. 进入工程属性配置(左上角)—>Edit Scheme——>Build Configuration 配置 Release
1. 查看 General 下,Automatically manage signning 状态取消. 另外看下应用配置 name. version 等版本以及下面的签名配置是否 ok。
1. 检查完毕之后. Commend+B 可以直接编译运行。
1. 到工程目录下 Products 目录下会生成一个 Demo.app 文件. 打开该文件目录拷贝到一个命名为 Playload 文件夹内。
1. 压缩 Playload 文件重命名其后缀名为.ipa 即可。
注意事项:出包时设备信息选择全部而不是某一台设备. 也就是 Generic iOS Device. 如果选择了某一台模拟器. 或者在 Build 之前设置工程编译属性为 Debug. 都有可能在安装时出现问题。另外不要创建多台模拟器. 会吃内存。
1. 上面这种方式是很久之前出包的方式了. 正常直接构建出 ipa 即可

3. iOS ipa 应用文件安装

1
1. Appstore 下载一个 Apple Configurator 进行安装

4. iOS 证书与描述文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1. 苹果证书分为调试证书与生产证书. 用法以及申请方式可上网查询。
2. 描述文件可以在申请的时候. 设置为包名通用的. 后面对应不同工程的包名都可以编译调试(个人或企业签皆可)。
3. P12 文件导入成功后可以在 Mac 钥匙串中找到。
4. 描述文件导入之后. 重命名描述文件中的 UUID 后并保存在~/library/MobileDevice/Provisioning Profiles 文件夹内。
5. P12 导入证书位置~/library/Keychains/
6. 打包时会遇到导入证书有问题. 可以查看一下证书是否过期。查看命令如下

```
#查看p12证书
openssl pkcs12 -legacy -in '/xxx.p12' -nodes | openssl x509 -noout -dates
openssl pkcs12 -legacy -in '/xxx.p12' -nodes -passin pass:123 | openssl x509 -noout -dates

#查看p12证书日期
openssl pkcs12 -in /xxx/xxx.p12  -nodes -passin pass:"123" | openssl x509 -noout -dates

#查看证书UUID和证书名称
security cms -D -i xxx.mobileprovision | grep -A1 "UUID"
openssl pkcs12 -legacy -in xxx.p12 -nodes -passin pass:"密码" | openssl x509 -noout -subject

#查看描述文件
openssl smime -inform der -verify -noverify -in /xxx/xxx.mobileprovision
```

5. Xcode 常用快捷键

1
2
3
1. Commend+R 工程运行
1. Commend+B 编译工程
1. Commend+/ 注释

6. 系统环境与框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
1. Cocoa OS X 与 IOS 操作系统的程序的运行环境。
1. Cocoa 两个基本框架:

    ```
    1. Foundation 基础库

    2. UIKit UI库. 高级对象
    ```

3. 面向对象 - 和 java 不一样的是没有垃圾回收机制. 除了有 C++与 java 的特点之外
优点:

    ```
    动态类:运行时确定类的对象。

    动态绑定:运行时确定需要调用的方法

    动态加载:运行时为程序加载新的模块
    ```

4.  核心概念

    |概念|C++ 写法|Objective-C 写法|备注|
    |---|---|---|---|
    |**头文件**|`.h`|`.h`|一样|
    |**源文件**|`.cpp`|`.m` (纯 ObjC) / **`.mm`\*\*** (混合)**|**UE4 只用 .mm**|
    |**类定义**|`class MyClass : public Parent`|`@interface MyClass : Parent`|ObjC 所有类继承自  `NSObject`|
    |**类实现**|`MyClass::Function() { ... }`|`@implementation MyClass ... @end`||
    |**实例方法**|`void MyFunc()`|`\- (void)myFunc;`|减号  `-`  代表成员函数|
    |**静态方法**|`static void MyFunc()`|`\+ (void)myFunc;`|加号  `+`  代表静态函数|
    |**方法调用**|`obj->MyFunc()`|`\[obj myFunc\];`|**中括号是核心**|
    |**包含头文件\*\*|`#include "MyClass.h"`|`#import "MyClass.h"`|import 自动防止重复包含|

    > "+" Class Method/Static

    > "-" Instance Method

    在阅读第三方 iOS SDK 文档时. 你经常会看到这样的描述:
    **方法定义:** 
    ```c++
    + (void)initWithAppId:(NSString *)appId;

    - (void)login;
    ```

    **开发者的解读方式:**
    1. 看到 **`+`** -> 脑补 `static` -> 这是一个**初始化**或**配置**用的全局方法 -> 代码里写 `[SDKName initWithAppId:...]`。

    2. 看到 **`-`** -> 这是一个**功能**方法 -> 我必须先拿到 SDK 的**单例对象**或者**实例**才能调用 -> 代码里写 `[[SDKName shared] login]`。
    

>实战案例A. 头文件 (DemoManager.h)

```
#import <Foundation/Foundation.h>
    // @interface 类名 : 父类
    @interface DemoManager : NSObject
    // 声明单例类方法 (+)
    (instancetype)sharedManager;
    // 声明一个实例属性 (相当于 Java 的 public String token)
    @property (nonatomic, copy) NSString *authToken;
    // 声明一个实例方法 (-)
    (void)loginWithUserId:(NSString *)uid;
@end
```

>实现文件

```
    #import "DemoManager.h"

        @implementation DemoManager

        // 1. 声明一个静态变量持有实例
        static DemoManager *_sharedInstance = nil;

        // 2. 实现单例方法
        + (instancetype)sharedManager {
            // dispatch_once 保证代码块在整个 App 生命周期只执行一次
            // 它是绝对线程安全的. 不需要 synchronized
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                _sharedInstance = [[super alloc] init];
                // 可以在这里做一些初始化. 比如 _sharedInstance.authToken = @"";
            });
            return _sharedInstance;
        }

        // 3. 实现功能方法
        - (void)loginWithUserId:(NSString *)uid {
            NSLog(@"[iOS] 正在登录用户: %@", uid);

            // 模拟登录成功. 设置属性
            // self.authToken 相当于 this.setAuthToken(...)
            self.authToken = [NSString stringWithFormat:@"token_%@", uid];
        }

    @end
```
>  具体调用

```
// 引入头文件
#import "DemoManager.h"

void MyUE4Function() {
    // 1. 获取单例并调用方法
    // [类名 方法] -> 拿到对象
    // [对象 方法] -> 执行逻辑
    [[DemoManager sharedManager] loginWithUserId:@"1001"];

    // 2. 分步写的形式 (更容易理解)
    DemoManager *manager = [DemoManager sharedManager];
    [manager loginWithUserId:@"1002"];

    // 3. 获取属性 (Getter)
    // 点语法 .authToken 其实是自动调用了 [manager authToken] 方法
    NSString *token = manager.authToken;

    NSLog(@"当前 Token 是: %@", token);
}

```

7. iOS 依赖库

1
2
3
4
5
6
7
1. 依赖库分为静态库与动态库。
2. 静态库是编译时链接到代码中. 完整的 copy 到可执行文件中去的. 多次使用就多次拷贝. 容易产生代码冗余。 动态库是运行时只加载一次. 在程序调用到的地方都可以加载。
3. iOS 中.a 库 与 .framework 库的关系
.a 纯二进制文件
.a +.h+sourcefile = .framework
4. 系统中的.framework 都是动态库. 我们自己创建的都是静态库。
5. .a 是不可直接使用的. 需要配合.h 与其他文件才能使用。framework 可以直接使用。

8. iOS 生命周期以及运行状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
>1. 共有五种状态

```
1>Not running        程序未启动

2>Inactive              前台运行

3>Active                 激活

4>Background        程序在后台. 且能执行代码. 大多数程序在进入该状态之后就直接Suspended了. 不过也有特殊程序一直处于Background状态

5>Suspended        程序在后台不能执行代码
```

> 2. AppDelegate 回调函数

```
//启动完成. 准备开始运行. 一般会在这里加载一些初始化活动
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSLog(@"AppDelegate didFinishLaunchingWithOptions");
return YES;
}
//进入到非活动状态. 比如Home键. 电话来了等
(void)applicationWillResignActive:(UIApplication *)application {
    NSLog(@"AppDelegate applicationWillResignActive");
}
//进入到后台时被调用
(void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"AppDelegate applicationDidEnterBackground");
}
//从后台进入到前台调用
(void)applicationWillEnterForeground:(UIApplication *)application {
    NSLog(@"AppDelegate applicationWillEnterForeground");
}
//应用程序进入到活动状态调用
(void)applicationDidBecomeActive:(UIApplication *)application {
    NSLog(@"AppDelegate applicationDidBecomeActive");
}
//应用程序将要退出. 保存数据和一些退出前的清理工作. 暂未调出. 慎用!
(void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"AppDelegate applicationWillTerminate");
}
```

执行顺序是启动程序. 执行程序. 然后 Home 桌面. 再次启动程序

```
//启动应用程序
2018-03-22 16:20:05.113 Demo1[3404:103041] AppDelegate didFinishLaunchingWithOptions
2018-03-22 16:20:05.144 Demo1[3404:103041] 程序执行
2018-03-22 16:20:05.145 Demo1[3404:103041] 这里有一个log
2018-03-22 16:20:05.147 Demo1[3404:103041] AppDelegate applicationDidBecomeActive

//Home回到桌面
2018-03-22 16:22:46.211 Demo1[3404:103041] AppDelegate applicationWillResignActive
2018-03-22 16:22:46.678 Demo1[3404:103041] AppDelegate applicationDidEnterBackground

//后台重新启动
2018-03-22 16:22:55.540 Demo1[3404:103041] AppDelegate applicationWillEnterForeground
2018-03-22 16:22:56.010 Demo1[3404:103041] AppDelegate applicationDidBecomeActive
```

9. iOS 中 OC 中四种数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1. 基本数据类型 int float double char
2. 构造类型 数组类型. 结构类型. 共用体类型
3. 指针类型 所有的系统类. 自定义类都是指针
4. 空类型 只有一个值 nil. 该类型没有名称. 没有空类型的变量不能转换成空类型. 但是空类型可以转换成任何引用类型。
5. BOOL 类型. YES. NO. 也就是通常意义上的非 0 值为 YES 的意思。注意:BOOL 类型用一个 8 位(一个字节)的整数来表示. 8 位全 0 则为 NO。

6. 接口声明与实现
```
//方法定义
// .h
@interface Foo : NSObject
- (void)doSomething;
@end

// .m
@implementation Foo
- (void)doSomething { }
@end

//符号定义
+:类方法(相当于 Java 的 static 方法). 用 [ClassName method] 调
-:实例方法. 必须先创建对象再调用 [[ClassName alloc] init] 或通过单例拿到实例后调用
```

7. 属性定义
```
    @property (nonatomic, copy) NSString *token;
    @property (nonatomic, strong) id service;
    //常用修饰:strong/weak/copy. 以及 nonatomic(移动端常用)
```

10. 提交包到 Appstore 流程

1
2
3
4
5
6
7
1. 打开最新版本 Xcode10. 目前 Xcode9 也可以提交。
2. 点击左上角 Xcode->open Developer tools->Application Load
3. 输入账号名密码. 选择使用 dis 签名的包提交上传即可。
4. 常见审核不通过原因:
    1. 2.1 苹果怀疑应用有后门开关或者已经有相似应用. 如果首次提交遇到该情况. 请逐条申明回复. 可在网上搜有正面怼的模板。
    2. 4.3 苹果审核怀疑重复应用. 如果是应用首次提交可照着上面做. 否则呵呵祝好运。
    3. 未审核通过的 ipa 包 180 天之后会自动删除。

11. 关于 ipa 逆向

1
2
3
1. 如果忘记了版本号需要查看当前包的版本. 可以解压缩该压缩包得到一个.app 文件. 右键查看内容即可进入该文件中. 找到 info.plist 文件查看。
2. 对于国内平台上架的越狱包. 如果需要修改版本号则无需重新出包. 参照上面再 info.plist 文件修改完版本号之后. 重新在 Payload 文件夹压缩后. 修改后缀为.ipa. 使用签名工具进行重签名即可。注意如果是替换某些文件. 在安装过该包的设备上需要卸载老包且重启才有效果(签名工具自己在 github 中找)
3. 如果 ipa 安装时一直装不上了. 很可能是签名过期了. 或者重签名失败了。

12. 关于 iOS 自动化

    这个比较复杂. 如果经常出包的应用只有一个. 可以考虑使用自动化工具. 比如 fastline 配置好之后. 在手动导入签名文件后. 可以实现一行代码搞定自动化出包。或则配置 Jenkins 框架(android 也可以考虑使用这个框架)。如果是有多个不确定的 iOS 要出包. 请研究下 ruby. openssl. python. shell 等语言一定能有一个不错的解决方式。

1
2
3
4
5
6
7
8
9
```
xcodebuild -list -project XXX.xcodeproj  #列出工程下所有target与Scheme
xcodebuild clean                         #清理
xcodebuild -scheme <Scheme> build        #编译
xcodebuild -target Demo(Scheme) -xcconfig configuration.xcconfig     #根据配置文件来编译
xcodebuild -scheme Demo SYMROOT="/Users/xxx/Desktop/app"          #设置一个本地Debug路径
xcodebuild -scheme Demo DSTROOT="/Users/xxx/Desktop/app" archive  #设置一个本地Release路径
通过xcodebuild -target                   #编译会输出默认的app包. 下面这里可以进行release与debug的切换。
```

13. iOS 混淆

    混淆思路:如果是为了防止逆向开发者破解代码. 可以考虑将所有的关键字以及类方法都混淆成乱序的方式. 注意一旦混淆后就无法回滚. 可以从 github 搜索. 一般研究两到三个. 就可以找到你想要的。

本文由作者按照 CC BY 4.0 进行授权