iOS App 签名的原理 && App 重签名(二)

.IPA 格式

  • .IPA 格式简介

    IPA(iPhone Application)是 iOS 系统中应用程序安装包的格式。.IPA 文件实质上是一个 .zip 格式的压缩包,因为 zip 格式不能记录权限和所有者等信息,所以苹果在 zip 格式的基础上扩展了 IPA 的安装方式(IPA 包即 zip 包,但是在 IPA 格式的 zip 包里面有特定的文件与目录,这些文件与目录用于支持 iOS 系统对应用程序的配置与安装)

    iOS 系统中的 App 类似于 Windows 系统中的绿色软件,不需要安装,解压后即可运行,卸载 App 只需要删除应用程序对应的目录和文件即可

  • .IPA 格式的结构

    在 macOS 下将 .ipa 格式的 App 安装包扩展名改为 .zip 并解压,得到以下 4 个组件:

    1. iTunesArtwork(图片文件)
    2. iTunesMetadata.plist(plist 文件)
    3. META-INFO(目录)
    4. Payload(目录)

    下面以 微信_6.5.14.ipa 为例,逐一解释这 4 个组件的作用:

    ① iTunesArtwork(图片文件):实质是一个无后缀名的高分辨率 App 图标,用来在 iTunes 中显示(可以为其添加 .png / .jpg / .jpeg 的后缀名)

    ② iTunesMetadata.plist(plist 文件):用于存储 App 的属性
    iTunesMetadata.plist
    ③ META-INFO(目录),存储描述数据属性的信息,里面包含两个子文件:
    com.apple.FixedZipMetadata.bin
    com.apple.ZipMetadata.plist
    com.apple.ZipMetadata
    ④ Payload(目录),用来存储 .app 格式的 App 程序包。通过:右键 - 显示包内容,可以查看到包含在 .app 里面的详细内容。.app 里面的内容,大致分为以下几部分:

    1. _CodeSignature(目录)
    2. embedded.mobileprovision(文件)
    3. archived-expanded-entitlements.xcent(文件)
    4. MachO(文件)
    5. Frameworks(目录)
    6. Bundle(文件)
    7. Plugins(目录)
    8. Assets.car(文件)
    9. Info.plist(文件)
    10. PkgInfo(文件)
    11. nib(文件)
    12. lproj(文件)
    13. SC_Info(目录)
    14. 程序文件(html、css、js、txt、xml、json、plist、dat、inf、conf、strings、storyboardc、…)
    15. 图片文件(主要是 AppIcon、LaunchImage)、音频文件、视频文件、字体文件
    16. 子程序目录(比如 iWatch 版本的程序)

    .IPA 格式的目录结构如下图所示:
    .IPA 目录结构图

  • .app 内容简介

  1. _CodeSignature(目录)
    _CodeSignature目录里面有一个 CodeResources 文件,实质上是一个无后缀名的 plist 文件,包含 .app 中所有其他文件的 Hash 值的签名结果
    为 CodeResources 文件添加 .plist 后缀,查看里面的内容:
    其中,files 和 files2 字典中,key 是文件名,value 是文件的 Hash 值的签名结果
    如果 key 所表示的文件是可选的,那么 value 本身也是一个字典。此时 value 字典有两个 key:hash(文件 Hash 值的签名结果)、optional(bool 型的可选项)
    CodeResource

  2. embedded.mobileprovision(文件)
    应用程序的供应配置证书 / 应用程序的描述文件 / PP 文件
    官方的 .app 包里面不存在此文件
    关于供应配置证书的相关介绍,请参考 iOS App 签名的原理 && App 重签名(一)

  3. archived-expanded-entitlements.xcent(文件)
    授权文件,决定了哪些系统资源在什么情况下允许被 App 使用。简单地说,授权文件实质上是一个沙盒的配置列表
    授权文件是由 Provisioning Profile 中 Entitlements 键对应的值(Dict)组成的 Plist 文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>application-identifier</key>
    	<string>532LCLCWL8.com.tencent.xin</string>
    	<key>aps-environment</key>
    	<string>production</string>
    	<key>com.apple.developer.associated-domains</key>
    	<array>
    		<string>applinks:help.wechat.com</string>
    	</array>
    	<key>com.apple.developer.healthkit</key>
    	<true/>
    	<key>com.apple.developer.networking.HotspotHelper</key>
    	<true/>
    	<key>com.apple.developer.networking.networkextension</key>
    	<array>
    		<string>packet-tunnel-provider</string>
    		<string>app-proxy-provider</string>
    		<string>content-filter-provider</string>
    	</array>
    	<key>com.apple.security.application-groups</key>
    	<array>
    		<string>group.com.tencent.xin</string>
    	</array>
    </dict>
    </plist>
    
  4. MachO(文件)
    Mach-O(Mach Object)是 macOS、iOS 上可执行文件的格式标准,可扩展出不同的文件后缀:
    .a / .dylib / .framework / 可执行文件 / .dsym / …
    可以使用 MachOView 查看 MachO 文件里面的内容(黑色的 exec 就是代表有可执行权限)
    MachO 00
    MachO文件中的代码签名
    关于 MachO 的详细内容,将在后面的章节中介绍

  5. Frameworks(目录)
    用于存放用户级别的 Framework(Static Framework / Embedded Framework)
    iOS 默认内置了系统级别的 Framework(Dynamic Framework),在项目中使用系统自带的 Framework 时,实际上是保存对系统级别的 Framework 的引用。因此,在项目打包时,系统级别的 Framework(Dynamic Framework)不需要拷贝到 .app 中
    因为每个 Framework 其实就是一个 App,所以 .framework 的结构类似于 .app 的基础结构,有:
    ① 存放签名信息的 _CodeSignature 目录
    ② Info.plist 文件
    ③ 与 Framework 同名的可执行文件
    ④ SC_Info 目录
    Framework 内部结构
    因为 Frameworks 目录下的每一个 Framework 都有自己独立的签名信息
    所以在进行 iOS App 重签名时,需要对 Frameworks 目录下的每一个 Framework 都进行强制重签名

  6. Bundle(文件)
    Bundle是一个含有编译过后可执行的二进制程序以及执行程序所需的资源,以特定标准的层次结构组合起来的文件夹。iOS 将以下类型的文件识别为 Bundle:.app / .bundle / .framework / .plugin / .kext / … ,iOS 中的 Bundle 分为下面 3 种类型:
    ① Application Bundle
    ② Framework Bundle
    ③ Plugins Bundle

  7. Plugins(目录)
    用于保存插件,无法对此目录进行重签名,因此 App 重签名时需要删除此目录(或者删除此目录下的所有文件)

  8. Assets.car
    项目中所有 .xcassets 打包后生成的一个压缩包(注意:.xcassets 中的 AppIcon 和 LaunchImage 不会参与 Assets.car 的打包,而是直接放在 .app 包中 )
    可以通过 github 上的开源工具 cartool 解压查看 Assets.car 里面的内容

  9. Info.plist(文件)
    为了提供更好的用户体验,iOS 和 macOS 的每个 App 或 Bundle 都依赖于特殊的元信息(Meta Information)。元信息有多种用途,包括:
    ① 用于直接向用户展示信息
    ② 用于在系统内部标识 App 或 App支持的文档类型
    ③ 用于辅助系统对 App 的加载
    App 通过一种特殊的属性列表文件,向系统提供自己的元信息
    这个属性列表文件可用来构建任意数据,这些数据在运行时是可访问的
    每个属性列表文件都包含了一个 Bundle 的配置信息。文件中的 keys 和 values 描述了许多需要应用于该 Bundle 的行为以及配置选项
    XCode 通常会为每个基于 Bundle 的工程自动创建该属性列表文件,并且提供许多合适的 keys 以及其对应的默认的 values 。之后可以编辑该文件,添加任何工程所需的 keys 和 values,或修改现有的 keys 所对应的 values

    每个 App 都使用 Info.plist 文件来存储以上的元信息(从名称上也可以判断出 Info.plist 就是以上提到的 属性列表文件)。iOS 使用 Info.plist 文件来决定:Bundle 所显示的 icon、当前 App 支持打开的文档类型、… 、等等其它的信息。正如以上所提到的,Info.plist 文件是一种结构化的文本文件,它包含了 App 的重要配置信息。Info.plist 文件多数情况下是以 UTF-8 进行编码,并且其内部的配置内容是以 XML 格式进行组织。XML 的根节点是一个字典(Dictionary),包含描述 Bundle 各个方面的 keys 和 values 。iOS 系统读取该文件,并获取 App 的配置信息

    按照约定,属性列表文件的名称就是 Info.plist(该文件名称是大小写敏感的)

  10. PkgInfo(文件)
    存储包信息的文件(包的 8 字节标识符,APPL = Application)

    APPL????
    
  11. nib(文件)
    iOS 和 macOS 工程中的 Xib 文件以及 Storyboard 文件最终会被 XCode 打包系统处理生成相应的 nib 文件并放置到 ipa 包中。nib 文件是二进制的 plist 文件(即 BPlist),其中保存了 App 中 GUI 组件的位置信息和设置信息
    可以通过 XCode 的 Interface Builder 组件,编辑 Xib 、Storyboard 的文本版本,XCode 在打包时,会将 Xib 、Storyboard 编译成二进制格式

  12. lproj(文件)
    Localized Project Folder,包含用于支持 iOS / macOS 中 App 的单种语言的文本文件和其他资源文件(可能包含 .strings 和 .nib 文件)

  13. SC_Info(目录)
    包含 appname.sinf 和 appname.supp 两个文件:
    ① .sinf 为 metadata 文件
    ② .supp 为解密可执行文件的密钥

  • .app 程序包中的两处签名

    众所周知,XCode 在生成 IPA 包时,会使用 CodeSign 工具对 IPA 包进行应用签名
    更具体的,CodeSign 是对 IPA 包中的 AppName.app 程序包进行应用签名
    IPA 包中的其他内容: iTunesArtwork、iTunesMetadata.plist、META-INF(目录以及子文件)、Payload(目录)是没有参与应用签名的

    CodeSign 对 AppName.app 程序包的应用签名分为以下 2 部分:

    1. 代码签名:对 App 主程序文件(可执代码)的签名,直接放在 App 主程序文件中(与 App 同名的 MachO 文件)
    2. 资源签名:对除 App 主程序外的所有资源文件的签名,都放在 _CodeSignature(目录)下的 CodeResources(plist) 文件中
  • iOS App 目录结构简介

    iOS 为了保证系统安全,在安装 IPA 包的时候会为 App 创建属于自己的沙盒目录
    沙盒(Sandbox),也叫沙箱,其原理是通过重定向技术,把 App 对文件的 增、删、改、查 等操作定向到自身文件夹中。在沙盒机制下:

    1. 每个 App 只能在 iOS 系统为其创建的沙盒(文件夹)中访问文件,不可以访问其他 App 沙盒(文件夹)中的内容
    2. 应用程序如果要跨越沙盒(文件夹)向外部读取或者写入数据,需要经过 iOS 系统的权限验证

    由上面的沙盒原理可知,沙盒目录是一个逻辑目录,所以以下讨论的是沙盒的逻辑结构

    iOS 在安装 IPA 包的时候,会在沙盒目录下创建容器目录,每个容器目录都有特定的功能:

    1. Bundle Container,用于存储 App 程序包(所有可执行文件+ 所有资源文件)
    2. Data Container,用于存储 App 运行过程中产生的所有数据
    3. iCloud Container,额外的容器目录
    4. … …
      沙盒目录逻辑结构

    ① Bundle Container:用于存储 App 程序包(所有可执行文件 + 所有资源文件),因为 App 程序包有经过签名处理,所以在 App 运行时不能修改 Bundle Container 里面的内容,否则 iOS 系统验证签名的时候不能通过,App 无法正常启动
    iTunes / iCloud 不会同步该目录下的所有内容

    ② Data Container:用于存储 App 运行过程中产生的所有数据,有 3 个子目录:

    1. Documents 目录:该目录主要用于保存与用户相关的数据。因为目录下的文件可以通过文件共享提供给用户,所以该目录下最好只存储 App 希望公开给用户的数据信息
      Documents 目录下,可能会有 Inbox 子目录,使用 Documents/Inbox 目录读取外部实体要求应用程序打开的文件(比如外部的邮件或者文档),App 只能读取或者删除 Documents/Inbox 目录下的文件,不能修改
      注意:iTunes / iCloud 会同步 Documents 目录下的所有内容
    2. Library 目录:该目录主要用于保存与 App 相关的数据。该目录下默认有两个子目录
      Library/Caches:用于存储 App 的缓存数据,在 iOS 系统存储空间紧张的时候,该目录下的数据可能会被 iOS 系统自动清除
      Library/Preferences:用于存储 App 的偏好设置,使用 NSUserDefaults 类访问此目录进行 App 偏好设置的存取,iOS 的 Settings 会在该目录中查找应用的设置信息
      注意: iTunes / iCloud 会同步 Library 目录下,除 Caches 子目录外的所有内容
    3. tmp 目录:该目录主要用于保存临时的数据,保存在该目录下的数据,在 App 没有运行时,可能会被 iOS 系统自动清除
      注意:iTunes / iCloud 不会同步 tmp 目录下的所有内容

    沙盒目录逻辑结构图:
    沙盒目录逻辑结构图
    使用 Objective-C 获取 iOS 沙盒目录及其子目录的物理路径:

    // 获取 Bundle Container 的物理路径
    NSString* bundleContainerPath = [[NSBundle mainBundle] bundlePath];
    NSLog(@"bundleContainerPath = \n%@\n", bundleContainerPath);
    
    // 获取 Data Container 的物理路径
    NSString* dataContainerPath = NSHomeDirectory();
    NSLog(@"dataContainerPath = \n%@\n", dataContainerPath);
    
    // 获取 Documents 的物理路径
    NSString* documentsDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSLog(@"documentsDirPath = \n%@\n", documentsDirPath);
    
    // 获取 Library 的物理路径
    NSString* libraryDirpath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSLog(@"libraryDirpath = \n%@\n", libraryDirPath);
    
    // 获取 Library/Caches 的物理路径
    NSString* cachesDirPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSLog(@"cachesDirPath = \n%@\n", cachesDirPath);
    
    // 获取 Library/Preferences 的物理路径
    // 此方法获取的沙盒路径是 SPreferencePanes ,但是并不存在这样的目录
    NSString* preferencePanesDirPath = [NSSearchPathForDirectoriesInDomains(NSPreferencePanesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSLog(@"preferencePanesDirPath = \n%@\n", preferencePanesDirPath);
    // 因为苹果官方希望开发者通过 NSUserDefaults 在 Library/Preferences 中存取 App 的偏好设置
    // 所以如果要访问 Library/Preferences ,只能通过路径拼接
    NSString* preferencesDirPath = [libraryDirpath stringByAppendingPathComponent:@"Preferences"];
    NSLog(@"preferencesDirPath = \n%@\n", preferencesDirPath);
    
    // 获取 tmp 的物理路径
    NSString* tmpDirPath = NSTemporaryDirectory();
    NSLog(@"tmpDirPath = \n%@\n", tmpDirPath);
    
    /* 输出结果:
    bundleContainerPath =
    /private/var/containers/Bundle/Application/3642C11D-9C31-496B-BB43-EA67D719F785/
       
    dataContainerPath =
    /var/mobile/Containers/Data/Application/AACEDEAB-B25F-4266-9C0A-1A8B18158E8E
       
    documentsDirPath =
    /var/mobile/Containers/Data/Application/AACEDEAB-B25F-4266-9C0A-1A8B18158E8E/Documents
       
    libraryDirPath =
    /var/mobile/Containers/Data/Application/AACEDEAB-B25F-4266-9C0A-1A8B18158E8E/Library
       
    cachesDirPath =
    /var/mobile/Containers/Data/Application/AACEDEAB-B25F-4266-9C0A-1A8B18158E8E/Library/Caches
       
    preferencePanesDirPath =
    /var/mobile/Containers/Data/Application/AACEDEAB-B25F-4266-9C0A-1A8B18158E8E/Library/PreferencePanes
       
    preferencesDirPath =
    /var/mobile/Containers/Data/Application/AACEDEAB-B25F-4266-9C0A-1A8B18158E8E/Library/Preferences
       
    tmpDirPath =
    /private/var/mobile/Containers/Data/Application/AACEDEAB-B25F-4266-9C0A-1A8B18158E8E/tmp/
    */
    
    // 在沙盒目录下,有些文件夹中的内容默认会被 iTunes / iCloud 同步
    // 当这些文件夹中的某些文件不需要被 iTunes / iCloud 同步时
    // 可通过以下代码设置不需要被同步的文件
    NSString* filePath = @"不想被 iTunes / iCloud 同步的文件的路径";
    NSURL* fileUrl = [[NSURL alloc] initFileURLWithPath:filePath];
    [fileUrl setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    

    由以上 iOS 沙盒目录及其子目录物理路径的输出结果可以看出,iOS 系统将沙盒目录及其子目录分 3 个物理路径存储:

    1. Bundle Container 存储在物理路径:
      /private/var/containers/Bundle/Application/AppHash0/
    2. Data Container 中的 Documents、Library(包括:Caches + Preference)存储在物理路径:
      /var/mobile/Containers/Data/Application/AppHash1/
    3. Data Container 中的 Tmp 存储在物理路径:
      /private/var/mobile/Containers/Data/Application/AppHash1/

    从而,iOS 系统通过重定向技术实现的沙盒目录是一个逻辑目录

  • 获取 IPA 包的三种方式

    ① 使用 爱思助手 下载(获取的是第三方的脱壳包)

    ② 使用 Apple Configurator 2 下载(获取的是官方的加壳包)

    ③ 使用 iTool 或 爱思助手 到越狱手机中将 IPA 包备份(拷贝)出来
    (如果 App 是从 AppStore 上面下载安装的,则为官方的加壳包;如果 App 是从第三方助手上面下载安装的,则为第三方脱壳包)

    附注:
    目前(2020.08.08)从爱思助手上获取的 IPA 包,也是官方的加壳包
    如需获取脱壳后的 IPA 包,可以使用砸壳工具对官方的 IPA 包进行砸壳
    关于砸壳工具的使用以及 IPA 包的脱壳流程,将在后面的章节讲解

    iOS 系统中有 3 种格式的 App 安装包:
    ① .ipa
    ② .deb
    ③ .pxl
    

其他概念(通用二进制文件、macOS 文件权限、Shell 脚本)

  • 通用二进制文件

    思考一个问题:
    不同的 iOS 设备,可能具有不同的 CPU 架构(armv6、armv7、armv7s、arm64)
    那么,同一个 IPA 包为什么可以运行在具有不同 CPU 架构的所有 iOS 设备上呢?
    因为,通过 XCode 存档(Archive)出来的 IPA 包中,包含了不同 CPU 架构的二进制代码

    可以在 XCode - Target - Build Setttings - Architectures 中,设置项目所支持的 CPU 架构
    Build Settings - Architectures
    各个选项的含义如下:

    1. Architectures 选项,指示项目将被编译成支持哪些 CPU 指令集的二进制代码。Standard architectures 表示标准架构,里面包含 armv7 和 arm64
    2. Valid Architectures 选项:指示项目可支持的 CPU 指令集。Architectures 选项 和 Valid Architectures 选项的交集,将是 XCode 最终生成的 IPA 包所支持的指令集
    3. Build Active Architecture Only 选项:指示是否只编译出当前真机调试的设备所对应的指令集,该选项 Debug 模式下默认为 YES,Release 模式下默认为 NO
      开发调试时,为了加快编译速度,一般只需编译出调试设备的 CPU 型号所对应的二进制代码即可
      测试发布时,为了涵盖大部分机型,一般需要编译出所有主流机型的 CPU 型号所对应的二进制代码

    接着再思考一个问题:
    由上面的介绍可知,IPA 包中含有不同 CPU 架构的二进制代码,那么这些不同 CPU 架构的二进制代码存放在 IPA 包中的哪里呢?
    这些不同 CPU 架构的二进制代码存放在 IPA 包中的 MachO 文件中

    通过 MachOView 查看 WeChat 的 MachO 文件:
    WeChat MachO

    通用二进制文件(Universal Binary)也叫胖二进制文件(Fat Binary):是由苹果公司提出的 能同时适用多种 CPU 架构的二进制文件,具有以下特点:

    1. 使用 通用二进制文件 的同一个程序包能同时为多种 CPU 架构提供最理想的性能
    2. 因为 通用二进制文件 需要储存多种 CPU 架构的二进制代码,所以 通用二进制应用程序 通常比 单一平台二进制应用程序 要大
    3. 因为两种 CPU 架构的二进制代码有共同的非可执行资源,所以 通用二进制应用程序 的大小并不会达到 单一平台二进制应用程序 的两倍之多
    4. 因为一个 iOS 设备只会有一种 CPU 架构,所以 通用二进制应用程序 在执行时只会调用一种 CPU 架构的代码。因此,通用二进制应用程序 运行起来不会比 单一平台二进制应用程 序耗费额外的内存

    可以通过以下命令行,查看 MachO 文件支持哪些 CPU 指令集

    ~ > cd '/Users/Airths/Desktop/WeChat IPA/微信 7.0.15/Payload/WeChat.app'
    ~/Desktop/WeChat IPA/微信 7.0.15/Payload/WeChat.app > file WeChat
    WeChat (for architecture armv7): 	Mach-O executable arm_v7
    WeChat (for architecture arm64): 	Mach-O 64-bit executable arm64
    

    同一个系列的 CPU 的指令集,一般是向下兼容的,比如:
    armv7 和 armv7s 是同一个系列的 CPU,armv7 的指令集可以运行在 armv7s 上
    虽然,通用二进制文件(Universal Binary)的同一个程序包能同时为多种 CPU 架构提供最理想的性能
    但是,armv7 指令集运行在 armv7s 上时,其性能损失非常小
    所以,App 在 Archive 的时候,综合性能与体积的考量,一般只会打包出包含 armv7 和 arm64 架构的通用二进制文件

    armv7s 是一个过渡型号的 CPU,只用在了少量机型上:
    iPhone5|iPhone5c|iPad4(iPad with Retina Display)

  • XCode 快捷键:Command + B 与 Command + Shift + K

    在 XCode 中,快捷键 Command + B 调用的是 Product - Build 的功能
    在 XCode 中,快捷键 Command + Shift + K 调用的是 Product - Clean Build Folder 的功能

    项目 Command + B 之后,XCode 将在
    ~/Library/Developer/Xcode/DerivedData/项目标识符/Build
    目录下生成以下两个子目录:
    ① Intermediates.noindex 目录(用于存放中间数据)
    ② Products 目录(用于存放 真机 / 模拟器 的 .app 包),有两个子目录:

    1. Debug-iphoneos 目录,用于存放针对真机环境编译出来的 .app 包
    2. Debug-iphonesimulator 目录,用于存放针对模拟器环境编译出来的 .app 包

    需要注意的是:只有用于真机的 .app 文件里面才包含 embedded.mobileprovision

    项目 Command + Shift + K 之后,XCode 将会清空并删除
    ~/Library/Developer/Xcode/DerivedData/项目标识符/Build
    目录下的两个子目录:
    ① Intermediates.noindex 目录
    ② Products 目录

    注意:
    Command + Shift + K 为 Clean Build Folder(清空编译文件夹)
    Command + K 为 Clear Console(清空调试输出)

  • macOS 文件权限

    可以通过以下命令查看 macOS 中,文件的详细信息:

    ~/Desktop/Training/Demo/Demo >> master > ls -l
    total 64
    -rw-r--r--@ 1 Airths  staff   238  7 31 15:53 AppDelegate.h
    -rw-r--r--@ 1 Airths  staff  1375  7 31 15:53 AppDelegate.m
    drwxr-xr-x  4 Airths  staff   128  7 31 15:53 Assets.xcassets
    drwxr-xr-x  4 Airths  staff   128  7 31 15:53 Base.lproj
    -rw-r--r--  1 Airths  staff  1982  7 31 15:53 Info.plist
    -rw-r--r--@ 1 Airths  staff   291  7 31 15:53 SceneDelegate.h
    -rw-r--r--@ 1 Airths  staff  2194  7 31 15:53 SceneDelegate.m
    -rw-r--r--@ 1 Airths  staff   225  7 31 15:53 ViewController.h
    -rw-r--r--@ 1 Airths  staff   354  7 31 15:53 ViewController.m
    -rw-r--r--@ 1 Airths  staff   502  7 31 15:53 main.m
    
    ## 从左到右:
    ## 第 0 列:文件的类型与权限
    ## 第 1 列:文件的连接数
    ## 第 2 列:文件的所有者
    ## 第 3 列:文件的所属组
    ## 第 4 列:文件的大小(Byte)
    ## 第 5 、6、7 列:文件的最后修改时间(月 日 时:分)
    ## 第 8 列:文件名
    

    macOS 中,常见的文件类型有:
    d:目录(directory)
    - :文件(file)

    macOS 的文件权限分为:
    r:read(可读),使用数字类型表示时,对应数字 4(0000 0100)
    w:write(可写),使用数字类型表示时,对应数字 2(0000 0010)
    x:execute(可执行),使用数字类型表示时,对应数字 1(0000 0001)
    可以看出 macOS 的文件权限是位移枚举类型,可以通过 & 运算叠加文件权限

    macOS 是多任务、多用户的操作系统,对资源使用者的身份分为:
    u:user(资源所有者)
    g:group(与资源所有者处于同一组的用户)
    o:other(与资源所有者处于不同组的用户)
    a:all(所有用户)

    下面以 Info.plist 文件的详细信息为例,说明文件的类型与权限:

    -|rw-|r--|r--  1 Airths  staff  1982  7 31 15:53 Info.plist
    ## 	macOS 中,表示文件类型与权限的字符标识共 10 位:
    ## 第 0 位,代表文件类型,- 代表文件的类型为 file(文件)
    ## 第 1 ~ 9 位,代表文件权限,公分三组:
    ## 		第 1 ~ 3 位为第一组,代表文件所有者(user)的权限(可读、可写、不可执行)
    ##		第 4 ~ 6 位为第二组,代表与文件所有者同组的其他用户(group)的权限(可读、不可写、不可执行)
    ##		第 7 ~ 9 位为第三组,代表与文件所有者不同组的其他用户(other)的权限(可读、不可写、不可执行)
    ## 每组的三个权限的顺序依次是 rwx,这三个权限的位置是固定不变的
    ## 出现 - 的位置,代表没有此项权限
    

    可以通过 chmod 命令更改文件权限:

    ## 通过 chmod 命令更改文件权限的方式有两种:
    ## 		1.通过数字类型更改文件的权限
    ## 		2.通过符号类型更改文件的权限
    ## 文件的权限分为三种身份 user、group、other
    ## 文件的权限分为三种类型 read(4)、write(2)、execute(1)
    
    ## 通过数字类型给 test.sh 设置 rwxr-xr-x 的权限
    ## user:4 + 2 + 1 = 7
    ## group:4 + 0 +1 = 5
    ## other:4 + 0 + 1 = 5
    chmod 755 test.sh
    
    ## 通过字符类型给 test.sh 的所有者(user)设置可读可写可执行(rwx)的权限
    ## +:添加权限
    ## -:删除权限
    ## =:设置权限
    chmod u+rwx test.sh
    
  • Shell 脚本

    Shell 是一种命令解释器,它在操作系统的最外层,负责直接与用户对话,把用户输入的命令解释给操作系统,并处理操作系统各种各样的输出结果,然后输出返回给用户。用户与 Shell 的对话方式,可以是交互式(使用 Shell 命令行界面),也可以是非交互式(使用 Shell 脚本程序)。下图中橙色部分就是解释器 Shell 在操作系统中的位置:
    Shell
    提示:
    Shell 的英文是外壳的意思,从上图中可以看出,命令解释器 Shell 就像一个外壳一样包住了操作系统的核心

    可以通过以下命令,查看 macOS 中安装的 Shell:

    cd /private/etc
    cat shells
    # List of acceptable shells for chpass(1).
    # Ftpd will not allow users to connect who are not using
    # one of these shells.
    
    /bin/bash
    /bin/csh
    /bin/dash
    /bin/ksh
    /bin/sh
    /bin/tcsh
    /bin/zsh
    
    # bash (Bourne Again Shell) :由 GNU 开发,保持了对 sh 的兼容性,是各种 Linux 发行版默认配置的 Shell
    # csh (C Shell) :由  Bill Joy 开发,语法有点类似 C 语言
    # dash (Debian Almquist Shell) :由 NetBSD 操作系统的 Almquist Shell(ash) 发展而来
    # ksh (Korn Shell) :由 David Korn 开发,基于 Bourne Shell 的源代码发展而来
    # sh (Bourne Shell) :由 Steve Bourne 开发,是第一个流行的 Shell
    # tcsh (TEXES/TOPS C Shell) :tcsh 是 csh 的增强版,并且完全兼容csh。它不但具有 csh 的全部功能,还具有命令行编辑、拼写校正、可编程字符集、历史纪录、作业控制等功能,以及 C 语言风格的语法结构
    # zsh (Z Shell) :由 Paul Falstad 编写,具有强大的可定制性,可通过 oh-my-zsh 配置,有终极 Shell 之称
    

    可以通过以下命令,切换 macOS 中默认的 Shell:

    # 注意:
    # 1.需要重启 Shell 的图形操作界面,切换 Shell 的指令才会生效
    # 2.Bash 是 Linux 和 macOS 默认的 Shell(所以 Shell 脚本入门一般都是先学 Bash)
    # 3.Shell 启动时,会先执行配置文件里面的内容:
    # 		3.1 Bash 的配置文件是 home/.bash_profile
    #		3.2 zsh 的配置文件是 home/.zshrc
    # 4.在使用 zsh 时,如果希望之前 Bash 中的配置依然生效
    #	可以在 zsh 的配置文件 home/.zshrc 中加入 :
    #	source ~/.bash_profile
    ~ > chsh -s /bin/bash
    Changing shell for Airths.
    Password for Airths:
     
    ~ > chsh -s /bin/zsh
    Changing shell for Airths.
    Password for Airths:
    

    Shell 的本质是命令解释器,每种 Shell 都有自己的语法格式,同一系列的 Shell 语法相近,不同系列的 Shell 语法不同,比如:
    sh、bash、dash、ksh 语法相近,csh、tcsh 语法相近
    bash 与 tcsh 的语法则有很大的不同
    一般开发较晚的 Shell 会继承之前 Shell 的优点,并发挥自己的特色

    上面介绍完了 Shell 的概念,接着讲一讲 Shell 脚本

    Shell 脚本(Shell Script / Shell 程序):由 Shell 命令、变量、流程控制语句和注释等有机地组合在一起,形成的实现特定功能的文本类型的文件。Shell 脚本类似于 DOS 系统下的批处理脚本(*.bat)

    Shell 脚本很擅长处理纯文本类型的数据,而 Linux / Unix 中几乎所有的配置文件、多数启动文件都是纯文本类型的文件。因此 Shell 脚本可以在系统运维中发挥巨大作用

    以下是一个清除 /var/log 下 messages 日志文件的 Shell 脚本示例:

    #!/bin/bash
    # Date:		20:00 2020-08-01
    # Mail:		xxxxx@xxx
    # Author:	Created by Airths
    # Function:	This scripts function is clean system log messages
    # Version:	1.0
    # Return:	0 for success ,1 for failure
    
    LOG_DIR=/var/log
    # 清除系统日志需要 root 权限,$UID为 0 时,用户才具有 root 用户的权限
    ROOT_UID=0
    if [ "$UID" -ne "$ROOT_UID" ]
    	then
    	echo "Mast be root to run this script."
    	exit 1
    fi
    
    cd $LOG_DIR || {
    	echo "Cannot change to necessary directory." >&2
    	exit 1 
    	}
    	
    cat /dev/null > messages && echo "Logs cleaned up."
    
    exit 0
    

    Shell 脚本的编写规范如下所示:

    1. 脚本第一行指定脚本使用的解释器(比如:#!/bin/sh 或 #!/bin/bash)
      字符 #! 被称为幻数,在执行 Shell 脚本的时候,系统内核会根据 #! 后的路径来确定该用哪个程序解释脚本的内容
      注意,这一行必须在每个脚本顶端的第一行,如果不是第一行则为脚本注释行
      如果不显式指定 Shell 脚本的解释器,则系统会使用默认的 Shell 对脚本进行解释
    2. 脚本中 # 表示注释,脚本中尽量使用英文注释,防止在切换系统环境时产生乱码
    3. 在脚本的开头加上:日期、作者、脚本功能介绍、脚本版本、脚本返回值等信息
    4. 脚本以.sh 为扩展名。虽然 Shell 脚本不检查脚本的扩展名(可以使用 .txt / .c / .m / .php 等等),但是 Shell 脚本的扩展名应该做到见名知意
    5. 脚本中应该保持代码缩进等良好的编程习惯

    当编写完 Shell 脚本之后,需要运行 Shell 脚本以调用开发完成的功能
    当 Shell 脚本运行时,解释器会先查找系统的环境变量 ENV,该环境变量指示了 Shell 环境文件的位置,解释器会从环境文件处开始执行,当读取完环境文件后,解释器会开始执行 Shell 脚本中的内容

    执行一个 Shell 脚本有以下 4 种方法:

    cd Shell_Script
    vim test.sh
    cat test.sh
    echo "hello world!"
    
    ----------------------------------------------------------------------------
    ## 格式 1,使用:shellName scriptName
    ## 重新建立一个子 Shell,并在子 Shell 中执行脚本
    ## 当脚本文件本身没有可执行权限或者文件开头没有指定解释器时,常用此方法
    ~/Desktop/Shell_Script > bash test.sh
    hello world!
    
    ~/Desktop/Shell_Script > sh test.sh
    hello world!
    
    ~/Desktop/Shell_Script > zsh test.sh
    hello world!
    
    ----------------------------------------------------------------------------
    ## 格式 2,使用:absolutePath/scriptName 或者 ./scriptName 
    ## 有一个前提:需要 脚本文件具有可执行权限
    ## 注意:
    ## 如果直接使用脚本名,则解释器会在系统的环境变量 PATH 中寻找目标脚本
    ## 而目录 Shell_Script 并没有在系统环境变量 PATH 中
    ## 因此,直接使用脚本名,解释器会提示找不到目标脚本
    ## ./scriptName 的含义是:告诉解释器,在当前目录下寻找目标脚本
    ~/Desktop/Shell_Script > /Users/Airths/Desktop/Shell_Script/test.sh
    zsh: permission denied: /Users/Airths/Desktop/Shell_Script/test.sh
    
    ~/Desktop/Shell_Script > chmod u+x test.sh
    
    ~/Desktop/Shell_Script > /Users/Airths/Desktop/Shell_Script/test.sh
    hello world!
    
    ~/Desktop/Shell_Script > ./test.sh
    hello world!
    
    ~/Desktop/Shell_Script > test.sh
    zsh: command not found: test.sh
    
    ----------------------------------------------------------------------------
    ## 格式 3,使用:source scriptName 或者 .scriptName 
    ## 在当前 Shell 环境中读取并执行脚本中的命令
    ## 此种方式可以强行让脚本中的命令立即影响当前的环境(一般用于加载配置文件)
    ## 此种方式会忽略脚本的文件权限,强制执行脚本
    ~/Desktop/Shell_Script > source test.sh
    hello world!
    
    ~/Desktop/Shell_Script > .test.sh
    hello world!
    
    ----------------------------------------------------------------------------
    ## 格式 4,使用管道输出,这种方式一般用得比较少
    ~/Desktop/Shell_Script > bash<test.sh
    hello world!
    
    ~/Desktop/Shell_Script > sh<test.sh
    hello world!
    
    ~/Desktop/Shell_Script > cat test.sh|zsh
    hello world!
    

注意

  • 关于 iOS 系统对 IPA 包进行签名校验的细节

    因为 XCode 在打包过程中会对工程内的每个文件都做签名,所以打包(或者重签名)后的程序包(.app)内的文件是不允许再修改的

    因为 iOS 系统在对 IPA 包进行签名校验的时候,只扫描文件,不扫描文件夹(目录),所以可以在打包(或者重签名)后的程序包(.app)内存放空文件夹,这样 iOS 系统在对 IPA 包进行签名校验的时候,就会认为没有变化

    因为 iOS 系统在对 IPA 包进行签名校验的时候,不会扫描 _CodeSignature 文件夹里面的内容,所以可以在打包(或者重签名)后的程序包(.app)内的 _CodeSignature 文件夹中添加其他文件,从而实现在打包(或者重签名)后的程序包(.app)的内动态地添加 App 相关的配置。需要注意的是:存放在 _CodeSignature 文件夹中的所有内容,包括我们自己在打包(或者重签名)后添加的其他文件,都是是只读的,不允许通过程序往 _CodeSignature 文件夹里面写入数据

  • Shell 补充

  1. 可以通过以下命令,快速打开 Shell 当前所在的路径:
    快速打开当前 Shell 所在路径

  2. 关于 iTerm2.app / terminal.app 与 Bash / zsh 的关系:

    iTerm2.app 和 terminal.app 是 Shell 的图形操作界面,用于将用户的输入传递给 Shell,并将 Shell 的返回结果显示给用户,可以对接各种 Shell

    Bash 和 zsh 是 Shell 的实体,用于接收命令,解释命令,执行命令,反回结果

    iTerm2.app / terminal.app 与 Bash / zsh 的关系,有点类似于,数据库可视化操作界面 与 数据库 的关系

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页