2024年4月

iOS项目环境搭建和依赖管理

一个项目总是会依赖一些库, 有些是第三方的, 有些可能是项目自身为了复用拆出去的.
现有主流的iOS依赖有多种形式, 比如CocoaPods, Carthage和swift package.

本文是一些环境搭建和使用项目依赖相关的笔记.

全局环境准备

基本上iOS开发都要准备的环境, 这些设置是全局的, 在每个机器上设置一次就行. (本文为mac环境).

homebrew: (已有可跳过)

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

rbenv

rbenv是一个管理ruby版本的工具, 如果同一个机器上有多个代码库, 可以用它来灵活切换ruby在不同项目中的版本.

具体可以看一下项目介绍:
https://github.com/rbenv/rbenv

setup ruby (这里选一个全局的版本号, 可根据具体情况更改):

brew install rbenv ruby-build

rbenv install 3.1.3

echo "export PATH=\"/opt/homebrew/bin/rbenv:\$PATH\"" >> ~/.zshrc
echo "eval \"\$(/opt/homebrew/bin/rbenv init -)\"" >> ~/.zshrc

source ~/.zshrc
rbenv global 3.1.3

验证安装成功:

ruby -v 
//得到刚刚指定的版本
which ruby 
//得到目录: $HOME/.rbenv/shims/ruby

Bundler和CocoaPods

Bundler(
https://bundler.io/
)和CocoaPods(
https://cocoapods.org/
)都是dependency manager.

它们管理的依赖种类不同.

Bundler用来下Ruby gems, 即
Gemfile
中的依赖.

CocoaPods用来下pod, 即
Podfile
中写的pods依赖.

但cocoapods本身是一个ruby gem, 所以它被bundler管理.

(可以想象bundler是大经理, cocoapods是小领导, 此处可以画一个树形图表示, 如果我记得的话.) (但是我突然决定不画了, 因为懒.)

iOS世界中还有一个著名的ruby gem是fastlane, 本文并不涉及就不展开讲了.

我们先把这两个工具安装到电脑上:

gem install bundler
gem install cocoapods
pod setup

Optional

你可能还需要的格式化工具:

brew install swiftformat

已有项目的配置

我们拿到一个新项目后往往需要拉它的依赖.

如果你的项目中有
Gemfile
文件

bundle install

之后发现还有Podfile文件:

bundle exec pod install

这些命令是每个项目都需要执行的, 当项目依赖变更时需要重新执行这些:

bundle install
bundle exec pod install

M1电脑可能需要配置:

bundle config build.ffi --enable-system-libffi
bundle config set --local path 'vendor/bundle'
// 这个命令运行完之后当前目录下会多出一个`.bundle/`文件夹.

命令行中的工作就基本做完了, 剩下打开xcode, 如果还有swift package xcode会帮你解析的.

CocoaPod

这里以CocoaPod为例讲一下如何添加使用一个CocoaPod库, 以及相关的文件.

安装cocoapods这一步我们在之前setup环境的时候已经跑过了:

gem install cocoapods
pod setup

每一个机器只要跑一次就行, 可以跳过.

CocoaPods有一个公开的specifications repo:

https://github.com/CocoaPods/Specs

这是一个中心化的代码仓库, 比较流行的库都放在上面.

官网是:
https://cocoapods.org/

在这里可以搜索任何你想要的cocoapods库.

使用一个公开的库

让我们拿一个比较流行的库试试:
https://github.com/Alamofire/Alamofire

首先在项目的根目录run:

pod init

会创建一个
Podfile
. (如果项目中已经存在了会报错.)

这个文件名是固定不变的.

用xcode打开这个文件:

open -a Xcode Podfile

里面的内容是这样: (这里
UsePublicCocoaPods
是我的项目名)

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'UsePublicCocoaPods' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for UsePublicCocoaPods

end

我们在这里加上一行, 用上Alamofire的最新版本:

