[{"content":" 2021年不知道发什么神经, 开始研究xcassets文件解析, 用Python把BOM和car的结构初步解析出来. 2025年又开始发神经, 将2021年的Python代码用Rust重写. 写着写着发神经, 想把car的内容提取出来. 被Deepmap2解码卡住, 无奈中断. 2026年碰上AI+Agent, 想起来这货, 于是拿来练手. 本文全部由Agent分析代码总结的文件结构、流程.\n如有审核不足, 可在评论区指出错漏的地方.\n代码有空整理再放出来吧🕊️🕊️🕊️\nApple Assets (.car) File Format Apple 的 .car (Compiled Asset Catalog) 文件是 Xcode 在编译 Asset Catalog (.xcassets) 时生成的二进制文件。它采用三层嵌套结构：BOM 容器 包裹 CAR 元数据与索引，CAR 索引指向 CSI 渲染数据。\n整体架构 graph TB subgraph \"Assets.car 文件\" BOM[\"BOM 容器层(Bill of Materials)\"] BOM --\u003e VarStore[\"变量表 (VariableStore)命名入口: CARHEADER, RENDITIONS, ...\"] BOM --\u003e IdxStore[\"索引表 (IndexStore)Block 偏移/长度表\"] BOM --\u003e Blocks[\"数据块 (Blocks)实际二进制数据\"] VarStore --\u003e |\"name → index\"| IdxStore IdxStore --\u003e |\"offset + len\"| Blocks subgraph \"CAR 层 (Named Trees)\" CARHEADER[\"CARHEADER文件头元数据\"] KEYFORMAT[\"KEYFORMAT属性类型定义\"] FACETKEYS[\"FACETKEYS资源名称 → 属性\"] RENDITIONS[\"RENDITIONS属性组合 → CSI 数据\"] APPEARANCEKEYS[\"APPEARANCEKEYS外观模式\"] EXTMETA[\"EXTENDED_METADATA构建工具信息\"] end Blocks --\u003e CARHEADER Blocks --\u003e KEYFORMAT Blocks --\u003e FACETKEYS Blocks --\u003e RENDITIONS Blocks --\u003e APPEARANCEKEYS Blocks --\u003e EXTMETA end style BOM fill:#4a9eff,color:#fff style RENDITIONS fill:#ff6b6b,color:#fff style FACETKEYS fill:#51cf66,color:#fff 1. BOM 容器层 BOM (Bill of Materials) 是 Apple 通用的二进制容器格式，.car 文件以此为底层封装。所有 BOM 结构使用大端序 (Big-Endian)。\n1.1 文件布局 block-beta columns 1 A[\"StoreHeader (Magic: 'BOMStore' + 元数据)\"] B[\"... (480 字节填充) ...\"] C[\"IndexStore (Block 偏移/长度数组)\"] D[\"VariableStore (命名变量 → Index 映射)\"] E[\"Block Data (散布在文件各处)\"] style A fill:#4a9eff,color:#fff style C fill:#ffd43b,color:#333 style D fill:#51cf66,color:#fff style E fill:#ff6b6b,color:#fff1.2 StoreHeader 偏移 大小 字段 说明 0x00 8B magic \u0026quot;BOMStore\u0026quot; 0x08 4B version 固定为 1 0x0C 4B block_count 非空 Block 数量 0x10 4B index_offset IndexStore 的文件偏移 0x14 4B index_len IndexStore 长度 0x18 4B var_offset VariableStore 的文件偏移 0x1C 4B var_len VariableStore 长度 0x20 480B (padding) 保留填充 1.3 IndexStore (Block 索引表) packet-beta 0-31: \"count: u32\" 32-63: \"Index[0] (offset + len)\" 64-95: \"Index[1] (offset + len)\" 96-127: \"... Index[N]\"每个 Index 条目 (8 字节)：\n字段 大小 说明 offset u32 Block 数据的文件绝对偏移 len u32 Block 数据长度 1.4 VariableStore (命名变量表) packet-beta 0-31: \"count: u32\" 32-79: \"Variable[0] (index + len + name)\" 80-127: \"... Variable[N] (变长)\"每个 Variable (变长)：\n字段 大小 说明 index u32 指向 IndexStore 的索引 len u8 名称长度 name [u8; len] 变量名 (如 \u0026quot;CARHEADER\u0026quot;) 1.5 B-Tree 结构 BOM 中的树用于存储键值对集合，CAR 的各命名树均采用此结构。\ngraph TD TH[\"TreeHeadermagic: 'tree'version: 1block_size: 4096path_count: N\"] TH --\u003e INT[\"内部节点 (is_leaf=0)indices[0].val → 子节点\"] INT --\u003e LEAF1[\"叶节点 (is_leaf≠0)count 个 key/value 对\"] INT --\u003e LEAF2[\"叶节点count 个 key/value 对\"] LEAF1 --\u003e |\"forward\"| LEAF2 LEAF2 --\u003e |\"backward\"| LEAF1 style TH fill:#4a9eff,color:#fff style LEAF1 fill:#51cf66,color:#fff style LEAF2 fill:#51cf66,color:#fffTreeHeader (位于命名 Block 中)：\n字段 大小 说明 magic 4B \u0026quot;tree\u0026quot; version u32 固定为 1 index u32 根节点在 IndexStore 中的索引 block_size u32 固定为 4096 path_count u32 叶节点中的 key/value 对总数 unknown u8 未知 TreePaths (树节点)：\n字段 大小 说明 is_leaf u16 0=内部节点, 非0=叶节点 count u16 子项数量 forward u32 下一个兄弟节点的 Block 索引 backward u32 上一个兄弟节点的 Block 索引 indices [TreePathIndex; count] key/value 对 TreePathIndex (每条 8 字节)：\n字段 大小 说明 val u32 value Block 在 IndexStore 中的索引 key u32 key Block 在 IndexStore 中的索引 1.6 寻址流程 sequenceDiagram participant Caller as 调用者 participant VS as VariableStore participant IS as IndexStore participant File as 文件数据 Caller-\u003e\u003eVS: 查找变量名 \"RENDITIONS\" VS--\u003e\u003eCaller: Variable { index: 42, ... } Caller-\u003e\u003eIS: 读取 IndexStore[42] IS--\u003e\u003eCaller: Index { offset: 0x1A00, len: 8192 } Caller-\u003e\u003eFile: 读取 offset=0x1A00, len=8192 File--\u003e\u003eCaller: Block 数据 (TreeHeader + ...) 2. CAR 层 (Asset Catalog) CAR 层建立在 BOM 容器之上，通过 6 个命名 BOM Tree 组织资源数据。CAR 层结构使用小端序 (Little-Endian)（ExtendedMetadata 除外）。\n2.1 命名树一览 树名 类型 说明 KEYFORMAT 单值 属性类型定义 (KeyFmt) CARHEADER 单值 文件头元数据 (Header) EXTENDED_METADATA 单值 构建工具信息 (大端序) APPEARANCEKEYS 键值树 外观模式映射 FACETKEYS 键值树 资源名称 → 属性 Token RENDITIONS 键值树 属性组合 → CSI 渲染数据 2.2 解析流程 flowchart LR A[\"Car::new(path)\"] --\u003e B[\"BOM::new_with_file(mmap 打开)\"] B --\u003e C[\"解析 KEYFORMAT(属性格式定义)\"] C --\u003e D[\"解析 APPEARANCEKEYS\"] D --\u003e E[\"解析 EXTENDED_METADATA\"] E --\u003e F[\"解析 CARHEADER\"] F --\u003e G[\"解析 FACETKEYS(资源名称索引)\"] G --\u003e H[\"解析 RENDITIONS(渲染数据库)\"] H --\u003e I[\"构建 MultiMap\u0026lt;u16, CSIItem\u0026gt;按 Identifier 索引\"] style A fill:#4a9eff,color:#fff style I fill:#ff6b6b,color:#fff2.3 CARHEADER (文件头) Magic: \u0026quot;RATC\u0026quot; (小端序)\n字段 类型 说明 coreui_version u32 CoreUI 版本 storage_version u32 存储格式版本 storage_timestamp u32 存储时间戳 rendition_count u32 渲染项总数 main_version_string String(128B) 主版本字符串 version_string String(256B) 版本字符串 uuid [u8; 16] 唯一标识符 associated_checksum u32 关联校验和 schema_version u32 Schema 版本 color_space ColorSpace 默认色彩空间 key_semantics u32 键语义 2.4 KEYFORMAT (键格式) Magic: \u0026quot;tmfk\u0026quot; (小端序)\nKeyFmt 定义了 RENDITIONS 树中键的属性类型序列，决定了每个渲染项的键如何被解析。\npacket-beta 0-31: \"'tmfk' (magic)\" 32-63: \"version: u32\" 64-95: \"max_count: u32\" 96-111: \"AttrType[0]: u16\" 112-127: \"(2B padding)\" 128-143: \"AttrType[1]: u16\" 144-159: \"(2B padding)\" 160-191: \"... AttrType[N] + pad\"AttributeType 枚举 (u16)：\n值 名称 说明 0 ThemeLook 主题外观 1 Element UI 元素类型 2 Part 部件 3 Size 尺寸类别 4 Direction 布局方向 6 Value 值 7 ThemeAppearance 主题外观模式 8-9 Dimension1/2 维度 10 State 状态 11 Layer 图层 12 Scale 缩放比例 (@1x/@2x/@3x) 13 Localization 本地化 15 Idiom 设备类型 16 Subtype 子类型 17 Identifier 资源标识符 (用于索引渲染数据库) 20-21 H/V SizeClass 水平/垂直尺寸类别 24 DisplayGamut 显示色域 25 DeploymentTarget 部署目标 2.5 FACETKEYS (资源名称索引) FACETKEYS 是一个 HashMap\u0026lt;String, KeyToken\u0026gt; 的树，将资源名称映射到属性集合。\ngraph LR FN[\"资源名称'AppIcon'\"] --\u003e KT[\"KeyTokenx, yattrs: [Attribute]\"] KT --\u003e A1[\"Attributename: Scaleval: 2\"] KT --\u003e A2[\"Attributename: Idiomval: 1 (Phone)\"] KT --\u003e A3[\"Attributename: Identifierval: 0x42\"] A3 --\u003e |\"用此值查询rendition_db\"| RDB[\"MultiMap\u0026lt;u16, CSIItem\u0026gt;\"] style FN fill:#51cf66,color:#fff style A3 fill:#ff6b6b,color:#fff style RDB fill:#ff6b6b,color:#fff2.6 RENDITIONS 树键值结构 RENDITIONS 树是 .car 文件的核心数据存储。\n键结构：按 KEYFORMAT 定义的属性类型序列，每个属性值为 u16 (小端序)。\nKey: [ThemeLook:u16][Element:u16][Part:u16]...[Identifier:u16]... ← 按 KEYFORMAT 中的顺序排列 → 值结构：CSIHeader + TLV 元数据 + 可选渲染数据 (详见第 3 节)。\n查找流程：\nsequenceDiagram participant App as 应用程序 participant FK as FACETKEYS participant RDB as Rendition DB participant CSI as CSIItem App-\u003e\u003eFK: 查找 \"AppIcon\" FK--\u003e\u003eApp: KeyToken { attrs: [..., Identifier=0x42] } App-\u003e\u003eRDB: rendition_db.get(0x42) RDB--\u003e\u003eApp: Vec (多个渲染变体) App-\u003e\u003eCSI: 按 Scale/Idiom 等筛选 CSI--\u003e\u003eApp: 目标 CSIItem (含图像数据)2.7 EXTENDED_METADATA Magic: \u0026quot;META\u0026quot; (大端序 - 唯一的大端序 CAR 结构)\n字段 大小 说明 thinning_args 256B Thinning 参数 deployment_platform_version 256B 部署平台版本 deployment_platform 256B 部署平台 authoring_tool 256B 构建工具信息 3. CSI 层 (Core Structured Image) CSI 是单个渲染项的完整数据结构，包含元数据、TLV 扩展信息和实际渲染数据。\n3.1 CSIHeader 布局 Magic: \u0026quot;ISTC\u0026quot; (小端序)\nblock-beta columns 1 A[\"CSIHeader 固定部分 (56 字节)\"] B[\"CSIMetadata (name: 128 字节)\"] C[\"TLV 数据 (tlv_length 字节)\"] D[\"Rendition 数据 (rendition_length 字节, 可选)\"] style A fill:#4a9eff,color:#fff style B fill:#ffd43b,color:#333 style C fill:#51cf66,color:#fff style D fill:#ff6b6b,color:#fff固定部分：\n字段 类型 说明 magic [u8; 4] \u0026quot;ISTC\u0026quot; version u32 CSI 版本 flags Flags 位域标志 (32位) width u32 图像宽度 height u32 图像高度 scale_factor u32 缩放因子 (100=@1x, 200=@2x, 300=@3x) encoding Encoding 像素编码格式 color_model ColorModel 色彩模型 CSIMetadata：\n字段 类型 说明 modification_time u32 修改时间 layout_type LayoutType 布局类型 name String(128B) 渲染项名称 BitmapList (紧跟 CSIMetadata 之后)：\n字段 类型 说明 bitmap_count u32 位图数量 (通常为 1) zero u32 保留 (通常为 0) rendition_length u32 Rendition 数据长度 3.2 Flags 位域 packet-beta 0-0: \"H\" 1-1: \"E\" 2-2: \"V\" 3-3: \"O\" 4-7: \"BmpEnc\" 8-8: \"O\" 9-9: \"F\" 10-10: \"T\" 11-11: \"P\" 12-31: \"reserved (20 bits)\" H=is_header_flagged_fpo, E=is_excluded_from_contrast_filter, V=is_vector_based, O=is_opaque, BmpEnc=bitmap_encoding(4bit), O=opt_out_of_thinning, F=is_flippable, T=is_tintable, P=preserved_vector_representation\n位 标志 说明 0 is_header_flagged_fpo FPO 标记 1 is_excluded_from_contrast_filter 排除对比度滤镜 2 is_vector_based 矢量图 3 is_opaque 不透明 4-7 bitmap_encoding 位图编码子类型 8 opt_out_of_thinning 不参与 Thinning 9 is_flippable 可翻转 10 is_tintable 可着色 11 preserved_vector_representation 保留矢量表示 12-31 reserved 保留位 3.3 Encoding (像素编码) Tag (4字节) 枚举值 说明 \\0\\0\\0\\0 None 无像素数据 BGRA ARGB BGRA 字节序 → RGBA (交换 R/B) ATAD Data 原始数据 YARG GRAY 灰度 8位 GEPJ JPEG JPEG 压缩 _FDP PDF PDF 矢量 PBEW WEBP WebP 压缩 WBGR ARGB16 16位 BGRA 61AG GA16 灰度+Alpha 16位 _8AG GA8 灰度+Alpha 8位 5BGR RGB5 XRGB1555 格式 _GVS SVG SVG 矢量 FIEH HEIF HEIF 压缩 3.4 ColorModel / ColorSpace ColorModel (u32)：\n值 名称 说明 0 None 无 1 RGB RGB 2 Monochrome 单色 3 RGB0 RGB (变体) 4 RGBP3 Display P3 ColorSpace (u32)：\n值 名称 1 sRGB 2 GrayGamma2.2 3 Display P3 4 Extended Range sRGB 5 Extended Linear sRGB 6 Extended Gray 257 System sRGB 3.5 TLV 元数据 (RenditionType) TLV (Tag-Length-Value) 格式存储附加元数据，紧跟在 BitmapList 之后。\npacket-beta 0-31: \"tag: u32\" 32-63: \"len: u32\" 64-127: \"data: [u8; len]\" 以上为单个 TLV 条目，CSI 中连续存放 N 个 TLV。\nTag 名称 数据内容 1001 Slices 九宫格切片信息 (x, y, w, h) × N 1003 Metrics 上右内边距 + 下左内边距 + 图像尺寸 1004 BlendModeAndOpacity blend_mode: u32 + opacity: f32 1005 UTI 统一类型标识符 (如 \u0026quot;public.png\u0026quot;) 1006 EXIFOrientation EXIF 旋转方向 (0-8) 1007 BytesPerRow 行字节步长 (stride) 1010 Reference 内部引用 (magic \u0026quot;INLK\u0026quot; + 坐标 + 键) 3.6 Rendition 数据类型 Rendition 是 CSI 中的实际载荷数据，按 magic 标识区分类型：\ngraph TD R[\"Rendition (可选)\"] R --\u003e |\"magic: RLOC\"| COLOR[\"Color色彩空间 + 分量值 (f64[])\"] R --\u003e |\"magic: DWAR\"| RAW[\"RawData原始二进制数据\"] R --\u003e |\"magic: MLEC\"| CBCK[\"ThemeCBCK图像数据 (最常见)\"] R --\u003e |\"magic: SISM\"| MSIS[\"MultisizeImageSet多尺寸图集\"] CBCK --\u003e V0[\"v0/v2: 单块数据\"] CBCK --\u003e V1[\"v1/v3: 多块数据(ThemePartHeader)\"] style R fill:#4a9eff,color:#fff style CBCK fill:#ff6b6b,color:#fffRenditionColor (RLOC)：\n字段 类型 说明 version u32 版本 color_space ColorSpace 色彩空间 components Vec\u0026lt;f64\u0026gt; 颜色分量 RenditionThemeCBCK (MLEC - 最常见)：\n字段 类型 说明 version u32 版本 (0-3) compression_type CompressionType 压缩类型 raw_datas Vec\u0026lt;Vec\u0026lt;u8\u0026gt;\u0026gt; 压缩数据块 3.7 CompressionType 值 名称 说明 0 Uncompressed 未压缩 1 Rle 行程编码 2 Zip Zip 压缩 3 Lzvn LZVN 压缩 4 Lzfse LZFSE 压缩 5 JpegLzfse JPEG + LZFSE 6 Blurred 模糊处理 7 Astc ASTC 纹理压缩 8 PaletteImg 调色板图像 9 HEVC HEVC 视频帧 10 DeepmapLzfse Deepmap + LZFSE 11 Deepmap2 Deepmap2 格式 (见第 4 节) 3.8 LayoutType 布局类型决定了渲染项的用途和展示方式：\n范围 类别 示例 6-9 特效 Gradient(6), Effect(7), Vector(9) 10-12 单部件 FixedSize(10), Tile(11), Scale(12) 20-25 三段式 H-Tile(20), H-Scale(21), V-Tile(23) 30-34 九宫格 Tile(30), Scale(31), EdgesOnly(34) 40 六段式 SixPart 50 动画 AnimationFilmstrip 1000-1014 语义类型 Data, ExternalLink, Color, Texture\u0026hellip; 3.9 Idiom (设备类型) 值 名称 0 Universal 1 Phone (iPhone) 2 Pad (iPad) 3 TV (Apple TV) 4 Car (CarPlay) 5 Watch (Apple Watch) 6 Marketing 4. Deepmap2 图像编码 Deepmap2 是 Apple 的专有图像编码格式，支持 4 种解码模式：原始像素、有损预测压缩 (YCoCg)、无损压缩和调色板索引。\n4.1 Deepmap2Header Magic: \u0026quot;dmp2\u0026quot; (小端序)\n偏移 大小 字段 说明 0x00 4B magic \u0026quot;dmp2\u0026quot; 0x04 1B decode_type 解码类型 (1-4) 0x05 1B version 色度缩放标志 0x06 1B predictor_type 辅助标志 0x07 1B pixel_format 像素格式 (1-4) 0x08 2B width 图像宽度 0x0A 2B height 图像高度 0x0C 2B palette_size 调色板条目数 (仅 Palette 类型) 0x0E 2B palette_type 调色板类型 (仅 Palette 类型) 0x10 N×4B palette 调色板数据 BGRA (仅 Palette 类型) DecodeType：\n值 名称 说明 1 None 原始未压缩像素 2 Default LZFSE + Zigzag + 预测 + YCoCg 3 Lossless LZFSE 压缩 (无预测) 4 Palette 调色板索引 PixelFormat：\n值 名称 每像素字节 说明 1 G8 1 灰度 2 GA88 2 灰度 + Alpha 3 Rgb888 3 RGB 4 Rgba8888 4 RGBA 4.2 解码流程总览 flowchart TD INPUT[\"输入数据\"] --\u003e MAGIC{检测 Magic} MAGIC --\u003e |\"'dmp2'\"| DMP2[\"解析 Deepmap2Header\"] MAGIC --\u003e |\"'KCBC'\"| KCBC[\"KCBC 分块容器\"] DMP2 --\u003e PAYLOAD[\"提取 Payload\"] PAYLOAD --\u003e TYPE{decode_type?} TYPE --\u003e |\"1: None\"| NONE[\"直接读取原始像素\"] TYPE --\u003e |\"2: Default\"| DEFAULT[\"LZFSE 解压缩\"] TYPE --\u003e |\"3: Lossless\"| LOSSLESS[\"LZFSE 解压缩\"] TYPE --\u003e |\"4: Palette\"| PALETTE[\"LZFSE 解压缩\"] DEFAULT --\u003e MULTI[\"解析多流布局[alpha][predictors][high][low]\"] MULTI --\u003e ZIGZAG[\"Zigzag 解码(合并高低字节流)\"] ZIGZAG --\u003e PRED[\"应用预测器(还原差分)\"] PRED --\u003e YCOCG[\"YCoCg → RGB 转换\"] YCOCG --\u003e RGBA1[\"RGBA 输出\"] LOSSLESS --\u003e DIRECT[\"直接像素格式转换\"] NONE --\u003e DIRECT DIRECT --\u003e RGBA2[\"RGBA 输出\"] PALETTE --\u003e PLOOKUP[\"调色板查表\"] PLOOKUP --\u003e RGBA3[\"RGBA 输出\"] KCBC --\u003e TILES[\"解析分块(col, row, dmp2 子数据)\"] TILES --\u003e EACH[\"逐块独立解码 dmp2\"] EACH --\u003e GRID[\"按网格拼合\"] GRID --\u003e RGBA4[\"RGBA 输出\"] style INPUT fill:#4a9eff,color:#fff style RGBA1 fill:#51cf66,color:#fff style RGBA2 fill:#51cf66,color:#fff style RGBA3 fill:#51cf66,color:#fff style RGBA4 fill:#51cf66,color:#fff style DEFAULT fill:#ff6b6b,color:#fff4.3 Default 解码 (Type 2) 详解 这是最复杂的解码路径，使用 LZFSE 压缩 + 多流分离 + Zigzag 编码 + 空间预测 + YCoCg 色彩模型。\n4.3.1 LZFSE 解压后的内存布局 block-beta columns 1 A[\"Alpha 平面 — width x height 字节 (仅 has_alpha)\"] B[\"预测器字节 — height 字节 (每行一个预测器类型)\"] C[\"High 字节流 — width x height x components 字节\"] D[\"Low 字节流 — width x height x components 字节\"] style A fill:#ffd43b,color:#333 style B fill:#51cf66,color:#fff style C fill:#4a9eff,color:#fff style D fill:#ff6b6b,color:#fff其中 components = 3 (彩色, YCoCg 三通道) 或 1 (灰度)。\n4.3.2 Zigzag 解码 将分离的高低字节流合并为有符号 16 位值：\ncombined = (lo as u16) | ((hi as u16) \u0026lt;\u0026lt; 8) magnitude = combined \u0026gt;\u0026gt; 1 value = if (combined \u0026amp; 1) != 0 { -magnitude } else { magnitude } 4.3.3 预测器算法 每行可独立选择预测器，有 5 种类型：\ngraph LR subgraph \"预测器类型\" P0[\"0: None无预测直接输出\"] P1[\"1: PaethvImage Paeth选择 left 或 up\"] P2[\"2: Left左邻预测累加和\"] P3[\"3: Up上邻预测逐元素加\"] P4[\"4: Mean均值预测(left+up+1)/2\"] end style P1 fill:#ff6b6b,color:#fff style P2 fill:#ffd43b,color:#333 style P3 fill:#51cf66,color:#fff style P4 fill:#4a9eff,color:#fff所有预测器以 3 个分量为一组 (PREDICTOR_GROUP_SIZE=3) 进行处理，对应 YCoCg 的 Y、Co、Cg 三通道。\nPaeth 预测器 (简化版)：\n对每组 3 个分量: dist_left = |up[0] - up_left[0]| dist_up = |left[0] - up_left[0]| 若 dist_left \u0026lt;= dist_up: 整组使用 left 否则: 整组使用 up 4.3.4 YCoCg → RGB 转换 co_scaled = Co \u0026lt;\u0026lt; chroma_scale cg_scaled = Cg \u0026lt;\u0026lt; chroma_scale temp = Y - trunc_div2(cg_scaled) R = clamp(temp + co_scaled - trunc_div2(co_scaled), 0, 255) G = clamp(temp + cg_scaled, 0, 255) B = clamp(temp - trunc_div2(co_scaled), 0, 255) 其中 chroma_scale = 1 (header.version ≠ 0) 或 0。\n4.4 Palette 解码 (Type 4) 调色板模式使用索引查表，支持两种子类型：\npalette_type 说明 Payload 布局 3 Alpha + 索引 [alpha × pixel_count][index × pixel_count] 4 仅索引 [index × pixel_count] 调色板条目格式：u32 LE BGRA → 转换为 RGBA 输出。\n4.5 KCBC 分块容器 大图像可使用 KCBC 容器将图像分块存储，每块独立编码。\npacket-beta 0-31: \"'KCBC' (magic)\" 32-63: \"col: u32\" 64-95: \"row: u32\" 96-127: \"rows: u32\" 128-159: \"len: u32\" 160-287: \"dmp2 子数据 (len 字节)\" 以上为单个 KCBC Block，文件中连续存放多个 Block。\n解码后按 (row, col) 网格拼合为完整图像。\n5. 端到端数据流 flowchart TB subgraph \"1. 文件打开\" FILE[\"Assets.car\"] --\u003e MMAP[\"内存映射 (mmap)\"] MMAP --\u003e BOM_PARSE[\"解析 BOM 容器(大端序)\"] end subgraph \"2. 索引构建\" BOM_PARSE --\u003e KF[\"读取 KEYFORMAT确定属性类型序列\"] KF --\u003e FK[\"读取 FACETKEYS构建名称→属性映射\"] FK --\u003e RD[\"遍历 RENDITIONS 树解析每个 CSI\"] RD --\u003e DB[\"构建 MultiMap\u0026lt;u16, CSIItem\u0026gt;按 Identifier 索引\"] end subgraph \"3. 资源查找\" QUERY[\"查询 'AppIcon'\"] --\u003e FACET[\"FACETKEYS 查表→ Identifier=0x42\"] FACET --\u003e LOOKUP[\"rendition_db.get(0x42)→ 多个渲染变体\"] LOOKUP --\u003e FILTER[\"按 Scale/Idiom 等筛选\"] end subgraph \"4. 图像解码\" FILTER --\u003e CSI_ITEM[\"CSIItem\"] CSI_ITEM --\u003e COMP{压缩类型?} COMP --\u003e |\"Lzfse\"| LZFSE_DEC[\"LZFSE 解压缩\"] COMP --\u003e |\"Deepmap2\"| DMP2_DEC[\"Deepmap2 解码(预测+YCoCg)\"] COMP --\u003e |\"Uncompressed\"| UNCOMP[\"直接读取\"] COMP --\u003e |\"JPEG/WebP\"| IMG_DEC[\"图像库解码\"] LZFSE_DEC --\u003e PIXEL[\"像素格式转换→ RGBA\"] DMP2_DEC --\u003e PIXEL UNCOMP --\u003e PIXEL IMG_DEC --\u003e PIXEL end PIXEL --\u003e OUTPUT[\"输出图像\"] style FILE fill:#4a9eff,color:#fff style DB fill:#ff6b6b,color:#fff style OUTPUT fill:#51cf66,color:#fff 6. Magic 字节汇总 Magic 字节序 层级 说明 BOMStore - BOM 文件头标识 tree - BOM B-Tree 头标识 RATC LE CAR CARHEADER ISTC LE CAR CSIHeader tmfk LE CAR KeyFmt META BE CAR ExtendedMetadata RLOC LE Rendition Color 类型 DWAR LE Rendition RawData 类型 MLEC LE Rendition ThemeCBCK 类型 SISM LE Rendition MultisizeImageSet INLK LE TLV Reference 引用 dmp2 LE Deepmap2 Deepmap2 图像头 KCBC LE Deepmap2 分块容器头 7. 字节序规则 层级 字节序 例外 BOM 容器 大端序 无 CAR 结构 小端序 ExtendedMetadata (META) 使用大端序 CSI / Rendition 小端序 无 Deepmap2 小端序 无 参考链接 Reverse engineering the .car file format (compiled Asset Catalogs) A Deep Dive into Apple\u0026rsquo;s .car File Format vaguilar/carutil facebookarchive/xcbuild iineva/bom ","date":"2026-04-05T00:00:00Z","permalink":"/p/apple-assets-car-%E6%96%87%E4%BB%B6/","title":"Apple Assets Car 文件"},{"content":" 这个库最开始是python的版本https://github.com/skorokithakis/shortuuid\n后来还有一个Go的版本https://github.com/lithammer/shortuuid\nUUID字符长度为32(去掉-), ShortUUID默认生成字符长度为20, 所以在以前在python项目中作为短的唯一标识生成, 趁着有空试试撸一个swift的版本https://github.com/skytoup/shortuuid。\n刚开始分析python版的源码, 发现设计得还挺简单的。所有算法逻辑都在一个文件, 源码也就141行。\nUUID简介 全称Universally Unique Identifier, 是通用唯一识别码, 由128 bit(16 byte)的数值组成。\n具体介绍: https://baike.baidu.com/item/UUID/5921266?fr=aladdin\n算法大概流程 ShortUUID包含一组字符Alphabet用于生成短UUID的字符, 以ascii码小到大排序并且不重复。\nUUID转(encode)short UUID\n把UUID作为数据不断整除Alphabet的长度, 并以余数作为索引选取Alphabet的字符, 直至被除数为0, 最后组成的字符翻转。\n伪代码\nwhile UUID \u0026gt; 0: UUID, rem = UUID / Alphabet.length, UUID % Alphabet.length `short uuid` += `Alphabet`[rem] `short uuid`.reverse() short UUID转(decode)UUID\n就是把encode的算发反转, UUID(初始为0)作为数据乘以Alphabet的长度, 再加上short UUID对应Alphabet的索引位置。\n伪代码\nUUID = 0 for char in `short UUID`: UUID * `Alphabet`.length + `Alphabet`.indexOf(char) 看似简单, 实现起来还是有些难度 大数运算 UUID为128 bit的数据, python自带大数运算功能, 但swift的最大位数Int也只有UInt64和Int64, 即大正整数运算只能用进行64 bit进行运算。而且在标准库中暂未发现有大数运算的相关支持库, 所以只能写一个。\n这个算法仅用到128 bit的运算有\n除并且求余 乘 加 主要实现思路是只拆分为32 bit的数据进行分别运算, 最后把拆分的运算结果组合为128 bit运算结果(具体实现看源码)\nUUID5 python标准库有相关实现, 但swift没有。实现也比较简单, 主要流程是namespace对应UUID bytes拼上input string再进行SHA1(结果为160 bit), 取前128 bit修改部分数据即可。\n相关链接\npython uuid源码: https://github.com/python/cpython/blob/main/Lib/uuid.py oc的uuid5源码: https://gist.github.com/eliburke/1a55ed616bb15a7f908b ","date":"2021-06-27T00:00:00Z","permalink":"/p/shortuuid-uuid%E7%94%9F%E6%88%90%E5%AF%B9%E5%BA%94%E7%BC%A9%E7%9F%AD%E7%9A%84%E5%AD%97%E7%AC%A6/","title":"ShortUUID - UUID生成对应缩短的字符"},{"content":"简介 操作归档的DWARF调试符号文件\n场景一 打包有开启bitcode, 上传时没有勾选上传符号文件。 结果iTunes后台处理完安装包后, 不会有下载dSYM文件, 导致该安装包发生的闪退日志无法还原符号, 只能看到一堆地址。\n解决方法\n找到对应打包的的***.xcarchive归档文件, 点击右键选择查看包内容(Show Package Contents), 看到里面有两个符号表相关的文件夹, 分别是BCSymbbolMaps和dSYMs。 虽然这里有dSYMs, 但是是隐藏了部分符号的, 直接使用会导致还原的堆栈信息显示很多hidden符号。 打开终端并切换当前工作目录到你的***.xcarchive(即cd /....../***.xcarchive), 再输入命令dsymutil --symbol-map BCSymbolMaps dSYMs/*, 即可把BCSymbolMaps内的符号信息更新到dSYMs目录下的对应文件 场景2 在开发周期内, 打的一些测试包没有上传符号文件, 后来这个安装包发生闪退的情况, 想还原堆栈时发现该打包的相关文件已经被清理。\n或者最近看到有些开发者说bugly和UMeng的线上闪退堆栈无法还原, 而且上传了对应的dSYM, 可以先尝试到iTunes后台下载对应包dSYM上传试试, 还是不行可以往下看试试。\n解决方法\n获取发生闪退的ipa 已上线 打包后归档的文件里面, 有一个***.ipa的文件 越狱设备安装该线上包后, 进行脱壳提取 日常开发测试(上传到类似fir、pgyer的网址) 打包后归档的文件里面, 有一个***.ipa的文件 到对应地方下载安装的***.ipa 解压***.ipa, 对app的主二进制文件(Payload/***.app/***)使用restore-symbol(https://github.com/tobefuturer/restore-symbol)进行部分被裁的符号表还原 restore-symbol(https://github.com/tobefuturer/restore-symbol)的使用请参考该工具, 主要是根据class-dump恢复OC的对应符号信息更新到到mach-o, 也有一些fork分支有实现恢复swift的符号还原 终端执行命令dsymutil {替换为上一步已还原符号的二进制文件路径} -o {替换为dSYMs输出的目录路径} 例如dsymutil testBin -o ./dSYMs 参考链接 dsymutil - man: https://llvm.org/docs/CommandGuide/dsymutil.html Apple Docs - Restore-Hidden-Symbols: https://developer.apple.com/documentation/xcode/adding-identifiable-symbol-names-to-a-crash-report#Restore-Hidden-Symbols ","date":"2021-06-22T00:00:00Z","permalink":"/p/dsymutil-%E7%A8%8B%E5%BA%8F%E8%B0%83%E8%AF%95%E7%AC%A6%E5%8F%B7%E5%B8%AE%E5%8A%A9%E5%B7%A5%E5%85%B7/","title":"dsymutil 程序调试符号帮助工具"},{"content":" 老鸟可略过\u0026hellip;\n仓库源 在Cocoapods 1.7.2版本前, 首次安装还需要初始化好几个GB的仓库源(git方式拉取, 源为github), 拉起速度又慢, 而且改非常大。\n后来在1.7.0提出一个实验性方案, 把git方式的仓库源改为CDN方式的仓库源, 最后在1.7.2正式启用。\n旧的仓库源: https://github.com/CocoaPods/Specs.git 新的仓库源: https://cdn.cocoapods.org/ Cocoapods相关目录及结构 ${用户目录}/.cocoapods/repos/${源仓库名称} # 源仓库 git源仓库结构 ${三方库名称}/${版本号}/${spec文件} CDN源仓库结构(主要结构) all_pods.txt 所有库的版本号信息 all_pods_versions_${MD5[0]}_${MD5[1]}_${MD5[2]}.txt 按MD5(库名称)[0...3]切分文件存储库的版本号信息 txt文件每行一个库, 格式${库名称}/${版本号}/${版本号}/... Podfile\u0026amp;源仓库\u0026amp;三方库关系图 pod update/install的部分简略流程 本地源仓库同步远程源仓库(强烈建议命令后面添加参数--no-repo-update略过这步, 有需要同步再手动更新pod repo update) 按库的名称在本地源仓库查找合适版本号的spec(git和CDN的源查找方式不一样) 根据spec中的source和其它相关参数下载(git/svn/hg/http)仓库文件(这步之前还有个本地缓存查找的过程) 库的名字查找spec git仓库源 根据本地目录结构查找spec文件 CDN仓库源 三方库的名称进行MD5(小写), 结果取前3位字母 访问对应的本地文件(all_pods_versions_${MD5[0]}_${MD5[1]}_${MD5[2]}.txt)查找合适的版本号 通过链接https://cdn.cocoapods.org/Specs/${MD5[0]}_${MD5[1]}_${MD5[2]}/${仓库名}/${版本号}/${仓库名}.podspec.json获取spec文件 rust写了个根据podfile.lock检测库是否有新版本的小工具, 里面就是利用CDN仓库源方式查找版本, 有兴趣可以去看看https://github.com/skytoup/PodHelper\n源仓库管理命令 pod repo # 列出添加的源 pod repo add ${源仓库本地名称} ${源仓库链接} # 添加源 pod repo remove ${源仓库本地名称} # 移除源, 也可以直接删除${用户目录}/.cocoapods/repos/${源仓库名称}` 相关链接 1.7.2更新说明: https://blog.cocoapods.org/CocoaPods-1.7.2/ podspec.source doc: https://guides.cocoapods.org/syntax/podspec.html#source ","date":"2021-04-28T00:00:00Z","permalink":"/p/2021-cocoapods%E6%96%B0%E6%89%8B%E8%BF%9B%E9%98%B6%E6%8C%87%E5%8D%97%E4%B9%8B%E4%BB%93%E5%BA%93%E6%BA%90/","title":"2021 Cocoapods新手进阶指南之仓库源"},{"content":" 老手可完全略过, 其它废话不多说\u0026hellip;\n一. 安装 打开终端(Terminal), 输入命令\nsudo gem install cocoapods\n还是提示无法安装可尝试\nsudo gem install cocoapods --user\n二. pod命令使用 pod init 在项目的根目录运行, 生成一份Podfile文件 可自行创建Podfile文件, 不使用此命令 pod deintegrate 在项目根目录运行, 移除项目cocoapods依赖管理相关的所有设置(*.xcodeproj里面的build setting相关设置), 一般用不到 pod install 按照Podfile的设置(版本号、仓库索引源\u0026hellip;)安装依赖库, 并且生成/更新 Podfile.lock(记录依赖库的依赖关系、版本号、仓库索引源\u0026hellip;) pod update 按照Podfile的设置并结合Podfile.lock(必须有)进行版本升级 一般使用\npod init # 初始化出一份Podfile文件 Podfile文件内添加三方库 pod install # 项目首次安装依赖库 pod update # 日常根据Podfile设定的规则进行版本更新 小技巧\npod init和pod update每次运行都会先更新仓库索引源数据, 略略费时间, 可以改为手动更新 可在后面增加参数--no-repo-update, 可以安装/更新时不更新仓库索引源 pod install --no-repo-update pod update --no-repo-update 手动更新仓库索引源 pod repo update 三. Podfile pod init生成的模板\n# Uncomment the next line to define a global platform for your project # platform :ios, \u0026#39;9.0\u0026#39; target \u0026#39;iOSDemo\u0026#39; do # Comment the next line if you don\u0026#39;t want to use dynamic frameworks use_frameworks! # Pods for iOSDemo end platform: 设定项目兼容的平台和版本号 target: 某个target依赖库安装配置 里面填写一些需要安装的依赖库和配置 pod {依赖库的名称}, 比如pod 'AFNetworking', pod 'AFNetworking', ~\u0026gt; 1.0 # Uncomment the next line to define a global platform for your project # platform :ios, \u0026#39;9.0\u0026#39; target \u0026#39;iOSDemo\u0026#39; do # Comment the next line if you don\u0026#39;t want to use dynamic frameworks use_frameworks! # Pods for iOSDemo pod \u0026#39;AFNetworking\u0026#39; end 依赖库版本设置\n= 0.1 0.1版本 \u0026gt; 0.1 任何大于0.1版本 \u0026gt;= 0.1 任何大于或等于0.1版本 \u0026lt; 0.1 任何小于0.1版本 \u0026lt;= 0.1 任何小于或等于0.1版本 ~\u0026gt; 0.1.2 0.1.2(包括)到2.0.0(不包括)版本 ~\u0026gt; 0.1.3-beta.0 beta的0.1.3版本, 和release的0.1.3(包括)到2.0.0(不包括)版本 版本语义说明(有兴趣可以了解下)\nSemantic Versioning: https://semver.org/lang/zh-CN/ RubyGems Versioning Policies: https://guides.rubygems.org/patterns/#semantic-versioning\n参考链接 官网: https://cocoapods.org ","date":"2021-04-20T00:00:00Z","permalink":"/p/2021-cocoapods%E6%96%B0%E6%89%8B%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97/","title":"2021 Cocoapods新手入门指南"},{"content":" 不涉及逆向相关内容, 仅做分析功能\n仅作为技术研究, 如有侵权, 请及时联系删除\n基本结构 首页大概的样子\n广告栏 分类 各种活动\u0026hellip; 分类 + 瀑布流列表 看到这样的首页, 首先想的是用CollectionView实现, 实现起来也挺困难的, 多个section, 布局也各不同。\n但是这个首页用的是TableView实现, 单个section, 各种不一样布局的Cell。\n总体操作流畅, 高速滑动有一点点掉帧(底下的瀑布流列表有圆角离屏渲染)。这么复杂的布局, 能有这样的体验, 挺不错的。\n基本实现分析 TableView的数据管理抽离了DataSource类, 实现UITableViewDataSource和UITableViewDelegate, 还负责加载和管理数据(或许还有其它功能, 暂时没有太关注)\n网络数据到cell的过程\nDataSource获取网络数据 parser把json解析为cell item, 并计算cell需要的size DataSource把cell item通过cell factory生成不同的cell。 内置多种cell, 可以根据数据动态切换布局\ncell不使用AutoLayout, 全部使用frame布局, 可以为滑动流畅提供一定的保障。\n下部分商品瀑布流列表 大家可能已经发现一个问题, TableView怎么实现最下面的瀑布流列表？？？\n下部分是TableView的FootView里面放了一个CollectionView + 瀑布流Layout实现瀑布流商品列表。\n里面做了滑动手势冲突处理, 连续滑动不间断。\n","date":"2020-05-23T00:00:00Z","permalink":"/p/%E6%9F%90%E6%9F%90%E7%94%B5%E5%95%86%E5%95%86%E5%9F%8E%E9%80%86%E5%90%91%E5%88%86%E6%9E%90%E4%B9%8B%E4%B8%80-%E9%A6%96%E9%A1%B5%E5%9F%BA%E6%9C%AC%E5%B8%83%E5%B1%80%E7%BB%93%E6%9E%84/","title":"某某电商商城逆向分析之一, 首页基本布局结构"},{"content":" 不涉及逆向相关内容, 仅做分析功能\n仅作为技术研究, 如有侵权, 请及时联系删除\n电商类应用列表布局、页面跳转复杂, 很难写push/present vc, 一般是通过类似web的route那样, 定义一套route参数来统一处理页面跳转。\n开始分析 这里主要分析某某电商商城首页列表的点击页面跳转\n上篇文章提到parser把json解析为cell item, 其中cell item还包含用于页面跳转的action数据。\naction数据因各个app的业务不一而定义, 比如login(是否需要登录)、modal(是否使用模态视图)、path(跳转路径)、className(vc类名)、extraDict(额外的数据)... 列表上的item被点击之后, 调用delete的方法, 传递cell item(包含action数据), 最终到vc进行处理。\naction handler统一管理了页面跳转, 也是个log打点的好地方 action handler根据vc传递action数据处理各种页面跳转情况, 如push/present vc、scheme调用、打开网页(内部、外部)\u0026hellip;\n","date":"2020-05-23T00:00:00Z","permalink":"/p/%E6%9F%90%E6%9F%90%E7%94%B5%E5%95%86%E5%95%86%E5%9F%8E%E9%80%86%E5%90%91%E5%88%86%E6%9E%90%E4%B9%8B%E4%BA%8C-%E9%A6%96%E9%A1%B5%E5%88%97%E8%A1%A8%E7%82%B9%E5%87%BB%E7%9A%84%E9%A1%B5%E9%9D%A2%E8%B7%B3%E8%BD%AC%E7%AE%A1%E7%90%86%E7%AE%80%E5%8D%95%E5%88%86%E6%9E%90/","title":"某某电商商城逆向分析之二, 首页列表点击的页面跳转管理简单分析"},{"content":"SwiftUI小实践 - 重写之前的Authenticator 对于之前做的一个TOTP两步验证器, 之前决定用SwiftUI重写一遍。后来工作忙, 一直拖到了过年时才完成。\n地址: https://github.com/skytoup/Authenticator, 只为了把功能做出来, 还没怎么整理代码\n已上架AppStore, 可直接下载体验: itms-apps://itunes.apple.com/app/id1509275023\n功能 增、删、改、查、排序(double作为score)验证码 相册、相机扫描二维码导入 二维码导出 watchOS同步数据, 生成动态码 数据同步到个人iCloud(仅AppStore版本有, 因为需要开发者计划才能开启此功能) 小经验 基本视图布局 对于List的Item添加onTapGesture, 点击空白地方无法触发, 使用.contentShape(Rectangle())可解决 单独隐藏List的分割线ListSeparatorNoneViewModifier.swift 定时刷新生成的验证码 MyTimer.swift 使用Timer.publish发现很多奇奇怪怪的问题 比如暂停和恢复, 实现出来的之后发现恢复会失败, 最后还是自定义一个Timer 配合CoreData刷新数据 CodeList.swift 输入关键字查找功能, 使用FetchRequest, 放在View的属性里面, 在View初始化时根据关键字生成FetchRequest UIViewController混用 LBXScan.swift, ImagePicker.swift 还是比较简单, 新建一个Struct, 实现UIViewControllerRepresentable即可 watchOS Watch Extension/CodeView.swift 对于watchOS实在好, Storeboard很对UI都没法实现, 用了SwiftUI发现UI的可定制型更高了 实践的经验不算多, 希望对大家有帮助😄\n","date":"2020-03-27T00:00:00Z","permalink":"/p/swiftui%E5%B0%8F%E5%AE%9E%E8%B7%B5/","title":"SwiftUI小实践"},{"content":" 为啥不叫SimpleStore??? 因为Cocoapods已有同名, 临时在后面加个Data😭, 实际使用还是SimpleStore 下文SimpleStore和SimpleStoreData指的为同一样东西\n两三年前, 公司需要Swift开发App, 从那时刚开始接触学习, 发现struct的一些特性可以结合UserDefault简化一些业务数据存储和读写, 于是就写出了SimpleStore的雏形.\n随着Swift 5.1的发布, 发布了一个PropertyWrapper的新特性, 尝试把这个特性加到SimpleStore的雏形里面, 发现使用起来还是更方便. 顺便还升级了功能, 一个StoreItem由原本只支持单一类型存储, 改为支持多类型.\n最近对SimpleStore的雏形进行了一番思考, 对其再次升级, 最后整理开源了SimpleStore.\n设计思路 核心 SimpleStoreItem修改后, 触发SimpleStore进行对数据存储 基本结构 SimpleStore: 管理和存储SimpleStoreItem SimpleStoreItem: 存放数据 Mapper: 辅助数据间转换(非必须) 扩展性 可实现SimpleStore协议可配合SimpleStoreItem扩展更多存储方式(目前只有UserDefault) SimpleStoreDictItem实现了SimpleStoreItem协议, 通过Mapper提供Item和dict的转换, 为中间转存提供方便, 易于扩展更多存储方式 用法 场景: 存储登录用户的数据(存储到UserDefault)\n定义一个用户性别的枚举, 因为UserDefault不支持直接存储enum, 所以需要实现UDCoding\nUserDefault支持的存储类型:\nString Int Float Double Bool Data Date URL enum Gender: Int { case unknown = 0 case male case female } /// 对于`UserDefault`不支持的存储类型提供数据转换 extension Gender: UDCoding { func toUDAny() -\u0026gt; Any { rawValue } static func fromUDAny(_ any: Any?) -\u0026gt; Gender? { guard let any = any as? Int else { return nil } return Gender(rawValue: any) } } 定义存储用户数据的字段(重点提示: SimpleStoreItem必须使用struct实现), 字段使用UDMapperParam属性包装定义, 再实现mapValue方法进行数据映射\n/// **重点提示: SimpleStoreItem必须使用struct实现** struct UserStoreItem: SimpleStoreUDItem { @UDMapperParam(key: \u0026#34;name\u0026#34;, default: \u0026#34;\u0026#34;) var name @UDMapperParam(key: \u0026#34;age\u0026#34;, default: 0) var age @UDMapperParam(key: \u0026#34;gender\u0026#34;, default: Gender.unknown) var gender mutating func mapValue\u0026lt;Mapper\u0026gt;(_ mapper: inout Mapper) where Mapper : DictMapper, UserStoreItem.Key == Mapper.Key, UserStoreItem.Value == Mapper.Value { mapper \u0026lt;- _name mapper \u0026lt;- _age mapper \u0026lt;- _gender } } 定义管理StoreItem的管理类(此处简单使用单例, 可根据自己的业务进行调整, manager也可定义更多业务方法)\nclass UserManager: SimpleStoreUD\u0026lt;UserStoreItem\u0026gt; { static let shared = UserManager(udKey: \u0026#34;__User__\u0026#34;) // 这里可以添加一些业务方法 } 使用演示, 修改item数据即可存自动储, 无需其它调用方法\nvar userM = UserManager.shared // 清空之前的数据 userM.ud.set(nil, forKey: userM.udKey) // 提示: 一次性更新多个字段最好是使用方法`batchUpdate` userM[\\.name] = \u0026#34;sky\u0026#34; print(\u0026#34;第一次修改 \\(userM.ud.dictionary(forKey: userM.udKey) ?? [:])\u0026#34;) // 第一次修改 [\u0026#34;gender\u0026#34;: 0, \u0026#34;age\u0026#34;: 0, \u0026#34;name\u0026#34;: sky] userM[\\.age] = 18 print(\u0026#34;第二次修改 \\(userM.ud.dictionary(forKey: userM.udKey) ?? [:])\u0026#34;) // 第二次修改 [\u0026#34;name\u0026#34;: sky, \u0026#34;age\u0026#34;: 18, \u0026#34;gender\u0026#34;: 0] userM[\\.gender] = .male print(\u0026#34;第三次修改 \\(userM.ud.dictionary(forKey: userM.udKey) ?? [:])\u0026#34;) // 第三次修改 [\u0026#34;age\u0026#34;: 18, \u0026#34;name\u0026#34;: sky, \u0026#34;gender\u0026#34;: 1] // 批量修改字段再存储 userM.batchUpdate { item in item.name = \u0026#34;yks\u0026#34; item.age = 81 item.gender = .female } print(\u0026#34;batchUpdate \\(userM.ud.dictionary(forKey: userM.udKey) ?? [:])\u0026#34;) // batchUpdate [\u0026#34;age\u0026#34;: 81, \u0026#34;name\u0026#34;: yks, \u0026#34;gender\u0026#34;: 2] // 相当于重置数据 userM.item = UserStoreItem() print(\u0026#34;重置数据 \\(userM.ud.dictionary(forKey: userM.udKey) ?? [:])\u0026#34;) // 重置数据 [\u0026#34;age\u0026#34;: 0, \u0026#34;name\u0026#34;: , \u0026#34;gender\u0026#34;: 0] 详情 详情请参考github地址: https://github.com/skytoup/SimpleStoreData\n","date":"2020-03-18T00:00:00Z","permalink":"/p/%E4%BE%BF%E6%8D%B7%E5%AD%98%E5%82%A8%E7%AE%80%E5%8D%95%E6%95%B0%E6%8D%AEsimplestoredata/","title":"便捷存储简单数据SimpleStoreData"},{"content":"题目链接: https://www.codewars.com/kata/52423db9add6f6fc39000354/c\n题目大概翻译(99.9%机翻😅): 给你一个二维数组 和 经过多少代, 模拟计算Conway\u0026rsquo;s_Game_of_Life的n个时间点\n游戏规则:\n任何具有少于两个活邻居的活细胞都会死亡，好像是由于人口不足造成的。 任何具有三个以上活邻居的活细胞都会死亡，就像人满为患一样。 任何有两个或三个活邻居的活细胞都可以存活到下一代。 具有正好三个活邻居的任何死单元将变为活单元。 每个单元的邻域是它周围的8个单元(即Moore邻域). 空间在x和y维度上都是无限的，并且所有单元格最初都是死的, 除了指定单元格为活的。返回值应该是围绕所有活细胞裁剪的二维数组。（如果没有活细胞，则返回[[]]。）\n出于说明目的，将0和1分别表示为░░和▓▓块（PHP，C：纯黑色和白色正方形）。您可以利用该htmlize功能来获取Universe的文本表示形式，例如：\nchar *universe = htmlize(cells, rows, columns); printf(universe); free(universe); 在C语言中，GoL空间的初始尺寸通过指针和的引用传递到函数中。在扩展/收缩GoL空间时，请通过这两个变量(rowptr, colptr)来跟踪修改后的网格的尺寸。\n题目解析 场景大概是有一个二维空间, 上面有一堆活或死的细胞, 每经过一代都根据游戏规则计算细胞的活/死 输入 一个二维数组(0, 1), 代表每个细胞的 活/死 状态 一个int, 代表经过多少代 C语言才有: 两个尺寸的指针, 代表输入的二维数组的大小, 也是输出的二维数组的大小 输出一个二维数组(0, 1), 代表每个细胞的 活/死 状态 其它注意的点 单元的邻域是指周围的8个单元格(上、下、左、右、左上、右上、左下、右下) 空间是无限的, 没有边界, 即输入的二维数组大小和输出的二维数组大小不一定相同(已踩坑, 以为在输入的二维数组大小做计算就好了, 谁知道是二维数组是可扩展的\u0026hellip;) 输出的空间是围绕着活细胞裁剪的二维数组, 即二维数组最外层四周至少有一个活细胞, 否则空间需要缩小 输入的二维数组的数据需要深拷贝 思路(苦逼的C语言解题\u0026hellip;) 一开始的想法 创建并初始化两个二维数组, 一个为现在这代的细胞状态(cur_cells), 另一个是下一代的细胞状态(next_cells) 遍历cur_cells, 根据游戏规则计算每个细胞的状态存到next_cells 最后cur_cells和next_cells指针互换, 重置next_cells数据为0 一番思考之后🤔 遍历起来的时候, 重复访问了不少单元格 可以把next_cells改为cal_cells, 作用是单元格周围有多少活细胞 (其实不用两个数组也可以, 一个数组足够存储数据, 可分高4低4位存储, 由于太懒了, 没去弄\u0026hellip;) 优化之后😢 遍历cur_cells, 如果是活细胞, 把对应的cal_cells周边8个单元格数字+1 遍历cal_cells, 根据游戏规则计算每个细胞的状态存到cur_cells 最后重置cal_cells 采坑\u0026hellip;😂 代码提交上去, 简单测试通过了, 但是随机测试一直无法通过 一番折腾之后, 发现边界是不限定的, 可无限扩张, 或者需要收缩 由于用的是C, 一开始考虑的是realloc+memmov方案, 后来发现随机测试的二维数组大小最大也就100 * 100这样子 最后干脆创建150*150的二维数组用来计算, 初始数据存在数组中央位置(计算偏移位置); 然后每次计算状态之后, 再计算扩展边界; n代过去之后, 再计算收缩的偏移位置, 输出到新的二维数组返回 ","date":"2020-01-24T00:00:00Z","permalink":"/p/codewars-kata-conways-game-of-life-unlimited-edition/","title":"CodeWars kata Conway's Game of Life - Unlimited Edition"},{"content":" 2020-02-06 添加Swift 5.1开始, ABI稳定特性打包兼容说明\n(Carthage暂不考虑\u0026hellip;, 略过)\n简(fei)介(hua) 随着越来越多方便的第三方组件, 现在开发App基本十多个第三方依赖以上. 没有build cache或者打正式包的时候, 每次基本耗时10分钟以上.\n主要原因是需要编译大量源文件(大概分为app和Cocoapods依赖库), 所以把可抽离代码编译好, 再去引用就能减少源文件编译时间\n至于有没有其它更高级玩法, 暂时还不太清楚\n想法(来自网络各种方法综合) 简单的平民解决方法: 建立私有二进制的repo\napp 抽离封装为一个库, 打包为二进制, 存到私有Cocoapods repo Cocoapods依赖 每个组件版本的源码打包为二进制, 存到私有Cocoapods repo 如果公司或者你有足够资源, 可以弄双私有源(两个私有repo, 一个二进制, 一个源码), 不然就简单点, 弄个私有二进制的repo也够用\n思路 由于没机器和资源, 只实践了后面的简(diao)单(si)版. 有条件最好是做一套自动化的, 我是没机器和资源, 打包还是各种脚本在自己机器上手动打包, 好惨\u0026hellip;\n简单版思路:\n打包第三方源码 每个tag打包二进制(只弄了打包framework, 不知道别人的static framework如何制作, 试了不成功) 私有repo 其实就是一个git仓库, 私有属性即可(虽然公有也行, 只是别人能看), 一般github, gitlab, gitee或者自建git创建一个仓库就可以了 二进制文件存放 打包出来的二进制文件, 需要找个地方提供下载, 有条件的话可以存CDN管理, 没的话在内网放个静态文件服务器存也行, 还有可以存git(需要有http(s)下载地址)\u0026hellip; 实践 需要的一些辅助工具\nhttps://github.com/tripleCC/cocoapods-bin https://github.com/tripleCC/binary-server 步骤\n打包第三方源码 git clone各种组件源码库 脚本打包 tag拉分支, 并切换(打包之后删除, 再回去master) 使用pod gen根据podsec创建工程 xcodebuild打包framework(真机和模拟器) 合并真机和模拟器的framework 看上去是这么简单, 实践的时候会发现有些库是不能通用一个脚本的 上传framework(用了binary-server管理) 二进制的podsec上传到私有的repo 使用pod bin spec create创建二进制版本的podsec 有subsec的会要求提供template 某些平台要求版本会低于8, 因为是使用framework, 需要平台版本需要改为8以上 上传生成的podsec 使用 在Podfile加上source {你的私有repo}, 然后再加一个官方的repo(例如: source 'https://cdn.cocoapods.org/'). 官方的一定要加在后面, 有相同的库会根据source的顺序获取依赖. 想切回源码注释私有库那一行就好了(因为只是简单的一个私有二进制repo) 某些坑 不知道会有啥影响, AppStore的打包还是老老实实的用源码build modulemap记得要把真机和模拟器的合一起 真机和模拟器的DSYM文件保留(万一哪一天用上) 有些第三方库是*.podsec, 有些是*.podsec.json pod bin spec create创建的二进制podsec文件名是带加了binary, 需要去除, 不然和podsec的name和文件名对不上, 无法通过pod spec lint pod repo push上传私有spec的时候, 记得加--use-json --allow-warnings 或许还有其它\u0026hellip; 其它 Swift 5.1开始, ABI已稳定, 可打包兼容多个不同swift版本打包可相互使用, 不需要重新打包framework 开启地方位于项目的build setting - Build Option - Build Libraries for Distribution改为YES framework的Modules里面会多生成一个类似源码的interface文件 相关资料 https://developer.apple.com/videos/play/wwdc2019/402/ ","date":"2020-01-14T00:00:00Z","permalink":"/p/cocoapod%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%8C%96%E5%AE%9E%E8%B7%B5/","title":"Cocoapod二进制化实践"},{"content":"前几天macOS 10.15正式版推出, 于是把电脑升级了, 顺便体验下SwiftUI\n经过一番基础学习, 计划把之前的项目(https://github.com/skytoup/Authenticator)尝试使用SwiftUI实现, 发现List的坑挺多的\nList相对于TableView提供的功能有点少, 还不太习惯, 而且问题也挺多的 配合CoreData使用是很爽, 但是发现编辑模式时, 选中功能不正常了, 勾选row和selection的数据无法匹配, 得自行实现选中逻辑和UI才能解决 row左/右滑动菜单系统没有提供, TableView是提供了代理方法可简单实现该功能, 有人推荐使用ContextMenu(用起来也方便)替代 延伸另一个问题, ContextMenu的按钮文字、图标按钮没法改颜色 拖动row到上一行时, 把数据源交换之后, 动画很奇怪(拖动的row回到原位, 然后动画显示拖动的row到拖动目标的位置), 其它拖栋没发现问题(向下一/多行, 向上多行) 暂时也只是发现这么多\nSwiftUI使用起来也是很舒服, 体验类似在写前端vue, 数据更新自动修改view, 省了很多更新代码, 还能实时预览和操作\n~~ 东西还没完成, 继续研究补坑去\u0026hellip; ~~ 已用SwiftUI改写了所有界面: https://github.com/skytoup/Authenticator\n","date":"2019-10-17T00:00:00Z","permalink":"/p/swiftui%E5%88%9D%E6%AC%A1%E4%BD%93%E9%AA%8C/","title":"SwiftUI初次体验"},{"content":" GoogleAuthenticator TOTP部分实现的验证器(没有实现HOTP)\n已上架AppStore, 可直接下载体验: itms-apps://itunes.apple.com/app/id1509275023\n由于GoogleAuthenticator没有导出功能, 而且在watch上也没有查看功能, 干脆自己实现一个\n纯Swift5.x实现, 操作体验良好, 界面极简\n另外增加相册二维码导入、二维码导出、watchOS同步查看\n详情请到: https://github.com/skytoup/Authenticator\n部分截图 ","date":"2019-10-11T00:00:00Z","permalink":"/p/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAgoogleauthenticator/","title":"实现一个GoogleAuthenticator"},{"content":"GoogleAuthenticator 使用于二次验证的动态密码生成工具, 一般用于登录验证、替代一些短信验证等\u0026hellip;\n其中动态密码生成算法为HOTP(HMAC-Based One-Time Passcode)和TOTP(Time-based One-Time Passcode)\nTOTP和HOTP均基于OTP(One-Time Passcode)\nOTP 一次性密码生成基本公式: OTP(K, C) = Truncate(EncryptionAlgorithm(K, C))\nK 秘钥 C 一个8 byte数据 Trucate 截取Hash数据组成验证码的函数 EncryptionAlgorithm 加密算法(对称加密算法, HASH, HMAC) HOTP和TOTP 一次性密码生成公式变更为:\n# HOTP HOTP(K, C) = Truncate(HMAC-based(K, C)) # TOTP TOTP(K, T) = Truncate(HMAC-based(K, T)) T = (CurrentUnixTime − T0) / X HOTP参数变更:\nC 计数器 TOTP参数变更:\nT 时间点 CurrentUnixTime 当前Unix时间戳 T0 初始时间戳 X 时间步长(秒), 相当于一次性密码变更, 默认为30 公共参数:\nHMAC-based 使用HMAC算法进行Hash HMAC-SHA1 HMAC-SHA256 HMAC-SHA512 Truncate截取算法: 取HMAC结果(20 byte)的最后一个字符的低4位offset num offset num % 16 计算出字符截取的初始位置offset HMAC结果的第offset开始, 取4个byte组成一个32位int Digits参数 其中算法还有一个参数Digits, 代表验证码的位数\n默认为6, 或者为8\n秘钥URL GoogleAuthenticator添加秘钥时, 可扫描二维码添加, 其中二维码为一条链接(otpauth://TYPE/LABEL?PARAMETERS)\n例如: otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP\u0026amp;issuer=Example\n参数说明:\nTYPE 秘钥计算的类型, hotp或totp Label 用于标识账号, 包括了账号名和标识内容 label = accountname / issuer (“:” / “%3A”) *”%20” accountname PARAMETERS(URI query参数) Secret 秘钥, base32编码 Issuer 谁提供者此秘钥, 就是标识属于那个网站的验证 Algorithm(可选) HMAC使用哪种HASH算法 SHA1(默认) SHA256 SHA512 Digits(可选) 验证码位数 6(默认) 8 Counter(当type为hotp时为必需) 计算器初始值 Period(当type为totp时为才需要, 可选) 时间步长(秒) 30(默认) 对于GoogleAuthenticator, 很多参数会忽略掉, 均为其默认值\nAlgorithm(SHA1) Digits(6) Period(30) 在实现HOTP时遇到的一些问题(应该比较多人遇到吧) 计算出的时间点需要转为8 byte数据 输入的秘钥需要Base32解码之后, 才能进行作为秘钥传入HMAC Truncate函数最后取的4 byte数据, 需要转为32位的int 参考 HOTP: An HMAC-Based One-Time Password Algorithm(rfc4226): https://tools.ietf.org/html/rfc4226 Key Uri Format: https://github.com/google/google-authenticator/wiki/Key-Uri-Format HOTP和TOTP算法图解: https://www.jianshu.com/p/a7b900e8e50a ","date":"2019-10-08T00:00:00Z","permalink":"/p/googleauthenticator%E7%9A%84%E4%B8%80%E6%AC%A1%E6%80%A7%E5%AF%86%E7%A0%81%E7%94%9F%E6%88%90%E7%AE%97%E6%B3%95/","title":"GoogleAuthenticator的一次性密码生成算法"},{"content":"iOS 13支持多窗口, 引入了Screen相关API。App的AppDelegate部分已经改变, 想把Storeboard删掉, 增加UIWindow已经不是在AppDelegate里面修改。\n整理出以下步骤(Xcode11 Swift5.1)\n删除Main.storyboard文件 Info.plist删除 Application Scene Manifest - Scene Configuration - Application Session Role - 0 - Storyboard Name 在ScreenDelegate添加window guard let scene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: scene) self.window?.backgroundColor = .white let rootVC = UIViewController() let winRootVC = UINavigationController(rootViewController: rootVC) self.window?.rootViewController = winRootVC self.window?.makeKeyAndVisible() ","date":"2019-10-06T00:00:00Z","permalink":"/p/xcode11%E5%88%A0%E9%99%A4storyboard/","title":"Xcode11删除Storyboard"},{"content":"collections.Counter 简介\nCounter是dict的子类, 用于可hash的对象计数 key按dict的key排序, 计数的值按dict的value排序 计数的值为整数, 可为正负数和0 示例\nfrom collections import Counter # 初始化, 可传 [None, Iterable, Mapping] counter = Counter() # None counter = Counter(\u0026#39;12345\u0026#39;) # Iterable, 统计每个元素频率 counter = Counter({\u0026#39;a\u0026#39;: 1, \u0026#39;b\u0026#39;: 2}) # Mapping counter = Counter(a=1, b=2) # Mapping # 没有赋值过的元素不会触发KeyError counter[\u0026#39;c\u0026#39;] # 0 counter[\u0026#39;c\u0026#39;] += 1 # 也能移除元素(真实移除), 然后计数归0 counter[\u0026#39;c\u0026#39;] = 100 del counter[\u0026#39;c\u0026#39;] counter[\u0026#39;c\u0026#39;] # 0 # 获取计数最大的元素排序list c = Counter(\u0026#39;1122234444449999999999\u0026#39;) c.most_common() # [(\u0026#39;9\u0026#39;, 10), (\u0026#39;4\u0026#39;, 6), (\u0026#39;2\u0026#39;, 3), (\u0026#39;1\u0026#39;, 2), (\u0026#39;3\u0026#39;, 1)] # 获取前n个 c.most_common(3) # [(\u0026#39;9\u0026#39;, 10), (\u0026#39;4\u0026#39;, 6), (\u0026#39;2\u0026#39;, 3)] # 比较特殊的操作 +c # 去除计数为0和负数的元素 -c # 去除计数为0和正数的元素, 并把负数的元素取绝对值 collections.defaultdict 简介\ndict的子类 提供默认值构造器传到内部, 可为不存在的key设置默认值 默认值构造器为一个无参可调用的对象, 返回默认的数据 示例\nfrom collections import defaultdict df_dict = defaultdict(list) df_dict[\u0026#39;a\u0026#39;].append(1) df_dict[\u0026#39;a\u0026#39;].append(2) df_dict[\u0026#39;b\u0026#39;].append(3) df_dict[\u0026#39;b\u0026#39;].append(4) # {\u0026#39;a\u0026#39;: [1, 2], \u0026#39;b\u0026#39;: [3, 4]} df_dict = defaultdict(lambda: list()) # 使用lambda ","date":"2019-07-21T00:00:00Z","permalink":"/p/python3%E5%AE%98%E6%96%B9%E5%BA%93collections%E6%AF%94%E8%BE%83%E6%9C%89%E7%94%A8%E7%9A%84%E9%83%A8%E5%88%86/","title":"Python3官方库collections比较有用的部分"},{"content":"python使用request-html时, 发现pyppeteer报了几个错误, 然后越来越多chrome的进程\n查了下原因, 原来pyppeteer只是下载了chromium, 但是没有安装需要的依赖\n安装补上即可\nyum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y ","date":"2018-08-31T00:00:00Z","permalink":"/p/centos7%E5%AE%89%E8%A3%85puppeteer%E4%BE%9D%E8%B5%96/","title":"centos7安装puppeteer依赖"},{"content":"pip install pycurl后, 使用pycurl时, 出现了些错误:\nPycURL: ImportError: pycurl: libcurl link-time ssl backend (nss) is different from compile-time ssl 解决方法:\npip uninstall pycurl export PYCURL_SSL_LIBRARY=nss # 错误提示nss, 所有使用nss, 所有选项 openssl, gnutls, nss pip install pycurl ","date":"2018-08-31T00:00:00Z","permalink":"/p/pip-install-pycurl-%E5%B0%8F%E9%97%AE%E9%A2%98/","title":"pip install pycurl 小问题"},{"content":"Homebrew是MacOSX上的软件包管理工具, 可以方便安装各种Apple没有预装的东西。安装软件时, 还会自动安装依赖, 但是卸载时, 依赖不会被卸载。(本人有洁癖, 喜欢把没用的东西都清理掉)\n获取安装列表 Homebrew有提供命令\nbrew deps --installed 然后会出现一堆软件及其依赖, 例如:\nautoconf: automake: autoconf carthage: cmake: python@2: gdbm openssl readline sqlite readline: sqlite: readline tree: usbmuxd: libplist libusb watchman: gdbm openssl pcre python@2 readline sqlite ... 数量少的话, 人工分析也是可以的, 然而平时乱装东西(安装是在方便)\u0026hellip;\n分析(python3) 执行命令, 获取依赖列表\n# 代码均为片段, 完整代码请到下面的源码地址 with os.popen(\u0026#39;brew deps --installed\u0026#39;) as popen: dep_lines = popen.read().split(\u0026#39;\\n\u0026#39;) 解析列表\n# 代码均为片段, 完整代码请到下面的源码地址 class BrewPackage: def __init__(self, name: str): self.name = name self.deps = [] self.is_dep = False def __repr__(self) -\u0026gt; str: return \u0026#39;{}: {}\u0026#39;.format(self.name, self.deps) for line in dep_lines: if not line: continue result = line.split(\u0026#39;: \u0026#39;) name = result[0] deps_name = result[1].split(\u0026#39; \u0026#39;) if len(result) == 2 and result[1] else None package = BrewPackage(name) if not deps_name: continue for dep_name in deps_name: dep_package = BrewPackage(name) dep_package.id_dep = True package.deps.append(dep_package) 数据整理\n# 代码均为片段, 完整代码请到下面的源码地址 # 上面第二步, 把BrewPackage缓存起来(每一个name只存在一个BrewPackage), 然后数据就会变成一张单向图结构. all_package_dict = {} # name -\u0026gt; package def get_cache_package(package_name: str) -\u0026gt; BrewPackage: pkg = all_package_dict.get(package_name) if not pkg: pkg = BrewPackage(package_name) all_package_dict[package_name] = pkg return pkg # 把第二部创建BrewPackage的部分, 换成get_cache_package即可 输出结果\n# 代码均为片段, 完整代码请到下面的源码地址 # 为了更好看的输出结果, BrewPackage重写__repr__方法 @classmethod def _deps_str(cls, package, level: int = 0, need_deep: bool = True): ts = \u0026#39;\\t\u0026#39; * (level + 1) s = \u0026#39;\u0026#39; for p in package.deps: s += \u0026#39;{}- {}\\n\u0026#39;.format(ts, p.name) if need_deep and p.deps: s += cls._deps_str(p, level + 1) return s def __repr__(self): return \u0026#39;{}:\\n{}\u0026#39;.format(self.name, BrewPackage._deps_str(self)) ------------------------------------------------------------------------- # 输出所有软件包 print(\u0026#39;All package and deps\u0026#39;) print(\u0026#39;--------------------\u0026#39;) for package in all_package_dict: print(package) print(\u0026#39;\\n\u0026#39;) # 这里print出来的包, 都没有被依赖, 看看那个不认识或不想要都可以卸载掉, 卸载完一遍后, 需要再次运行该程序分析 # 如此循环, 直到没有找到需要卸载的包, 这样就清理干净了 print(\u0026#39;Not dependent packages\u0026#39;) print(\u0026#39;----------------------\u0026#39;) not_dep_package = [p for p in all_package_dict.values() if not p.is_dep] for p in not_dep_package: print(p.name) 完整地址:\ngithub gitee ","date":"2018-06-27T00:00:00Z","permalink":"/p/python3%E5%88%86%E6%9E%90homebrew-%E6%B8%85%E7%90%86%E6%97%A0%E7%94%A8%E8%BD%AF%E4%BB%B6/","title":"python3分析Homebrew \u0026 清理无用软件"},{"content":"安装 第一步, 安装rustup 打开终端, 输入命令:\ncurl https://sh.rustup.rs -sSf | sh Tip:\nbrew install rust感觉有点问题, 还是用官方推荐的命令比较好. 安装有点慢, 第一步下载setup-init时, 可以给curl设置代理; 到了第二步, setup-init会使用内部的curl下载, 暂时没有找到设置代理的方法. 第二步, 安装rust及套件(docs, std, cargo, rustc) 终端继续输入命令:\nrustup install nightly rustup default nightly 这样就算安装完成了.\nTIP:\n更新rust命令: rustup update 更新rustup命令: rustup self update 简略说明:\nrustc: rust编译器, rs文件编译成二进制可执行文件 rust-docs: rust的文档 rust-std: rust的标准库 cargo: rust项目构建工具 基本使用 rustc(一般都不用) rustc main.rs ./main cargo 创建项目\ncargo new \u0026lt;project_path\u0026gt; [--bin|lib] [--name] --bin是构建二进制可执行文件工程(默认), --lib是构建库 --name指定工程名, 默认为工程目录名 项目结构\nsrc(源码目录) - main.rs Cargo.toml(项目配置文件) 构建工程(终端当前路径为项目根目录)\ncargo build # 构建工程 cargo run # 运行工程, 一般用这个就好, 源码修改后, 会自动构建 Hello World main.rs\nfn main() { println!(\u0026quot;Hello World\u0026quot;); } fn定义函数 println!是一个宏, 打印字符串输出 参考 https://www.rust-lang.org/zh-CN/install.html https://github.com/rust-lang-nursery/rustup.rs/blob/master/README.md ","date":"2018-06-24T00:00:00Z","permalink":"/p/maxosx%E5%AE%89%E8%A3%85rust-%E5%8F%8A-%E5%85%A5%E9%97%A8/","title":"MaxOSX安装Rust 及 入门"},{"content":"概(fei)述(hua) \u0026hellip;(省略1024亿个byte)\n探究 为了研究Objective-C的消息转发，做了一系列测试\nTest 0 内容: 调用对象存的方法\n步骤:\n定义一个TestObject类，继承NSObject, 重写的方法如下:\n@implementation TestObject + (BOOL)resolveClassMethod:(SEL)sel { BOOL ret = [super resolveClassMethod:sel]; NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p resolveClassMethod: %@, return: %d\u0026#34;, [self class], self, NSStringFromSelector(sel), ret); return ret; } - (BOOL)respondsToSelector:(SEL)aSelector { BOOL ret = [super respondsToSelector:aSelector]; NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p respondsToSelector: %@, return: %d\u0026#34;, [self class], self, NSStringFromSelector(aSelector), ret); return ret; } - (id)forwardingTargetForSelector:(SEL)aSelector { id ret = [super forwardingTargetForSelector:aSelector]; NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p forwardingTargetForSelector: %@, return: %@\u0026#34;, [self class], self, NSStringFromSelector(aSelector), ret); return ret; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature* ret = [super methodSignatureForSelector:aSelector]; NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p methodSignatureForSelector: %@, return: %@\u0026#34;, [self class], self, NSStringFromSelector(aSelector), ret); return ret; } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p forwardInvocation: %@\u0026#34;, [self class], self, anInvocation); [super forwardInvocation:anInvocation]; } @end 2. 代码 ```objc [[TestObject alloc] init]; ``` 结果: 什么都没有log\n结论: 调用存在的方法, 没有发生任何消息转发\nTest 1 内容: 调用对象不存在的方法\n步骤:\n代码\nTestObject* obj = [[TestObject alloc] init]; [obj performSelector:NSSelectorFromString(@\u0026#34;not exists sel\u0026#34;)]; 结果: TestObject调用了forwardingTargetForSelector:(返回nil), 然后调用methodSignatureForSelector:(返回nil), 最后log unrecognized selector sent to instance\n结论: 调用不存在的方法, 发生了消息转发\nTest 2 内容: TestObject_1重写forwardingTargetForSelector:(返回TestObject对象), 调用它们都不存在的方法\n步骤:\n定义TestObject_1, 继承TestObject @implementation TestObject_1 - (id)forwardingTargetForSelector:(SEL)aSelector { id ret = [[TestObject alloc] init]; NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p forwardingTargetForSelector: %@, return: %@\u0026#34;, [self class], self, NSStringFromSelector(aSelector), ret); return ret; } @end 代码: TestObject_2* obj = [[TestObject_2 alloc] init]; [obj performSelector:NSSelectorFromString(@\u0026#34;not exists sel\u0026#34;)]; 结果: TestObject_1对象先调用了forwardingTargetForSelector:(返回TestObject对象), 然后TestObject对象调用forwardingTargetForSelector:(返回nil), 跟着调用methodSignatureForSelector:(返回nil), 最后log unrecognized selector sent to instance\n结论: 不存在的方法转发到了其它对象处理失败\nTest 3 内容: TestObject_2重写forwardingTargetForSelector:(返回TestObject_1), 调用TestObject_2不存在, 但TestObject_1存在的方法\n步骤:\n定义TestObject_2, 继承TestObject @implementation TestObject_2 - (id)forwardingTargetForSelector:(SEL)aSelector { id ret = [[TestObject_1 alloc] init]; NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p forwardingTargetForSelector: %@, return: %@\u0026#34;, [self class], self, NSStringFromSelector(aSelector), ret); return ret; } @end TestObject_1, 添加方法 - (void)testObject_1ExistsMethod { NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p I am exists\u0026#34;, [self class], self); } ```\n3. 代码 ```objc TestObject_2* obj = [[TestObject_2 alloc] init]; [obj performSelector:NSSelectorFromString(@\u0026ldquo;testObject_1ExistsMethod\u0026rdquo;)]; ```\n结果: TestObject_2对象先调用了forwardingTargetForSelector:(返回TestObject_1对象), 然后TestObject_1对象调用了testObject_1ExistsMethod 结论: 不存在的方法转发到了其它对象处理成功 Test 4 内容: TestObject_3重写methodSignatureForSelector:(不返回nil)和forwardInvocation:, 调用不存在的方法\n步骤:\n定义TestObject_3, 继承TestObject @implementation TestObject_3 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p methodSignatureForSelector: %@\u0026#34;, [self class], self, NSStringFromSelector(aSelector)); return [NSObject methodSignatureForSelector:@selector(init)]; } - (void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@\u0026#34;--\u0026gt;\u0026gt; %@ %p forwardInvocation: %@\u0026#34;, [self class], self, anInvocation); // [super forwardInvocation:anInvocation]; // will crash, because the method signature is not right } @end 代码 TestObject_3* obj = [[TestObject_3 alloc] init]; [obj performSelector:NSSelectorFromString(@\u0026#34;not exists sel\u0026#34;)]; 结果: 先调用了forwardingTargetForSelector:, 然后methodSignatureForSelector:, 最后forwardInvocation:, 没发生崩溃\n结论: TestObject_3自己处理了不存在方法\n总结 如下图 测试代码 TestMessageForwarding: https://github.com/skytoup/TestMessageForwarding\n","date":"2018-05-08T00:00:00Z","permalink":"/p/objective-c%E6%B6%88%E6%81%AF%E8%BD%AC%E5%8F%91%E6%8E%A2%E7%A9%B6/","title":"Objective-C消息转发探究"},{"content":"原文来自Apple官方文档:\nNSHashTable: https://developer.apple.com/documentation/foundation/nshashtable?language=objc NSMapTable: https://developer.apple.com/documentation/foundation/nsmaptable?language=objc NSPointerFunctions: https://developer.apple.com/documentation/foundation/nspointerfunctions?language=objc NSPointerFunctionsOptions: https://developer.apple.com/documentation/foundation/nspointerfunctionsoptions?language=objc NSHashTable 一个类似set的集合，但是它有更多的内存语义(*更自由定义集合元素的内存如何管理)。\n概述 NSHashTable和NSSet不同之处： - 它可以对它的元素保持弱引用。 - 它的元素可以在输入时拷贝副本 或 使用指针地址对比相同和哈希。 - 他可以存储任意指针(它的元素不限制是对象[*应该是指NSObject])\n你可以配置一个NSHashTable实例用于任意的指针，不限于是对象，显然你可以能够使用把C函数转为 void* 指针。它的基本API(如addObject:)将会不对非对象的指针进行类型转换。\n因为它的配置，NSHashTable不是一个Set，应为它们有不同之处(比如，如果配置了使用对比指针地址，则两个isEqual:的字符串都将被添加)\n当配置哈希表时，请注意只有在NSHashTableOptions列出的配置才能使API正常使用\u0026ndash;包括复制、归档和快速遍历。而其它NSPointerFunctions配置使用于某些配置，例如保持任意指针，单并非所有组合的配置都是有效的。对于谋学组合，哈希表可能无法正常工作，甚至无法正常初始化。\nNSMapTable 一个类似字典的集合，但是它有更多的内存语义(*更自由定义集合元素的内存如何管理)。\n概述 NSMapTable和NSDictionary不同之处： - Key/Value 是可以配置保持弱引用，当对象被回收时就会被移除。 - Key/Value可以在输入时拷贝副本 或 使用指针地址对比相同和哈希。 - 他可以存储任意指针(它的元素不限制是对象[*应该是指NSObject])\n你可以配置一个NSMapTable实例去操作任意指针，不限于对象，显然你可以能够使用把C函数转为 void* 指针。它的基本API(如addObject:forKey:)将会不对非对象的指针进行类型转换。\n当配置MapTable时，请注意只有在NSMapTableOptions列出的配置才能使API正常使用\u0026ndash;包括复制、归档和快速遍历。而其它NSPointerFunctions配置使用于某些配置，例如保持任意指针，单并非所有组合的配置都是有效的。对于谋学组合，哈希表可能无法正常工作，甚至无法正常初始化。\nNSPointerFunctions 一个NSPointerFunctions实例定义了调出函数用于管理如何保存指针。\n概述 NSPointerFunctions实例所指定的函数被分为两个簇——定义一些“特性”的函数，如“object”或“C-string”，以及描述内存管理问题的函数，如内存分配函数。对于常见的特性和内存管理器的选择有一些常量(参见内存和特性选项)。\nNSHashTable，NSMapTable 和 NSPointerArray使用一个NSPointerFunctions对象定义指针的获取和存储。但是请注意，并不是所有的特性和内存管理的组合对这些集合都有效。指针集合对象在输出输入时复制NSPointerFunctions对象，\t所以不能把NSPointerFunctions子类化。\nNSPointerFunctionsOptions 对NSPointerFunction对象定义内存和特性的配置\n概述 当指定一个值时，你只能使用内存选项的其中一个。\n配置选项 Memory Options(这些选项是互斥的) NSPointerFunctionsMachVirtualMemory 使用Mach内存 NSPointerFunctionsMallocMemory 删除使用free()，复制使用calloc() NSPointerFunctionsOpaqueMemory 当指针被删除时，不要采取任何操作 NSPointerFunctionsStrongMemory 使用强引用来存储，在复制时使用垃圾回收 NSPointerFunctionsWeakMemory 对ARC和GC的读写使用弱引用。使用NSPointerFunctionsWeakMemory对象引用被释放后将在将会返回NULL Personality Options(这些选项是互斥的) NSPointerFunctionsCStringPersonality 使用字符串的哈希 和 比对；C-string使用‘%s’ NSPointerFunctionsIntegerPersonality 使用未位移的值用于哈希和比对 NSPointerFunctionsObjectPersonality 使用哈希 和 isEqual方法对比是否相同，使用description方法显示详情 NSPointerFunctionsObjectPointerPersonality 使用移位指针进行哈希和直接对比相同，使用description方法显示详情 NSPointerFunctionsOpaquePersonality 使用移位指针进行哈希和直接对比相同 NSPointerFunctionsStructPersonality 使用内存的哈希 和 memcmp(使用sizeFunction). ","date":"2018-04-28T00:00:00Z","permalink":"/p/nshashtable%E5%92%8Cnsmaptable/","title":"NSHashTable和NSMapTable"},{"content":"安装 MacOS 先安装brewhome, 官网: brewhome(已安装的跳过) 打开终端, 输入命令brew install pypy3(没翻墙的可能比较慢) 安装pypy的pip, 在终端输入curl -O https://bootstrap.pypa.io/get-pip.py \u0026amp;\u0026amp; pypy3 get-pip.py CentOS 待测试\u0026hellip;\n基本使用 基本命令 pypy3: 运行脚本, 相当于python3 pypy3 xxx.py # 运行脚本xxx.py pypy3 -m pip install xxx # 安装依赖xxx pypy3 -m venv xxx # 创建一个虚拟环境到xxx目录 切换到该虚拟环境: source xxx/bin/activate, 退出虚拟环境: deactivate pip_pypy3: 安装依赖库, 相当于 pip3 pip_pypy3 install xxx # 安装依赖xxx pip_pypy3 install -r requirement.txt # 递归安装requirement.txt里面的依赖 easy_install_pypy3: 安装依赖库, 相当于easy_install3 easy_install_pypy3 xxx # 安装依赖xxx 测试性能 # test.py from time import time times = 10 start_time = time() for t in range(times): sum = 0 st = time() for i in range(10 ** 6): sum += i print(\u0026#39;{}: speed time: {}\u0026#39;.format(t, time()-st)) total_time = time()-start_time print(\u0026#39;speed time avrage: {}, total: {}\u0026#39;.format(total_time / times, total_time)) python3 test.py\n0: speed time: 0.16138482093811035 1: speed time: 0.1585860252380371 2: speed time: 0.1659379005432129 3: speed time: 0.1676487922668457 4: speed time: 0.16137290000915527 5: speed time: 0.16071820259094238 6: speed time: 0.16538310050964355 7: speed time: 0.1611499786376953 8: speed time: 0.17231488227844238 9: speed time: 0.17661476135253906 speed time avrage: 0.165160608291626, total: 1.6516060829162598 pypy3 test.py\n0: speed time: 0.005280017852783203 1: speed time: 0.003924131393432617 2: speed time: 0.002089977264404297 3: speed time: 0.0024471282958984375 4: speed time: 0.0020821094512939453 5: speed time: 0.0025098323822021484 6: speed time: 0.002827882766723633 7: speed time: 0.0020329952239990234 8: speed time: 0.002518892288208008 9: speed time: 0.001984119415283203 speed time avrage: 0.002937006950378418, total: 0.02937006950378418 简单的测试来看, pypy比cpython快了好几倍\n","date":"2017-07-28T00:00:00Z","permalink":"/p/pypy3%E5%AE%89%E8%A3%85%E5%92%8C%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/","title":"pypy3安装和基本使用"},{"content":"废话 平时都在使用fir, 但是公司网速有时候很蛋疼, 安装包体积一大, 就安装个10多分钟都搞不定。而且fir开始有点点收费了, 所以干脆自己做一个简单的工具。断断续续地做了一个月, 终于完成了一些基本功能。\n效果图 首页 上传App App详情页 App编辑页 基本思路 上传安装包, 然后区分apk和ipa安装包来进行解析, 获取各种包信息, 最后存到数据库 apk可以直接下载点击安装, ipa则需要一个plist文件来在线安装(详情请参考: http://help.apple.com/deployment/ios/#/apda0e3426d7) 省略各种增删改查\u0026hellip;\u0026hellip; 使用技术 服务端 使用python3.5以上的版本 选择了一个比较新的框架👉Sanic 数据库简单使用了sqlite3, ORM使用了sqlalchemy 源码传送门 👉 AppServer\n前端(基本没做过, 很简陋) 直接选用了React 看到dva这个React框架比较简单, 就选了这个 在dva哪里看到antd这个UI框架, 感觉还不错 源码传送门 👉 AppServerHTML\n喜欢的就给两个start吧😁 ","date":"2017-03-01T00:00:00Z","permalink":"/p/%E4%B8%80%E4%B8%AA%E5%AE%9E%E7%8E%B0app%E5%9C%A8%E7%BA%BF%E4%B8%8B%E8%BD%BD%E5%AE%89%E8%A3%85%E7%9A%84%E5%B7%A5%E5%85%B7/","title":"一个实现App在线下载、安装的工具"},{"content":"背景 Python安装第三方模块的时候, 是安装到系统全局的环境。当你的多个项目里面用到了同一个库, 但是版本却不一样, 这样就会产生冲突了。\npyvenv(还有一个比较好的非官方工具Virtualenv就不介绍了) pyvenv是Python3安装时自带的创建一个虚拟环境工具(tip: Python3.4版本前的pyven创建的虚拟环境不带pip)\n用这个工具就能创建出多个Python的虚拟环境, 把第三方模块安装到不同的虚拟环境, 就能让不同的项目使用不同的Python环境, 互相不会受到影响。\n基本使用(Mac、Linux, Win很久没用了) 创建虚拟环境命令:\npyvenv /path/to/new/virtual/environment 激活虚拟环境命令:\nsource /path/to/virtual/environment/bin/activate 退出虚拟环境命令:\ndeactivate 例子:\npyvenv py_1-evn # 当前目录创建一个虚拟环境叫py_1-evn source py_1-evn/bin/activate # 在本终端激活这个虚拟环境(没有激活虚拟环境时是使用全局的环境) pip install 。。。。 # 可以安装各种第三方模块, 都会安装到激活的虚拟环境 python xxx.py # 在激活的虚拟环境运行py脚本 deactivate # 退出激活的虚拟环境 更高级的使用(Python API) 请参考官方文档: https://docs.python.org/3/library/venv.html\n","date":"2016-08-28T00:00:00Z","permalink":"/p/python3%E7%9A%84%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83/","title":"Python3的虚拟环境"},{"content":"2016-09-11: 1.添加使用Python封装打包命令的开源库(最下面~) 2016-06-06: 1.经过一轮测试之后, 发现文章有点错漏, 小修改一下, 增加命令四 每次要出包的时候, 总要打开XCode, 然后点击Product-Archive, 等待好几分钟的各种build, 然后还要手动上传到AppStore, 甚至还要上传到蒲公英、fir等\u0026hellip;\n很久以前就看了很多关于iOS自动打包ipa的文章, 看着感觉很简单, 但是因为一直没有AppleDeveloper账号可以给我用, 到了真的要搞自动打包的时候, 才发现到处都是坑。\n基本命令 xcedebuild: 生成Archive、导出ipa, 还有其它功能\u0026hellip; xcrun: 把*.app打包成ipa, 还有其它功能\u0026hellip; 基本使用 一. xcedebuild打包Archive文件\nxcedebuild -workspace ${path to *.xcworkspace} -scheme ${scheme} -destination generic/platform=iOS archive -configuration Release ONLY_ACTIVE_ARCH=NO -archivePath ${export path *.xcarichive} -workspace 你的*.xcworkspace文件 -scheme 项目文件里面的scheme -archivePath 生成的*.xcarichive文件路径 二. xcedebuild从*.xcarchive导出ipa\nxcodebuild -exportArchive -exportFormat IPA -archivePath ${path to *.xcarchive} -exportPath ${export path *.ipa} -exportProvisioningProfile ${ProvisioningProfileName} -archivePath 你的*.xcarchive文件, 可以使用上一个命令导出 -exportPath 导出的ipa路径 exportProvisioningProfile 你的Distribution发布证书的名称(只需要名称) 三. xcrun打包ipa\nxcrun -sdk iphoneos PackageApplication -v ${path to *.app} -o ${package path *.ipa} -v 你的*.app文件, 生成的*.xcarchive里面有 -o 打包生成的*.ipa文件路径, 注意！！！这里是不能填相对路径, 因为这里的路径环境变量不是当前执行命令的路径了 四. 最新的正确xcodebuild导出ipa\nxcodebuild -exportArchive -archivePath ${path to *.xcarchive} -exportPath ${export path to dir} -exportOptionsPlist ${path to export options *.plist} -archivePath 你的*.xcarchive文件, 可以使用第二个命令导出 -exportPath 导出的ipa目录, ipa的名称好像是scheme的名称 -exportOptionsPlist 导出plist格式的配置文件 exportOptionPlist: 新建一个plist文件, 里面是一个Dictionary, key-value如下, 都是可选值, 不需要全部填上\ncompileBitcode: Bool For non-App Store exports, should Xcode re-compile the app from bitcode? Defaults to YES.\nembedOnDemandResourcesAssetPacksInBundle : Bool For non-App Store exports, if the app uses On Demand Resources and this is YES, asset packs are embedded in the app bundle so that the app can be tested without a server to host asset packs. Defaults to YES unless onDemandResourcesAssetPacksBaseURL is specified.\niCloudContainerEnvironment For non-App Store exports, if the app is using CloudKit, this configures the \u0026ldquo;com.apple.developer.icloud-container-environment\u0026rdquo; entitlement. Available options: Development and Production. Defaults to Development.\nmanifest : Dictionary For non-App Store exports, users can download your app over the web by opening your distribution manifest file in a web browser. To generate a distribution manifest, the value of this key should be a dictionary with three sub-keys: appURL, displayImageURL, fullSizeImageURL. The additional sub-key assetPackManifestURL is required when using on demand resources.\nmethod : String Describes how Xcode should export the archive. Available options: app-store, ad-hoc, package, enterprise, development, and developer-id. The list of options varies based on the type of archive. Defaults to development.\nonDemandResourcesAssetPacksBaseURL : String For non-App Store exports, if the app uses On Demand Resources and embedOnDemandResourcesAssetPacksInBundle isn\u0026rsquo;t YES, this should be a base URL specifying where asset packs are going to be hosted. This configures the app to download asset packs from the specified URL.\nteamID : String The Developer Portal team to use for this export. Defaults to the team used to build the archive.\nthinning : String For non-App Store exports, should Xcode thin the package for one or more device variants? Available options: (Xcode produces a non-thinned universal app), (Xcode produces a universal app and all available thinned variants), or a model identifier for a specific device (e.g. \u0026ldquo;iPhone7,1\u0026rdquo;). Defaults to .\nuploadBitcode : Bool For App Store exports, should the package include bitcode? Defaults to YES.\nuploadSymbols : Bool For App Store exports, should the package include symbols? Defaults to YES.\n踩坑 坑一 使用第一个命令前, 需要确保你的项目的签名配置好, 证书下好最新的\n坑二 第二个命令的-exportProvisioningProfile填的只是你的发布证书的名称, 不是那一串id\n坑三 第二个命令打包出来的*.ipa不能用来上传到AppStore, 一直报CocoaPods里面的第三方库签名错误\n坑四 打包出来的*.ipa需要上传到AppSotre的话, 可以使用第三个命令, xcrun那一个\n坑五 使用第三个命令打包出来的*.ipa, 上传到AppStore之后, 登录到iTunes Connect-APP-所有构建版本查看到上传的*.ipa正在构建。但是过了一会儿, AppleDeveloper账号的邮箱就会收到一封报错的邮件, 大概是说你的*.ipa包里面, 缺少了一个SwiftSupport文件夹\n经过各种搜索之后, 原来需要把xcrun打包出来的*.ipa解压, 然后新建一个文件夹, *.xcarchive里面的SwiftSupport文件夹copy进去, 还有把ipa解压出来的move进入, 最后打个zip包, 再改成ipa后缀就可以上传到AppStore了(应该吧, 还没测试😂)\n坑六 经过一轮测试之后, 发现用这个xcrun这个命令打的包需要自己吧SwiftSupport文件加到压缩包, 其实有一个命令没有那么麻烦的\u0026hellip;\n使用上面的第四个命令使用*.xcarchive把ipa导出, 导出的*.ipa里面会包含了SwiftSupport, 还不需要自己把它加进去\n番外篇 在踩到了第五个坑之后, 在github发现了一个iOS的打包、发布库\u0026hellip; 上地址: https://github.com/nomad/shenzhen\t懒得自己搞的可以使用这个库, 感觉还是挺不错的\nPython封装打包命令 github: package-ipa\n","date":"2016-05-31T00:00:00Z","permalink":"/p/ios%E8%87%AA%E5%8A%A8%E5%8C%96%E6%89%93%E5%8C%85%E4%B8%8A%E4%BC%A0%E7%9A%84%E8%B8%A9%E5%9D%91%E8%AE%B0/","title":"iOS自动化打包上传的踩坑记"},{"content":" 2016-06-01 修改安装的第四个命令多写了一个\u0026rsquo;o'\nCocoaPods简介 CocoaPods是一个管理Swift和Objective-C的Cocoa项目的依赖工具。它现在有超过一万八千多个库，可以优雅地帮助你扩展你的项目。简单的说，就是替你管理Swift和Objective-C的Cocoa项目的第三方库引入。\n官网地址: https://cocoapods.org/\n安装 Mac上面本来就自带了ruby，所有就不用自己安装了(除非你卸载了)。\n打开Terminal(终端)，输入以下命令(第二个命令可能会需要稍等一会儿)\ngem sources --remove https://rubygems.org/ gem source -a https://gems.ruby-china.org 第一个命令是移除官方源，因为在不翻墙的情况下，使用起来比较慢；第二个命令是添加ruby-china的RubyGems镜像(很多旧教程都是说使用taobao的gem源，但是taobao的gem源已经停止维护了，原文: https://ruby-china.org/topics/29250)。\n接下来运行一个命令查看是否成功添加了ruby-china的gem源:\ngem source 出现下图这样子，则代表成功添加~\n然后就可以开始真正安装CocoaPods了，输入一下命令:\nsudo gem install cocoapods 等一会儿就能安装完成~~~\n安装结束后，需要运行一下命令初始化CocoaPods:\npod setup 没有什么错误的话，就算了安装结束了。\n基本使用 打开Terminal(终端)，cd到你的Project目录，输入一下命令:\npod init 运行结束后，该目录下，会生成了一个Podfile文件\n使用文本编辑器(vim、Sublime Text2、等等\u0026hellip;)打开它(Podfile)，大概会看到以下的东西\nplatform :ios, 'xxx' # 目标平台及其版本 use_frameworks! # swift项目需要这句话，是Objective-C项目的话，请在前面加个`#`注释掉 target 'xxxx' do # 在这里添加你的依赖库说明，如pod xxx pod 'Alamofire', '~\u0026gt; 3.1’ # 例如这是引入Alamofire这个第三方库 end 编辑完Podfile后，使用Terminal(终端)输入其中一个命令(需要cd到项目的根目录，即Podfile所在目录):\npod install --no-repo-update or pod install 第一个命令是不更新本地库信息进行安装，速度会快一点，毕竟不需要更新。但是会有一点点问题，当有一个新的库发布的时候，就会无法安装成功。如果不嫌麻烦，可以定时执行以下命令更新CocoaPods的库，然后就可以在一段时间使用以上的第一个命令进行安装:\npod repo update 安装完成之后，打开项目就需要打开xxx.xcworkspace，而不是xxx.xcodeproj了\n如果在安装之后，修改了Podfile文件，可以执行以下的其中一个命令进行库的更新(两个命令的区别和上面说的一样):\npod update --no-repo-update or pod update 安装CocoaPods的可能失败原因 gem过旧，使用以下命令更新一下，再进行安装(先切换到了ruby-china的gem源再运行一下命令更新):\nsudo gem update ","date":"2016-05-16T00:00:00Z","permalink":"/p/2016-cocospods%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/","title":"2016 CocosPods安装教程"},{"content":"平时在iOS开发的时候，很多情况会导致内存泄露。有时候因为循环引用，导致了UIViewController不回收，还有其它好多原因。一般检测内存泄露都是使用Xcode的Instruments工具。但是这个工具有点复杂，新手入门还是有点难度。所以想到了使用runtime替换UIViewController的-(void)dealloc:方法的实现，检测ViewController是否被释放，从而知道ViewController里面有没有内存泄露。\nruntime的相关细节就不说了，不理解也能用，复制放到项目里面就好了。 一、思路 需要被替换的方法是UIViewController的-(void)dealloc:，所以新建一个UIViewController的Category，在其+(void)load里面执行方法替换。 替换的新方法里面做一个简单地log一下当前UIViewController的类名就好了就好了，即当UIViewController被回收的时候，log其类名。 二、实现 1.新建一个UIViewController的Category，编写新的dealloc方法。代码如下(可自行修改):\n- (void)skyLogInDealloc { printf(\u0026#34;\\n\u0026#34;); NSLog(@\u0026#34;-------------start-------------\u0026#34;); NSLog(@\u0026#34;Dealloc : %@\u0026#34;, NSStringFromClass([self class])); NSLog(@\u0026#34;--------------end--------------\u0026#34;); printf(\u0026#34;\\n\u0026#34;); [self skyLogInDealloc]; } 2.重写+(void)onLoad:方法。代码如下:\n+ (void)load { [super load]; SEL originSEL = NSSelectorFromString(@\u0026#34;dealloc\u0026#34;); SEL swapSEL = @selector(skyLogInDealloc); Method originMethod = class_getInstanceMethod(self, originSEL); Method swapMethod = class_getInstanceMethod(self, swapSEL); IMP originIMP = method_getImplementation(originMethod); IMP swapIMP = method_getImplementation(swapMethod); BOOL didAddMethod = class_addMethod(self, originSEL, swapIMP, method_getTypeEncoding(originMethod)); if(didAddMethod) { class_replaceMethod(self, swapSEL, originIMP, method_getTypeEncoding(originMethod)); } else { method_exchangeImplementations(originMethod, swapMethod); } } 完整的m文件实现代码\n#if DEBUG + (void)load { [super load]; SEL originSEL = NSSelectorFromString(@\u0026#34;dealloc\u0026#34;); SEL swapSEL = @selector(skyLogInDealloc); Method originMethod = class_getInstanceMethod(self, originSEL); Method swapMethod = class_getInstanceMethod(self, swapSEL); IMP originIMP = method_getImplementation(originMethod); IMP swapIMP = method_getImplementation(swapMethod); BOOL didAddMethod = class_addMethod(self, originSEL, swapIMP, method_getTypeEncoding(originMethod)); if(didAddMethod) { class_replaceMethod(self, swapSEL, originIMP, method_getTypeEncoding(originMethod)); } else { method_exchangeImplementations(originMethod, swapMethod); } } - (void)skyLogInDealloc { printf(\u0026#34;\\n\u0026#34;); NSLog(@\u0026#34;-------------start-------------\u0026#34;); NSLog(@\u0026#34;Dealloc : %@\u0026#34;, NSStringFromClass([self class])); NSLog(@\u0026#34;--------------end--------------\u0026#34;); printf(\u0026#34;\\n\u0026#34;); [self skyLogInDealloc]; } #endif 三、部分细节 在ARC下，使用@selector(dealloc:)会报错，所以只好这样子获取它的SEL: NSSelectorFromString(@\u0026quot;dealloc\u0026quot;) DEBUG是一个宏，当构建项目使用Debug的时候，其值会为YES，当使用Release的时候，其值会是NO。加上去就是为了发布的时候，也不需要担心忘记将其移除功能。 附上github地址: https://github.com/skytoup/SkyLogInDealloc\n","date":"2016-02-02T00:00:00Z","permalink":"/p/ios%E4%BD%BF%E7%94%A8runtime%E7%9B%91%E6%B5%8Buiviewcontroller%E7%9A%84dealloc/","title":"iOS使用runtime监测UIViewController的dealloc"},{"content":"一、初始化工程 npm init \u0026#39;project name\u0026#39; 输入各种信息之后，新建工程完毕。\n二、安装babel npm install --save-dev babel-cli 三、修改Webstorm配置 打开Setting -\u0026gt; Languages \u0026amp; Frameworks -\u0026gt; JavaScript -\u0026gt; JavaScript language version，选择ECMAScript6。\n四、安装babel预插件 npm install babel-preset-es2015 --save-dev 五、创建.babelrc配置文件 echo \u0026#39;{ \u0026#34;presets\u0026#34;: [\u0026#34;es2015\u0026#34;] }\u0026#39; \u0026gt; .babelrc 或自行在项目根目录创建.babelrc文件，并输入{ \u0026quot;presets\u0026quot;: [\u0026quot;es2015\u0026quot;] }，保存、退出。\nEND tip: 运行的时候选择转换好的文件，别选原文件！！！ 更详细请参考官方说明: Babel_Webstorm\n","date":"2016-01-28T00:00:00Z","permalink":"/p/webstorm%E4%BD%BF%E7%94%A8babel6/","title":"Webstorm使用babel6"},{"content":"近来想用Node.js做一个BT下载的程序，于是研究了一下BT下载相关的知识。\n一、BT文件解析 用了那么久的bt下载，都知道下载前，都是先解析种子文件(.torrent)的吧。 种子文件使用的是BEncode(B编码)保存数据，这种编码有四种数据结构：\n1.string(字符串) string的编码为 \u0026lt;string length\u0026gt;:\u0026lt;string\u0026gt;\n例: 7:example, 表示字符串 'example' 2.integer(整数) integer的编码为 i\u0026lt;integer\u0026gt;e\n例: i123456e, 表示整数 123456 3.list(列表) list的编码为 l\u0026lt;BEncode\u0026gt;e\n例: l6:stringi123ee, 表示数组 ['string', 123] 4.dictionary(字典) dictionary的编码为 d\u0026lt;BEcode的string\u0026gt;\u0026lt;BEncode\u0026gt;e,即d\u0026lt;key\u0026gt;\u0026lt;value\u0026gt;e,key为BEcode的string,value为BEncode\n例:d3:key5:value4:testi123ee, 表示{key:'value', test:123} 注: list和dictionary可以相互嵌套\n解析基本思路，dictionary的key肯定为字符串，value和list的元素为任意类型，使用递归思路解析比较简单。\n附上一份自己写的简单解析代码: node.js简单解析bencode\n二、BT文件结构 参考别人的文章: torrent文件分析\n三、BT协议 还是参考别人的文章:\nBT（带中心Tracker）通信协议的分析\nBitTorrent协议规范\n常见P2P协议之BitTorrent 分析\n发现BT协议还是挺复杂的,下载的代码一时半刻写不出来。 番外篇 一、 关于info_hash 网上的资料比较少，搜了好久，发现很多都是说得不是很清楚。\n1.BT协议中的info_hash\ninfo_hash是种子解析后的对象的info节点中的pieces，其长度为20的倍数，每20个字节对应一块文件。在BT协议中，下载文件需要一块一块下载，下载那一块，就是看你传过去的info_hash。\n但是BT文件里面的部分数据是ASCII码，不能直接传输过去，需要经过一定的编码。编码很简单，凡是ASCII码的数字、字母，都按数字、字母传输，其余的以它的16进制前面加一个%符号传输。\n例如: ASCII编码 6D8875 应该为 m%88u\n2.BT种子转磁力链接的info_hash\n经过各种搜索、测试，最后看源码发现，BT种子是如何转磁力链接。 首先把种子的info节点以BEcode编码，将其sha1散列，最后把得到的结果拼接到这里的末尾: magnet:?xt=urn:btih:。多个文件的情况，好像是用\u0026amp;符号拼接每一个文件。\n原本想做一个测试测试的，谁知道node.js用buffer读取bt的部分string后，length就变了...... 二、node.js的BT下载库 找了好久, 终于发现了一个node.js的BT下载库, 功能还是挺强大的，貌似还有其它的功能。\n上地址: webtorrent\n官方文档写得挺详细的，而且这个库简单易用。至于还有其它什么功能我也不太了解了。\n文章参考 torrent文件分析 BT（带中心Tracker）通信协议的分析 BitTorrent协议规范 常见P2P协议之BitTorrent 分析 ","date":"2016-01-25T00:00:00Z","permalink":"/p/node.js%E7%9A%84bt%E4%B8%8B%E8%BD%BD/","title":"Node.js的BT下载"},{"content":" 近段时间在学习React-Native，发现了还可以用flux思想进行开发，于是便想试一试。\n查了各种关于flux的资料之后，有好几个这类型的框架。但是Redux这个框架貌似比较多人使用，所以就选择这个框架了。\n选好框架之后，发现框架的介绍资料很少，而且没有搭建的教程，只好自己做一下实验了。经过无数次实验、踩坑，终于成功了\u0026hellip;\u0026hellip;..\n注: 我是一个iOS程序员，不太懂大前端，为了学习react-native，react只是随便学习了一点基础，若本文有错，望你能指出\n下面分享一下成果:\n1. 搭建基本React-Native项目 很简单，大家都会，一句命令\nreact-native init 你的项目名称 2. 添加框架模块 新建完成基本项目后，打开package.json，在dependencies下，添加\n\u0026#34;react-redux\u0026#34;: \u0026#34;^3.1.2\u0026#34; 有人或许想问，最新的`react-redux`最新版不是4.x么？为什么不用最新的。\t这是一个坑，到github的项目主页看下面的文档，里面说4.x不支持`react-native`，请使用3.x的。 添加完成后，继续输入一下命令：\nnpm i # 安装刚才添加的项目 npm install --save redux npm install --save redux-thunk 3. 修改项目结构 以下是我的目录结构 ├── App │ ├── Action │ ├── Component │ ├── Container │ ├── Reducer │ ├── Store │ └── Main.js 4.创建Redux项目框架(相关名词不解析了，请自行学习Redux框架了解) 这里就不详细说了，简单说一下搭建时，编辑文件的顺序估计就可以了，详细的编写内容，到我的github上clone一下示例项目就好了。 新建Action \u0026ndash;\u0026raquo; 新建Redicer \u0026ndash;\u0026raquo; 新建Store \u0026ndash;\u0026raquo; 新建Main.js入口 \u0026ndash;\u0026raquo; 修改android和iOS的入口文件(即项目根目录的index.xxx.js)\n基本就是这样子了~\n最后附上详细代码的github地址: https://github.com/skytoup/react-native_ReduxTest 本文参考:\nreact-native的Redux示例: https://github.com/chentsulin/react-native-counter-ios-android ","date":"2015-12-24T00:00:00Z","permalink":"/p/react-native%E6%90%AD%E5%BB%BAredux%E6%A1%86%E6%9E%B6/","title":"React-Native搭建Redux框架"},{"content":" 首先下载Atom，下载地址:https://atom.io\n打开Atom，在右上角Atom的菜单处选择Install Shell Comments(安装apm) 到github下载一下nuclide，直接用git或者下载zip文件解压。\n项目地址: https://github.com/facebook/nuclide git命令: git clone https://github.com/facebook/nuclide 安装nuclide前，需要电脑有以下环境\n- python 2.6 or later - Atom 0.209.0 or later - Node 0.12.0 or latr - node、npm、apm、git在你的$PATH (node、npm可通过安装node.js来安装) 执行一下命令\ncd nuclide ./script/dev/setup # 上面的命令会通过npm下载东西，可能比较久 apm link # 注意，上面的命令是把当前目录 软链接 到 ~/.atom/packages 目录下，所以安装完了之后，别把nuclide这个文件夹删了！！！ 搞定之后，打开Atom试一下是否安装成功，如果不成功，`Atom`会提示你rebuild插件，我也不知道什么问题，我安装的时候失败了，点了几次rebuild就突然成功了。 配置一下nuclide-flow吧，这个插件可以检测语法错误，CMD+左键点击跳转。\n使用这个插件之前，请先安装flow，安装完之后，到Atom的Setting-Packages，找到nuclide的插件，进入设置里面，滚动到nuclide-flow插件的设置，把flow的位置填上，根据个人喜好打上几个选项的钩。 另外有几个插件也不错\n- atom-beautify 代码格式化 - atomerminal 打开终端，pwd为当前文件所在路径 - docblockr 写注释的插件 可以通过apm install xxx来安装，如apm install atom-beautify。\n接下来就可以好好享受Atom了~~~\n","date":"2015-12-09T00:00:00Z","permalink":"/p/react-native-%E4%B9%8B-atom%E9%85%8D%E7%BD%AEnuclide%E6%8F%92%E4%BB%B6/","title":"React Native 之 Atom配置nuclide插件"},{"content":"iOS9有一个新特性\u0026ndash;ATS(App Transport Security)，加强了一下App网络传输安全。\n官方文档描述: App Transport Security (ATS) enforces best practices in the secure connections between an app and its back end. ATS prevents accidental disclosure, provides secure default behavior, and is easy to adopt; it is also on by default in iOS 9 and OS X v10.11. You should adopt ATS as soon as possible, regardless of whether you’re creating a new app or updating an existing one.\nIf you’re developing a new app, you should use HTTPS exclusively. If you have an existing app, you should use HTTPS as much as you can right now, and create a plan for migrating the rest of your app as soon as possible. In addition, your communication through higher-level APIs needs to be encrypted using TLS version 1.2 with forward secrecy. If you try to make a connection that doesn\u0026rsquo;t follow this requirement, an error is thrown. If your app needs to make a request to an insecure domain, you have to specify this domain in your app\u0026rsquo;s Info.plist file.\n大概意思就是iOS9和OS X 10.11不能直接使用HTTP，只能使用HTTPS，现在开始，最好计划好迁移工作。 实际上，没有强制一定要使用HTTPS，如果需要使用HTTP，可以到Info.plist文件配置一下。\nATS基本使用(大概用Json格式表示) 在Info.plist的Information Property List下添加NSAppTransportSecurity，类型是Dictionary。 在NSAppTransportSecurity下添加NSAllowsArbitraryLoads，类型是Boolean，值是YES。 { NSAppTransportSecurity: { NSAllowsArbitraryLoads : YES } } 大概意思就是，配置ATS，设置为允许使用任意的HTTP/HTTPS。 ATS进阶使用(大概用Json格式表示) 1、允许单个服务器的HTTP连接:\n{ AppTransportSecurity: { NSExceptionDomains: { 允许HTTP访问的地址: { NSExceptionAllowsInsecureHTTPLoads : YES } } } } 2、允许单个服务器的HTTPS使用较低的安全协议\n{ AppTransportSecurity: { NSExceptionDomains: { 允许HTTP访问的地址: { NSExceptionRequiresForwardSecrecy : NO, NSExceptionMinimumTLSVersion : TLSv1.0 } } } } 3、使用ATS，且允许使用HTTP访问指定的服务器\n{ AppTransportSecurity: { NSExceptionDomains: { 允许HTTP访问的地址: { NSExceptionAllowsInsecureHTTPLoads : YES } }, NSAllowsArbitraryLoads : YES } } 4、指定服务器使用ATS，其余的不使用\n{ NSAppTransportSecurity: { NSAllowsArbitraryLoads = YES, NSExceptionDomains: { 使用HTTPS访问的地址: { NSExceptionAllowsInsecureHTTPLoads : NO } } } } ","date":"2015-11-28T00:00:00Z","permalink":"/p/ios9-ats-%E8%AE%BE%E7%BD%AE/","title":"iOS9 ATS 设置"},{"content":"看到一篇文章说IntelliJ IDEA开发作为Go的开发环境不错，突然发神经地想试了一下。 谁知道跟着教程走，到后面越来越不对劲，去百度其它教程，谁知道千篇一律。。。\n好了下面开始了\n首先把GO安装好。。。（自行安装，附上一篇我之前写的MAC安装GO）\n安装IntelliJ IDEA，下载地址: https://www.jetbrains.com/idea/download/。\n下载go-lang-idea-plugin这个插件，下载地址: https://plugins.jetbrains.com/plugin/5047。(PS:网上百度的基本都是下源码、编译，搞了一个下午编译，谁知道有已经编译好的可以下载)\n下载之后，是一个zip文件，不需要解压，打开IntelliJ IDEA，打开Preferences-\u0026gt;Plugins，点击Install plugin from disk...，选择刚下载的zip文件，然后重启一下，插件就这样安装好了。 打开File-\u0026gt;Project Structure...，找不到的随便打开一个项目就能看到。点击SDKS，新建一个GO SDK，填上GO的安装目录。 使用:随便新建一个Go项目，点击Edit Configurations...，新建一个Go Application，右边File你的pack main包含func main的文件，Output directory为编译后的文件输出目录。新建完毕后，选择新建的Debug选项就可以编译、运行程序了。 接下来来点IntelliJ IDEA的快捷键吧(我的是Mac OSX) :\nCMD+Shift+O 查找跳转文件 CMD+Shift+L 代码对齐 CMD+Shift+Alt+F go fmt 一个\b文件 CMD+Shift+Alt+P go fmt整个项目 CMD+Alt+O 自动import CMD+F12 显示当前文件的结构 按住CMD点击结构体\b可以源码跳转 CMD+P 显示函数参数 CMD+E 显示最近编辑文件 Alt+Enter 自动修复错误 Shift+F6 重构 ","date":"2015-11-26T00:00:00Z","permalink":"/p/intellij-idea%E5%BC%80%E5%8F%91golang%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/","title":"IntelliJ IDEA开发golang环境配置"},{"content":"1.下载安装包 由于官网被墙了，所以到国内的一个go论坛下载:点击进入下载 下载后，双击进行安装。\n2.配置环境变量 $GOROOT:go lang 安装目录 $GOPATH:go lang 工作目录，有点像Eclipse那样子\n下面开始配置 到终端输入以下命令:\nvim ~/.bash_profile # 打开环境变量配置 在最下面添加\nexport GOPATH=/Users/(用户名/路径) # 改成你喜欢的路径 export GOROOT=/usr/local/go # 默认安装都市这个路径 添加完毕后，退出vim 到终端输入以下命令:\nsource ~/.bash_profile # 让环境变量配置生效 完成后，到$GOPATH的目录下，新建三个文件夹 bin:存放编译后的可执行文件; pkg:存放编译后的包文件; src:存放项目源文件; 最后，到终端输入以下命令验证时候安装成功:\ngo version 出现类似图中则为成功，若不成功，请检测一下你的环境变量配置。\n3.IDE 一.Sublime Text(个人比较喜欢) 安装好SublimeText后，再安装插件管理器，到插件管理器安装处，输入GoSublime，回车安装 安装完成后，还要安装一个语法提示插件: 到终端输入一下命令:\ngo get github.com/nsf/gocode go install github.com/nsf/gocode 二.LiteIDE 下载地址:LiteIDE\n下载其中一个即可 解压即可用\n三.Wide 项目地址:Wide项目 官网地址:[官网][wide] 下载后，使用go来运行，在浏览器打开指定的ip和端口即可使用(详情请参考[官网][wide]) [wide]:\thttps://wide.b3log.org/login\t\u0026ldquo;wide\u0026rdquo;\n4.学习Go lang 官方教程:免翻墙 Go语言中文网:Go语言中文网 GoLang中国:GoLang中国 ","date":"2015-10-03T00:00:00Z","permalink":"/p/macos%E4%B8%8B%E5%AE%89%E8%A3%85go-lang/","title":"MacOs下安装go lang"},{"content":"LocationARTest 测试环境：Xcode 6，iOS 7.0(真机)以上。 基于Metaio的demo修改出来的，查看周边的美食\ngithub链接 : https://github.com/skytoup/LocationARTest\n注意 本demo不能直接运行\n需要修改JHKey.h中的JH_ID(修改为你的聚合id，并到聚合数据网站申请数据)\n聚合数据账号注册：http://www.juhe.cn/ 数据申请：http://www.juhe.cn/docs/api/id/45 需要修改Info.plist中的MetaioLicenseString\nMetaioLicense申请：http://metaio.com/ 由于MetaioSDK.framework有300+M，github限制单文件100M，所以无法上传\n请自行到Metaio官网下载http://metaio.com/ 用到的其他工具 JsonToModule:一个命令行工具，能把json文件转换成java或者objc的模型类\n用到的第三方库 metaioSDK.framework JuheApisSDK ","date":"2015-08-27T00:00:00Z","permalink":"/p/ios-metaio%E8%99%9A%E6%8B%9F%E7%8E%B0%E5%AE%9E-%E4%BD%8D%E7%BD%AE-demo/","title":"iOS metaio虚拟现实-位置 demo"},{"content":"SkyCalendarPriceView 测试环境：Xcode 6，iOS 7.0以上。 github地址 : https://github.com/skytoup/SkyCalendarPriceView\n简介 一个可以自定义样式的价格日历\n高度封装，简单使用，传入对应的数据模型即可显示 灵活度高，大部分view的样式可调整 显示的文字可自定义处理，也可以自定义和系统处理一起使用 可在Xib或StoryBoard中拖拽使用，也支持自动布局，也可以手写固定的Frame 支持旋屏 使用方法 把头文件 SkyCalendarPriceView.h 导入项目。\n#import \u0026#34;SkyCalendarPriceView.h\u0026#34; 基本使用 // 创建价格日历 SkyCalendarPriceView *v = [SkyCalendarPriceView calendarPriceView]; // 创建数据模型 SkyCalendarPriceModel *model = [SkyCalendarPriceModel calendarPriceModelWithYear:@(2015) withMonth:@(3) withDay:@(22) withPrice:@(30) withCount:@(10)]; SkyCalendarPriceModel *model2 = [SkyCalendarPriceModel calendarPriceModelWithYear:@(2015) withMonth:@(9) withDay:@(22) withPrice:@(10) withCount:@(10)]; SkyCalendarPriceModel *model3 = [SkyCalendarPriceModel calendarPriceModelWithYear:@(2015) withMonth:@(7) withDay:@(24) withPrice:@(20) withCount:@(10)]; // 导入数据模型 _v.datas = @[model, model2, model3]; // 设置今天的时间，可不设置 _v.today = [NSDate date]; 定制样式 详情请看SkyCalendarPriceViewConfig.h\n代码修改部分view样式 // 可通过调用以下几个方法 SkyCalendarHeader + (void)SkyCalendarPriceViewInitHeaderViewOfYearMonthViewStyleWithBlock:(void(^)(UIView *view))block + (void)SkyCalendarPriceViewInitHeaderViewOfWeekLabelsStyleWithBlock:(void(^)(NSArray *labels))block; SkyCalendarCell + (void)SkyCalendarPriceViewInitCellStyleWithBlock:(void(^)(UICollectionViewCell *cell))block; 自定义显示的数据样式 // 通过实现SkyCalendarPriceViewDelegate的方法进行显示自定义的数据样式 - (NSDictionary*)skyCalendarPriceView:(SkyCalendarPriceView*)cview cellDataStringDictionaryWithIndexPath:(NSIndexPath*)indexPath withYear:(NSString*)year withMonth:(NSString*)month withDay:(NSString*)day withPrice:(NSString*)price withCount:(NSString*)count withIsToday:(BOOL)isToady; - (NSString*)skyCalendarPriceView:(SkyCalendarPriceView*)cview cellDayStringWithYear:(NSString*)year withMonth:(NSString*)month withDay:(NSString*)day withIsToday:(BOOL)isToday; - (NSString*)skyCalendarPriceView:(SkyCalendarPriceView *)cview headerLabelStringWithYear:(NSString*)year withMonth:(NSString*)month; 若不需要header停留在顶部 // 更换默认布局（注:Xib或StoryBoard中，需手动在代码或面板里设置Layout，设置的Layout需要为UICollectionViewFlowLayout的子类） skyCalendarPriceView.collectionViewLayout = [UICollectionViewFlowLayout new]; 监听选中/取消选中日期 // 通过实现SkyCalendarPriceViewDelegate的方法进行监听 - (BOOL)skyCalendarPriceView:(SkyCalendarPriceView*)cview shouldSelectIndexWithPriceModel:(SkyCalendarPriceModel*)model; - (void)skyCalendarPriceView:(SkyCalendarPriceView*)cview didUnselectIndexWithPriceModel:(SkyCalendarPriceModel*)model; ","date":"2015-08-27T00:00:00Z","permalink":"/p/ios%E4%BB%B7%E6%A0%BC%E6%97%A5%E5%8E%86/","title":"iOS价格日历"},{"content":"UIView背景色的四个边角自定义成圆角\n比较简单，没什么好介绍的\nGitHub链接：https://github.com/skytoup/SkyRadiusView\n测试环境：Xcode 6，iOS 7.0以上\nhttps://img-blog.csdn.net/20150812072718059https://img-blog.csdn.net/20150812072718059\npod \u0026lsquo;SkyRaduisView\u0026rsquo;, \u0026lsquo;~\u0026gt; 1.0.0\u0026rsquo;\n","date":"2015-08-12T00:00:00Z","permalink":"/p/ios-uiview%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9B%9B%E4%B8%AA%E8%BE%B9%E8%A7%92%E7%9A%84%E5%9C%86%E8%A7%92/","title":"iOS UIView自定义四个边角的圆角"},{"content":"简介 一个把json文件，转换为java、objcetive-c的module文件。 暂不支持一个array的下一级，还是array 简单方便地处理后台返回的json数据，不必要一个一个字段复制到module类上。 github [json2module](https://github.com/skytoup/JsonToModule) 用到的第三方开源库 cJSON c语言的json解析库 基本的实现逻辑 编译 命令行进入到项目根目录后 make 测试: make test 测试生成的文件在项目目录下的out_file里面 使用说明(English不是很好): json2moudle \u0026lt;json file path\u0026gt; [-n] [-o] [-p] [-h] [--java] [--objc] option: -n \u0026lt;module name\u0026gt; default is json file name -o \u0026lt;out path\u0026gt; default is run path -p \u0026lt;java pack name\u0026gt; default is \u0026quot;\u0026quot; -h help --java out java module file --objc out objective-c module file if not have --java or --objective-c, default is java 使用事例： json2moudle t.json json2moudle t.json -n test2 -o ~/Desktop/test -p test.com.hehe json2moudle t.json -n test2 -o ~/Desktop/test json2moudle t.json -n test2 -o ~/Desktop/test -p test.com.hehe --objc --java ","date":"2015-08-06T00:00:00Z","permalink":"/p/%E6%8A%8Ajson%E8%BD%AC%E6%88%90javaobjc%E7%9A%84module%E7%9A%84%E5%B0%8F%E5%B7%A5%E5%85%B7/","title":"把json转成java、objc的module的小工具"},{"content":"在用自动布局的时候，老是忘记更新Constraint使用哪个方法，特意去查了一下资料，做了一下笔记。 如果出现错误的地方，希望大家指出，谢谢。\nUIView: `// 重写此方法，当约束更新时，可更新你的特殊约束，别忘记调用super方法\n(void)updateConstraints;` `// 调用这个方法，会触发update Constraints的操作，即更新约束。在needsUpdateConstraints返回YES时，才能成功触发update Constraints的操作。我们不应该重写这个方法。\n(void)updateConstraintsIfNeeded;` `// 会调用drawRect方法\n(void)setNeedsDisplay;` `// 会默认调用layoutSubViews\n(void)setNeedsLayout;` `// 当一个自定义的View某一个属性的改变可能影响到界面布局，我们应该调用这个方法来告诉布局系统在未来某个时刻需要更新。系统会调用updateConstraints去更新布局。\n(void)setNeedsUpdateConstraints;` `// 布局系统使用这个返回值来确定是否调用updateConstraints\n(void)needsUpdateConstraints;` // 如果有刷新的标记(应该是指调用过-(void) setNeedsLayout这个方法吧)，立即调用layoutSubviews进行布局 -(void) layoutIfNeeded;\n`// 自动调用layoutIfNeeded，当使用基于约束的布局基本实现适用于基于约束的布局，否则什么也不做。\n(void)layoutSubviews;` ","date":"2015-08-04T00:00:00Z","permalink":"/p/%E8%AE%B0%E5%BD%95%E4%B8%80%E4%B8%8Bios%E7%9A%84%E5%87%A0%E4%B8%AAuiview%E7%9A%84%E6%96%B9%E6%B3%95/","title":"记录一下iOS的几个UIView的方法"},{"content":"SkyWaitingView（github链接） 测试环境：Xcode 6，iOS 7.0以上。 简介 一个简单的等待指示器\n可自定义圆弧粗细、颜色、旋转速率 可自定义标签显示 使用方法 把头文件 SkyWaitingView.h 导入项目，然后设置各属性，具体使用方法请参考示例项目。\nSkyCircleWatingView *v = [SkyCircleWatingView new]; v.frame = CGRectMake(50, baseY, 0, 0); [v sizeToFit]; [self.view addSubview:v]; v.rate = 1.f; [v start]; 联系方式 QQ：875766917，请备注 QQMail：875766917@qq.com ","date":"2015-07-06T00:00:00Z","permalink":"/p/ios%E4%B8%80%E4%B8%AA%E5%B8%A6%E5%8A%A8%E7%94%BB%E7%9A%84%E7%AD%89%E5%BE%85%E6%8C%87%E7%A4%BA%E5%99%A8/","title":"iOS一个带动画的等待指示器"},{"content":"1.链表基础 链表是一段非连续物理地址的存储结构，通过节点的成员变量，存储其它单元格的地址，构成一条链，称为链表。\n链表的结构如下图（画得不是很好）： 这是一个双向链表，可以通过每一个节点，找到它的上一个节点，或者下一个节点。 如果为单向链表，则节点没有pre这个成员变量。\n节点的结构体定义：\nstruct link_node { struct link_node *next; // 前一个节点 struct link_node *pre; // 后一个节点 void *val; // 节点值 }; 一般来说，都会有一个存储链表头节点的结构体，如上图的第一个圆角矩形。\n可以新建一个结构体来存储链表首节点，或者新建一个不存储数值的节点来存储首节点。\n2.优缺点 动态创建节点来存储数据 插入节点快 读取指定位置节点慢 结构较简单 3.扩展 栈 循环链表 队列（双向队列、单向队列） 树、图、链地址法的哈希表 结构的基础 。。。。。。（还有很多吧） ","date":"2015-06-20T00:00:00Z","permalink":"/p/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-%E9%93%BE%E8%A1%A8/","title":"数据结构-链表"},{"content":"三级联想菜单（SkyAssociationMenuView） 测试环境：Xcode 6，iOS 7.0以上。 以前做一个项目准备用来当做地区选择用的，后来没用上。。。。。\n只是粗略的实现了一下，写得不是很好，望大家见谅。。。。。\ngithub：https://github.com/skytoup/SkyAssociationMenuView\n效果图：\n","date":"2015-06-19T00:00:00Z","permalink":"/p/ios%E4%B8%89%E7%BA%A7%E8%81%94%E6%83%B3%E8%8F%9C%E5%8D%95skyassociationmenuview/","title":"iOS三级联想菜单（SkyAssociationMenuView）"}]