target 'UsePublicCocoaPods' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for UsePublicCocoaPods
  pod 'Alamofire', '5.6.4'

end

然后:

bundle exec pod install

(这里如果遇到问题, 记得在项目目录中跑
bundle config set --local path 'vendor/bundle'
).

安装完成之后会产生一些diff.

  • 项目下多了Pods文件夹. (应该被ignore)
  • 多了
    Podfile.lock
    文件. (不应该被ignore)
  • UsePublicCocoaPods/UsePublicCocoaPods.xcworkspace/

    UsePublicCocoaPods/UsePublicCocoaPods.xcodeproj/project.pbxproj
    都有改变.

这里需要检查一下
.gitignore
的配置.

然后就可以在代码里
import Alamofire
并使用它的代码了.

Dependency management tools

iOS中有多种依赖管理工具类型:

  • CocoaPods
  • Carthage
  • Swift package manager

CocoaPods

CocoaPods
比较老的依赖类型, 但是很多项目仍然在用.

Carthage

Carthage
build出来的是字节码, 一般也叫framework.

Swift package manager

swift-package-manager
比较新的依赖管理类型, 一般在Xcode的UI界面里操作.
本文就不介绍了.

项目结构和其他Trouble Shooting

Files and folders

Xcode是一个神奇的IDE, 它的文件夹叫”Group”.
在其中的文件分为”物理上存在”和”引用上存在”两种情况.

  • 删除文件的时候会问你只是删了引用(仅在Xcode里看不见)还是要进一步删了这个文件(物理上也删除).
  • 添加文件的时候仅仅拷贝到文件目录里是不够的, 需要”Add Files”, 打开目录添加一下, 才能在Xcode里也可见.

Project文件

ProjectName.xcodeproj/project.pbxproj
Project文件挺烦人的, 每新增/重命名一个文件它都会有diff.

有一些脚本工具可以帮忙将文件排序:
https://github.com/WebKit/webkit/blob/main/Tools/Scripts/sort-Xcode-project-file

这样产生diff之后运行一下(可以添加到git hook里), 文件会有序一些, 多人合作的时候不容易产生冲突.

真机调试

真机调试需要有证书, 并不是随便抓个机器连上线就能安装应用.
当设备的OS版本较高但Xcode版本较低时, 可能需要添加一些DeviceSupport文件:

比如:

将所缺版本拷贝到这个目录:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/

重启xcode再试.

References

前言:

上一篇介绍了 IL 指令的分类以及参数加载指令,该加载指令以ld开头,将参数加载到栈中,以便于后续执行操作命令。

本篇开始介绍参数存储指令,其指令以st开头,将栈中的数据,存储到指定的变量中,以方便后续使用。

参数存储指令介绍:

在 IL 中,除了参数存储指令
starg

stloc
之外,还有其他一些以 "st" 开头的指令,如
stfld

stsfld
,它们也用于存储值到特定位置。以下是所有的参数存储指令以及它们的用途:

  1. starg index
    :将计算堆栈顶部的值存储到方法的参数中,参数索引由后续字节指定。

  2. stloc index
    :将计算堆栈顶部的值存储到方法的局部变量中,局部变量索引由后续字节指定。

  3. stfld field
    :将计算堆栈顶部的值存储到对象的字段中,字段由元数据标识指定。

  4. stsfld field
    :用于将值存储到静态字段(static field)中。静态字段是属于类本身而不是类的实例的字段,它们在整个应用程序生命周期内只有一份拷贝,被所有实例共享。

这些指令都是用于在 IL 中进行值的存储操作,用途包括更新方法参数、修改局部变量值、设置对象字段值以及修改数组元素。它们在方法体中起到了关键的作用,用于实现各种数据操作和赋值操作。

1、存储指令:starg

  1. starg.s index
    :将计算堆栈顶部的值存储到方法的参数中,参数索引由单字节指定(适用于参数索引小于 256 的情况)。

  2. starg index
    :将计算堆栈顶部的值存储到方法的参数中,参数索引由后续字节指定(适用于参数索引大于等于 256 的情况)。

该指令为:store argument 存储参数的简写。

示例代码:

var dynamicMethod = new DynamicMethod("GetValue", typeof(object), new[] { typeof(string) }, typeof(AssMethodIL_ST));var ilGen =dynamicMethod.GetILGenerator();//使用 starg 指令将方法参数值传递给局部变量
ilGen.Emit(OpCodes.Ldstr,"abc");
ilGen.Emit(OpCodes.Starg,
0); //将方法的第一个参数值传递给局部变量//返回局部变量的值 ilGen.Emit(OpCodes.Ldarg_0); //加载第一个参数(message) ilGen.Emit(OpCodes.Ret); //返回该值

该示例的代码,主要体现在对参数重新赋值,对应的方法原型:

public static object GetValue(stringarg)
{
arg
= "abc";returnarg;
}

2、存储指令:stloc

stloc index
:将计算堆栈顶部的值存储到方法的局部变量中,局部变量索引由后续字节指定。

该指令为:store local 存储本地(变量)的简写。

该方法需要配合辅助变量使用,这个在上一篇辅助方法中有介绍到,这里重温一下上上篇的辅助方法,定义变量的内容:

该系列指令中,还有stloc_0、stloc_1、stloc_2、stloc_3,代表定义的第N个临时变量。

该变量的定义在反编绎 IL 中可以对照 .locals init 内容:

3、存储指令:stfld

stfld field
:将计算堆栈顶部的值存储到对象的字段中,字段由元数据标识指定。

该参数为:store filed 存储字段的简写,其常用于给字段变量赋值。

下面举一个给成员变量赋值的示例:

Entity entity = newEntity();

FieldInfo idInfo
= typeof(Entity).GetField("ID");var dynamicMethod = new DynamicMethod("SetValue", typeof(void), new[] { typeof(Entity), typeof(int) }, typeof(AssMethodIL_ST));var ilGen =dynamicMethod.GetILGenerator();

ilGen.DeclareLocal(
typeof(Entity));

ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldarg_1);
ilGen.Emit(OpCodes.Stfld, idInfo); ilGen.Emit(OpCodes.Ret); dynamicMethod.Invoke(null, new object[] { entity, 666666});

Console.WriteLine(entity.ID);
Console.Read();

运行结果:

方法原型对照:

小说明:

stfld 指令是成员变量的赋值,而我们常用的属性赋值【get】或取值【set】,对应的是方法调用,因此属性的相关操作会在方法调用指令一文中再述。

4、存储指令:stsfld

该参数为:store static filed 存储静态字段的简写,其常用于给静态字段变量赋值。

示例代码:

public class Entity { public int ID; public static intID2; }public static voidD3()
{
Entity entity
= newEntity();

FieldInfo idInfo
= typeof(Entity).GetField("ID2", BindingFlags.Static|BindingFlags.Public);var dynamicMethod = new DynamicMethod("SetValue", typeof(void), new[] { typeof(int) }, typeof(AssMethodIL_ST));var ilGen =dynamicMethod.GetILGenerator();

ilGen.DeclareLocal(
typeof(Entity));

ilGen.Emit(OpCodes.Ldarg_0);

ilGen.Emit(OpCodes.Stsfld, idInfo);
//加载第一个参数(message) ilGen.Emit(OpCodes.Ret); //返回该值 dynamicMethod.Invoke(null, new object[] { 222});

Console.WriteLine(Entity.ID2);
Console.Read();
}

运行结果:

5、数组存储指令:Stelem、Stelem_Ref

当涉及到对数组进行赋值时,可使用该指令:

如果是值类型数组,用 Stelem 指令:

如果是引用类型数组,用 Stelem_Ref 指令:

该指令的使用示例,我们在下一篇章节创建数组对象指令中进行演示。

总结:

相比于参数加载指令的类型复杂度,参数存储指令则相对简约许多。

总的来说,参数存储指令的重要性在于它为动态生成代码提供了强大的参数处理能力,让开发者可以更加灵活地操作方法的参数,实现更加复杂和多样化的编程逻辑。

通过合理运用参数存储指令,我们可以实现更加高效、智能和灵活的动态代码生成过程。

本文介绍在浏览器中,获取网页中的某一个请求信息,并将其导入到
Postman
软件,并进行
API
请求测试的方法。

Postman
是一款流行的
API
开发和测试工具,它提供了一个用户友好的界面,用于创建、测试、调试和文档化
API
。本文就介绍一下这一工具的最基本用法——导入网页请求,并配置相关的
Headers

Body
等信息,从而对请求加以测试的方法。

话不多说,我们直接开始操作。首先,为了可以从浏览器中获取网页请求的信息,我们需要在浏览器访问指定的网页(我这里用的是
Chrome
浏览器,因为
Chrome
支持以
cURL
格式将请求信息导出,相对更为方便一些,不过不用
cURL
格式也没问题);随后,按下
F12
按钮,打开开发者模式,选择“
网络
”选项,如下图所示;随后,我们需要再刷新一下网页。此时,我们就可以看到当前网页中各项内容的名称与信息了。

image

如上图所示,本文就以某一个网页中,名称为
NSW
的这一项为例,来介绍后续的操作。接下来,我们在
NSW
上右键,选择“
以cURL格式复制
”选项,如下图所示。如果大家的浏览器没有这一选项,那么可以直接复制请求的链接,但是需要后续手动将请求的其他信息(比如
Headers
)配置好——所以说
cURL
格式的请求用起来比较方便,就是因为它除了
链接
自身,还可以附带请求的其他信息,不需要我们手动再一一配置了。

接下来,在
Postman
软件中,选择“
Import
”选项,如下图所示。

随后,将我们刚刚复制好的
cURL
或者普通的
URL
粘贴到这里;如下图所示。

随后,就会看到如下图所示的界面。此时,如果大家前面“
Import
”时,复制的是
cURL
格式的请求,那么只要这个请求没有其他的问题,且在下图左侧位置调整好我们请求的类型(比如
GET
请求、
POST
请求等)之后,点击下图右侧紫色框内的“
Send
”选项,就可以发出请求了。如果大家用的是
URL
格式的请求,那么还需要手动配置一下请求的
Headers

Body
等信息(如果这些信息是必要的话);具体的配置方法大家继续看后文即可。

在我这里,虽然“
Import
”时,复制的是
cURL
格式的请求,但由于我这个请求有些问题,所以发出请求后,在“
Response
”还暂时看不到任何信息。

我这里需要再额外手动添加一项
Headers
才可以;如下图所示。当然,这里大家就结合自己请求的需要来修改就好。

配置
Headers
时,我们既可以用如上图所示的方式,来调整每一项
Headers

键值对
,也可以用下图中“
Bulk Edit
”方法,批量配置多条
Headers

如下图所示,我们可以直接复制多条
Headers
的信息,从而批量配置。需要注意,批量配置时,一行是一个
Headers
,每一个
Headers
的键、值用英文冒号
:
分开,同时可以用
//
注释掉不需要的
Headers

配置完毕对应的信息后,我们可以再“
Send
”一下我们的请求;如果在“
Response
”中看到如下图所示的信息,就说明我们的请求无误,且正常返回了内容;从下图右上角可以看出,请求返回的
HTTP
状态码为
200
,没有问题。

至此,大功告成。

本篇文章基于STM32F103和CH390H芯片进行例程移植及相关注意事项,简单验证TCP\UDP\Ping基础功能。

硬件:STM32F103开发板+沁恒CH390H的评估版图一示,SPI使用接口为:INT->PA0,RST->PA1,CS->PA2,SCK->PA5,MISO->PA6,MOSI->PA7,WOL->PA8。

图一

软件移植:采用的沁恒官方提供的CH390EVT中的Lwip例程进行移植,主要为CH390的驱动和Lwip协议栈部分,移植过程中注意有一个文件名为slipif.c文件会在编译过程中报错(图二),该文件可以删除,正常情况下用不到,如果需要可以重新自定义下。


图二

移植完成后,注意main函数中lwip初始化函数,IP地址为192.168.1.120,子网掩码255.255.255.0,网关为192.168.1.1。

void init_lwip_netif(void)
{
structip4_addr ipaddr, netmask, gateway;

IP4_ADDR(
&ipaddr, 192, 168, 1, 120);
IP4_ADDR(
&netmask, 255, 255, 255, 0);
IP4_ADDR(
&gateway, 192, 168, 1, 1);/*Initialize the LwIP stack without RTOS*/lwip_init();/*add the network interface (IPv4/IPv6) without RTOS*/netif_add(&ch390_netif, &ipaddr, &netmask, &gateway, NULL,&ethernetif_init, &ethernet_input);/*Registers the default network interface*/netif_set_default(&ch390_netif);
netif_set_up(
&ch390_netif);
}

------------------------------------------------------------------------------------------------------------------------------------------------

一、Ping测试:

CH390正常初始化后,用网线将CH390和电脑直连,PHY连接正常后D1灯会亮起,若未正常初始化评估版灯微亮,检查SPI接口时序和打印信息是否异常,图三打印信息。

图三

使用电脑CMD命令,ping192.168.1.120地址,图四示:

图四

正常ping通,说明网络连接正常,若出现ping不通,但网口灯已经常亮,建议关闭电脑防火墙,重新复位测试。

------------------------------------------------------------------------------------------------------------------------------------------------

二、TCP连接测试

(1)TCP Client:main函数注意修改tcp_clinet_int函数中的服务器IP地址,即电脑的IP地址为192.168.1.21,服务器端口为1000,本地端口是随机的。

void tcp_client_init(void)
{
ip4_addr_t server_ip;
IP4_ADDR(
&server_ip, 192, 168, 1, 21);

tcp_client_pcb
=tcp_new();

tcp_connect(tcp_client_pcb, (
const ip_addr_t *)&server_ip,
1000
, tcp_client_connected);
tcp_err(tcp_client_pcb, tcp_client_error);
}

采用网络测试工具建立TCP服务器,注意IP地址和端口号,建立成功后,会建立连接。程序中是回环测试,通过软件向CH390发送数据后,会进行数据回传,图五示。

图五

(2)TCP Server:注意本地端口为2300,图五示,创建一个TCP客户端连接,目标IP为192.168.1.120,目标端口为2300。

void tcp_server_init(void)
{
tcp_server_pcb
=tcp_new_ip_type(IPADDR_TYPE_ANY);
tcp_bind(tcp_server_pcb, IP_ANY_TYPE,
2300);
tcp_server_pcb
=tcp_listen(tcp_server_pcb);
tcp_accept(tcp_server_pcb, tcp_server_accept);
}

------------------------------------------------------------------------------------------------------------------------------------------------

三、UDP连接测试

UDP模式中:注意本地端口为2300,对端UDP端口为1000。

void udpecho_init(void)
{
struct udp_pcb *udp_echo_pcb;
udp_echo_pcb
=udp_new();

udp_bind(udp_echo_pcb, IP_ADDR_ANY,
2300);
udp_connect(udp_echo_pcb, IP_ADDR_ANY,
1000);
udp_recv(udp_echo_pcb, udpecho_recv, NULL);
}

通过软件建立UDP连接,图六示,注意目标端口和本地端口是否和程序设置对应。

以上为STM32+CH390H的应用测试,代码自取连接:
https://files.cnblogs.com/files/blogs/805237/CH390.rar?t=1712129908&download=true