[南栀的博客]&组件化-动态库实战
组件化-动态库实战
原文地址 ,此简书只做备份,强烈推荐原文,毕竟格式比简书好看,还清晰
起因
去年,链家网iOS端,之前由于所有的业务端代码都是混乱管理,造成开发有很多痛点
无法单测团队成员提交代码冲突机率大CI配合效果差功能性代码多端无法复用单仓库代码量大编译时间长等等痛点,领导和组内多次沟通开始着手组件化开发,希望能改进这些开发中的痛点,成立组件化团队。
组件化的方案大同小异,基础性代码封装私有库,业务组件交互交由中间件负责,项目依赖工具用 iOS项目事实上的标准CocoaPods
前期的基础性组件拆分都较为顺利,从依赖树的叶子节点开发是最合适的方案。
随着组件抽离的越来越多,私有库的依赖体系也越来越复杂,慢慢过渡到了业务组件。业务组件用了Swift的第三方组件,用了Swift库的同学都知道必须加上use_frameworks!,这个标记是说Pod管理的依赖全部编译为动态库,然后呢我们的很多组件又依赖了诸如百度地图,微信分享等静态库,于是我在执行pod install报了一个没有碰见过的错误
[!] The 'Pods-LJA_Example' target has transitive dependencies that include static binaries: (/Users/nero/Desktop/Static_Dynamic/Componment/Example/Pods/libWeChatSDK/libWeChatSDK.a)

这就尴尬了,于是一阵疯狂的搜索 google stackoverflow 等,然而并没有什么卵用,而且上面催得急,根本没时间处理这些
小问题业务重构是最主要的,以至于我们的业务组件没有做到独立仓库拆分。
直到最近终于找到了解决办法:( 主要是自己的功力不够深厚
理论功底
动态库和静态库
介绍
首先静态库和动态库都是以二进制提供代码复用的代码库
- 静态库 常见的是
.a - 动态库常见的是
.dll(windows).dylib(mac)so(linux) - framework(in Apple): Framework是Cocoa/Cocoa Touch程序中使用的一种资源打包方式,可以将代码文件、头文件、资源文件、说明文档等集中在一起,方便开发者使用。
也就是说我们的framework其实是资源打包的方式,和静态库动态库的本质是没有关系的
静态库和动态库的区别
静态库: 链接时会被完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码
动态库:
链接时不复制,在程序启动后用dyld加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存(不是磁盘是内存中只有一份动态库),还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows
和linux上面一般插件和模块机制都是这样实现的。
But我们的苹果爸爸在iOS平台上规定不允许存在动态库,并且所有的IPA都需要经过苹果爸爸的私钥加密后才能用,基本你用了动态库也会因为签名不对无法加载,(越狱和非APP store除外)。于是就把开发者自己开发动态库掐死在幻想中。
直到有一天,苹果爸爸的iOS升级到了8,iOS出现了APP Extension,swift编程语言也诞生了,由于iOS 主APP需要和Extension共享代码,Swift语言的机制也只能有动态库,于是苹果爸爸尴尬了,不过这难不倒我们的苹果爸爸,毕竟我是爸爸,规则是我来定,我想怎样就怎样,于是提出了一个概念Embedded Framework,这种动态库允许APP 和 APP Extension共享代码,但是这份动态库的生命被限定在一个APP进程内。简单点可以理解为 被阉割的动态库。
举个例子, iOS项目中使用Embeded Framework
如果你把某个自己开发的动态库(系统的不算,毕竟苹果是爸爸)放在了Linked Frameworks and Libraries里面,程序一启动就会报Reason: Image Not Found,你只能把它放在Embeded Binaries里面才能正常使用,
看图:

静态库和动态库如何构建和加载
简单点,说话的方式简单点~~
上面的介绍貌似有点抽象啊 套用在美团技术分享大会上的话就是:
静态库: 一堆目标文件(.o/.obj)的打包体(并非二进制文件)
动态库: 一个没有main函数的可执行文件
这里我们来复习下C语言的基本功,编译和链接
编译:将我们的源代码文件编译为目标文件
链接:将我们的各种目标文件加上一些第三方库,和系统库链接为可执行文件。
由于某个目标文件的符号(可以理解为变量,函数等)可能来自其他目标文件,其实链接这一步最主要的操作就是 决议符号的地址。
- 若符号来⾃静态库(本质就是.o的集合包)或 .o,将其纳⼊链接产物,并确定符号地址
- 若符号来⾃动态库,打个标记,等启动的时候再说---交给dyld去加载和链接符号
于是链接加装载就有了不同的情况
- Load 装载:将库⽂件载⼊内存
- Static Loading:启动时
- Dynamic Loading:启动后(使⽤时)
- Link 链接:决议符号地址
- Static Linking:构建(链接)时
- Dynamic Linking:运⾏时(启动时或使⽤时)
然后组合起来就是2*2 = 4了
- Static Loading + Static Linking
- Static Loading + Dynamic Linking
- Dynamic Loading + Dynamic Linking
-
Dynamic Loading + Static Linking
第一种是纯静态库相关了
第二种就是静态加载(启动时),动态链接 ,链接时,动态库参与链接,但是这时候只是给符号打了标记告诉我这个符号来自与动态库,程序启动时,iOS或者Mac OS操作系统的dyld自动 load+link。
既然全部都是自动的。那么符号的调用方完全不知道你到底是源码还是静态库,动态库 。
第三种收到调用dlopen + performSelector 通常iOS的APP不适用这里不讨论
第四种,没见过,个人也不是特别懂
有需求请参看文后的程序员的自我修养一书
静态库和动态库依赖关系
既然有2种库,那么依赖关系又是2*2喽
- libA.a dependency libB.a
- UIKit.dylib dependency Foundation.dylib
- libA.a dependency Foundation.dylib
- MyXX.dylib dependency libA.a
第一种 静态库互相依赖,这种情况非常常见,制作静态库的时候只需要有被依赖的静态库头文件在就能编译出来。但是这就意味者你要收到告诉使用者你的依赖关系
幸运的是 CocoaPod就是这样做的
第二种动态库依赖动态库,两个动态库是相互隔离的具有隔离性,但是制作的静态库的时候需要被依赖动态库参与链接,但是具体的符号决议交给dyld来做。
第三种,静态库依赖动态库,也很常见,静态库制作的时候也需要动态库参与链接,但是符号的决议交给dyld来做。
第四种,动态库依赖静态库,这种情况就有点特殊了。首先我们设想动态库编译的时候需要静态库参与编译,但是静态库交由dyld来做符号决议,but
这和我们前面说的就矛盾了啊。静态库本质是一堆.o的打包体,首先并不是二进制可执行文件,再者你无法保证主程序把静态库参与链接共同生成二进制可执行文件。这就尴尬了。
怎么办?
目前的编译器的解决办法是,首先我无法保证主程序是否包含静态库,再者静态库也无法被dyld加载,那么我直接把你静态库的.o偷过来,共同组成一个新的二进制。也被称做吸附性
那么我有多份动态库都依赖同样的静态库,这就尴尬了,每个动态库为了保证自己的正确性会把静态库吸附进来。然后两个库包含了同样的静态库,于是问题就出现了。 看到这里想必前面出现的错误你已经能猜出来了把_
后面再详细解释
先来个总结
可执⾏⽂件(主程序或者动态库)在构建的链接阶段
- 遇到静态库,吸附进来
- 遇到动态库,打标记,彼此保持独⽴
Xcode 项目结构
target-对于一个产物(app,.a ,.framework)
project-一个项目包含多个target
workspace:一个包含多个target
schema: 指定了一个产物是按照何种的依赖关系,编译-链接到最终的一个产物
iOS依赖管理事实上的标准
这么多年,Apple的博客和文档也就告诉了我们什么是静态库 什么是动态库,如何制作等。但是并没有给我们提供一系列的依赖管理工具。所以CocoaPods成了事实上的标准。
通常CocoaPods管理的工程结构如下:
• CocoaPods
+ App.workspace
+ App.project
• Pods.project
• pod target => .a
那么当我们按下CMD+B的时候,整个项目按照先编译被依赖Pod,然后依赖其他Pod的Pod也被构建出来,最终所有的组件被编译为一个lib-Pods-XXXAPP.a被添加进项目进去。资源通过CocoaPods提供的脚本也一并被复制进去。想了解CocoaPods做了什么的读者可以参看后面的链接
解决问题
这么多理论功底的建立,相信我们已经能分析出来之前pod install的原因了。就是用了use_framework那么我们的所有Pod都会以动态库(Embeded Framework)的形式去构建,于是那些非开源的库(如 百度地图,微信分享)如果被多个Pod依赖(组件化开发中太常见了)于是被吸附到动态库里面,所以CocoaPod直接就不让我们install成功。因为你现在的依赖管理就是错误的。
在听取美团叶樉老师分享的时候 他们的出发点是因为要绕过苹果爸爸在iOS9以下对__text段60M的限制使用了动态库方案,我们是因为某些swift库必须要用到(历史遗留原因)动态库。美团的做法是摘除依赖关系,自定义CocoaPods(开源的本来就是用着不爽我就改)。但是我是个小菜鸡啊。我也不会ruby(以后会学的),但是叶樉老师给我提了别的idea。 前面我们知道 动态库和动态库是隔离性,动态库依赖静态库具有吸附性,那么我们可以自定义一个动态库把百度地图这种静态库吸附进来。对外整体呈现的是动态库特性。其他的组件依赖我们自定义的动态库,由于隔离性的存在,不会出现问题。
制作动态库
1 创建动态库项目这里以wx举例

2 按照微信的官方文档。添加依赖库(我是因为pod install巨慢 所以我直接拽进来了)

3 将wx的PublicHeader暴露出来,注意由于我并没有使用到wx相关API所以链接器帮我们链接动态库 的时候可能并不会把wx静态库吸附进来。我们手动在build Setting的other link flags加上-all_load标记

4.在Schema里面跳转编译配置为Release ,并且选择所有的CPU架构


5 然后选择模拟器或者Generic iOS Device运行编译就会生成对应版本的Framework了。

6.但是为了保证开发者使用的时候是真机模拟器都能正常使用,我们需要合并不同架构
这里在Build Phases里添加以下脚本,真机和模拟器都Build一遍之后就会在工程目录下生成Products文件夹,
if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework
DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework
SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"
lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"
open "${DEVICE_DIR}"
open "${SRCROOT}/Products"
fi

于是我们有了我们自己的私有动态库LJWXSDK,那么我们来验证我们之前的问题
首先指定一个LJWXSDK.podspec这里我直接传到了我的Github上面
#
# Be sure to run `pod lib lint LJPod.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'LJWXSDK'
s.version = '0.1.0'
s.summary = 'A short description of LJWXSDK.'
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/ValiantCat/LJWXSDK'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'ValiantCat' => '519224747@qq.com' }
s.source = { :http => 'http://onk2m6gtu.bkt.clouddn.com/LJWXSDK.framework.zip' }
s.ios.deployment_target = '8.0'
s.default_subspec = 'zip'
s.subspec 'zip' do |zip|
puts '-------------------------------------------------------------------'
puts 'Notice:LJWXSDK is zip now'
puts '-------------------------------------------------------------------'
zip.ios.vendored_frameworks = '*.framework'
end
end
注意上面我是把二进制压缩丢进了七牛的oss文件存储。毕竟免费还快。
然后通过pod lib create创建了一个pod用来验证之前我们的传递性依赖问题,
文件夹结构如下
.
├── Example
│ ├── LJA
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Images.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── LJA-Info.plist
│ │ ├── LJA-Prefix.pch
│ │ ├── LJAppDelegate.h
│ │ ├── LJAppDelegate.m
│ │ ├── LJViewController.h
│ │ ├── LJViewController.m
│ │ ├── en.lproj
│ │ │ └── InfoPlist.strings
│ │ └── main.m
│ ├── LJA.xcodeproj
│ ├── LJA.xcworkspace
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ │ ├── Headers
│ │ ├── LJWXSDK
│ │ │ └── LJWXSDK.framework
│ │ │ ├── Headers
│ │ │ │ ├── LJWXSDK.h
│ │ │ │ ├── WXApi.h
│ │ │ │ ├── WXApiObject.h
│ │ │ │ └── WechatAuthSDK.h
│ │ │ ├── Info.plist
│ │ │ ├── LJWXSDK
│ │ │ ├── Modules
│ │ │ │ └── module.modulemap
│ │ │ ├── _CodeSignature
│ │ │ │ └── CodeResources
│ │ │ └── read_me.txt
│ │ ├── Local\ Podspecs
│ │ │ ├── LJA.podspec.json
│ │ │ ├── LJB.podspec.json
│ │ │ └── LJWXSDK.podspec.json
│ │ ├── Manifest.lock
│ │ ├── Pods.xcodeproj
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace
│ │ ├── Target\ Support\ Files
│ │ │ ├── LJA
│ │ │ │ ├── Info.plist
│ │ │ │ ├── LJA-dummy.m
│ │ │ │ ├── LJA-prefix.pch
│ │ │ │ ├── LJA-umbrella.h
│ │ │ │ ├── LJA.modulemap
│ │ │ │ └── LJA.xcconfig
│ │ │ ├── LJB
│ │ │ │ ├── Info.plist
│ │ │ │ ├── LJB-dummy.m
│ │ │ │ ├── LJB-prefix.pch
│ │ │ │ ├── LJB-umbrella.h
│ │ │ │ ├── LJB.modulemap
│ │ │ │ └── LJB.xcconfig
│ │ │ ├── Pods-LJA_Example
│ │ │ │ ├── Info.plist
│ │ │ │ ├── Pods-LJA_Example-acknowledgements.markdown
│ │ │ │ ├── Pods-LJA_Example-acknowledgements.plist
│ │ │ │ ├── Pods-LJA_Example-dummy.m
│ │ │ │ ├── Pods-LJA_Example-frameworks.sh
│ │ │ │ ├── Pods-LJA_Example-resources.sh
│ │ │ │ ├── Pods-LJA_Example-umbrella.h
│ │ │ │ ├── Pods-LJA_Example.debug.xcconfig
│ │ │ │ ├── Pods-LJA_Example.modulemap
│ │ │ │ └── Pods-LJA_Example.release.xcconfig
│ │ │ └── Pods-LJA_Tests
│ │ │ ├── Info.plist
│ │ │ ├── Pods-LJA_Tests-acknowledgements.markdown
│ │ │ ├── Pods-LJA_Tests-acknowledgements.plist
│ │ │ ├── Pods-LJA_Tests-dummy.m
│ │ │ ├── Pods-LJA_Tests-frameworks.sh
│ │ │ ├── Pods-LJA_Tests-resources.sh
│ │ │ ├── Pods-LJA_Tests-umbrella.h
│ │ │ ├── Pods-LJA_Tests.debug.xcconfig
│ │ │ ├── Pods-LJA_Tests.modulemap
│ │ │ └── Pods-LJA_Tests.release.xcconfig
│ │ └── libWeChatSDK
│ │ ├── README.md
│ │ ├── WXApi.h
│ │ ├── WXApiObject.h
│ │ ├── WechatAuthSDK.h
│ │ └── libWeChatSDK.a
├── LICENSE
├── LJA
│ ├── Assets
│ └── Classes
│ └── LJA.m
├── LJA.podspec
├── LJB
│ ├── Assets
│ └── Classes
│ └── LJB.m
├── LJB.podspec
├── README.md
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj



测试工程我也丢在7牛上面。下载测试即可
编译运行。完美。我们又可以愉快的和swift第三方库配合使用。
很多人可能会问 诸如百度地图 微信这种sdk为什么官方不支持动态库版(所说的都是embeded Framework),猜测是为了兼容更低iOS7版本吧
很多人会觉得麻烦的要死。首先每个公司多多少少都有历史包袱,麻烦也要做,再者这是一次对基本功的补充,即便你们没有用到,但是为了学习,这篇教程所做的也值得你尝试一次。
剖析下动态库Framework吧
上述解决了我们一开始遇到的问题。but既然动态库和静态库压根就不一回事,所以里面还是有很多细节值得我们去了解的。
回过头来看Embened Framework
首先我们之前记得如果一个动态库加在LinkedFrameworksand Libraies程序启动就会报ImageNotFound,如果放在EmbededBinaries里面就可以。这是为什么呢。我们拿MacoView来看下两种情况下可执行文件的细节



其中@rpth这个路径表示的位置可以查看Xcode 中的链接路径问题
这样我们就知道了其实加在EmbededBinaries里面的东西其实会被复制一份到xx.app里面,所以这个名字起得还是不错的直译就是嵌入的框架
Why Swift does not Support Staic Libraies
造成这个的主要原因是Swift的运行时库(不等同于OC的runtime概念),由于Swift的ABI不稳定,静态库会导致最终的目标程序中包含重复的运行库,相关可以看下最后的参考文章SwiftInFlux#static-libraries。等到我们的SwiftABI稳定之后,我们的静态库支持可能就又会出现了。当然也可能不出,Swift伴随诞生的SPM(Swift,Package Manager),可能有更好的官方的包依赖管理工具。让我们期待吧。
CocoaPods 使用Use_framework!
既然加了Swift的第三方库之后就需要在Podfile里面加上use_framework! 那么CocoaPods就会帮我们生成动态库,但是奇怪的是,我们并没有在主工程的embeded binaries看到这个动态库,这又是什么鬼。其实是CocoaPods使用脚本帮我们加进去了。脚本位置在主工程的 build Phase下的 Emded Pods frameworks
"${SRCROOT}/Pods/Target Support Files/Pods-LJA_Example/Pods-LJA_Example-frameworks.sh"
动态库Framework的文件结构
.
├── Headers
│ ├── LJWXSDK.h
│ ├── WXApi.h
│ ├── WXApiObject.h
│ └── WechatAuthSDK.h
├── Info.plist
├── LJWXSDK
├── Modules
│ └── module.modulemap
└── _CodeSignature
└── CodeResources
- Headers 一般是头文件。非private里面的头文件都会在里面
- info.plist 配置信息,不深究
- Modules 这个文件夹里有个module.modulemap文件,后面在讲解
- 二进制文件,这就是上面提到的
不带 main的二进制文件了,.o的打包体 - _codeSignature 签名文件 (苹果爸爸的约束)
- more 资源文件。这里暂时没用到,所以没有 ,但是这个也是个大坑
更愉快的导入文件
@class,@protocol 不说了就是声明一个类,并不导入。
#import <>, #import""是加强版的#include<>,#include"" 防止重复导入的。
#import<> : 通过 build setting里面中的 header Search Path里面去找
#import"" : 第一步先搜索user Header search Path 再搜索 header search Path 。所以对我们的framework来说,CocoaPod 帮我们加到了 Header search Path 目前2种导入方式都是可以支持的。
上面的导入方式都带了 某个framework的路径 <XX/xx.h> "xx/xx.h" ,我们在开发自己主工程的时候会发现我们导入主工程其他类是不需要导入前缀的。 这又是怎么回事。
看下面的配置

还是不建议修改的好。
大家都知道iOS7之后多了@import,这又是什么鬼。
简单理解这个方式叫做Module导入,好处就是使用了@import之后不需要在project setting手动添加 framework,系统会自动加载,而且效率更高。
最主要的是swift也只能这样用。
导入的时候系统会查找如果有模块同名的文件就会导入这个文件。如果没有CocoaPods帮我们生成一个module-umbrela.hl文件,然后就是导入的这个文件。
回过头来看我们的framework的结构 里面有个Modules文件夹,里面有个文件module.modulemap
framework module LJWXSDK {
umbrella header "LJWXSDK.h"
export *
module * { export * }
}
我们可以看到其实被暴露的header就是这个文件,之前我在按照#import "/"的时候有个警告

而且按照@import导入的东西发现没有导入可用的头文件就是因为并没有在 umbrella header的头文件中加入其他头文件。
加入之后我们就可以完美的使用
@import ,并且#import"/" 也不会报warning更多关于
umbrella Header 参看文后参考
资源问题
首先我们来看常见的资源文件:主要分为图片和其他类资源那么加载图片和加载其他资源都是怎么做的?
1: [UIimage imageNamed:]
2: [NSbundle bundleForclass[XXX class]]
其实方式1去本质就是去mainBundle去拿资源,方式2从XXX所在的框架里面去拿。
前面也说道framework只是资源的打包方式,本质上是有两种的。
我们这个framework如果本质是静态库,那么无需改变使用方式,资源最终都会打包到Main Bundle里面
如果我们这个framework本质是动态库,那么我们的资源就发生了变化,资源就会被存放在 framework里面。所以我们需要使[NSbundle bundleForclass[XXX class]]。需要注意的是很多人为了简单,下意识的使用self class 传递,但是有可能这个self实例不在资源所属的framework。所以会出现资源加载 失败。一定要谨慎使用。
参考
程序员的自我修养,链接,装载 和库
iOS里的动态库和静态库
Systems Programming: What is the exact difference between Dynamic loading and dynamic linking?
CocoaPods 都做了什么?
Dynamic Linking of Imported Functions in Mach-O
OS里的导入头文件
iOS - Umbrella Header在framework中的应用
SwiftInFlux#static-libraries
iOS里的导入头文件
iOS - Umbrella Header在framework中的应用
@import vs #import - iOS 7
小礼物走一走,来简书关注我
写了 52388 字,被 2972 人关注,获得了 1196 个喜欢
學習了
可是還得努力 太深奧....
“workspace:一个包含多个target”这里是不是写错了,包含多个project吧
说的确实不错,但是有一个问题请教一下你,动态库对静态库具有吸附作用,你是通过把所有的静态库打成动态库,不错这样是可以解决的。但是你文章中提到可以通过修改cocoapods的源码来实现另外一种方式,请问可以说的详细一点吗?
#pre_install do |installer|
# # workaround for https://github.com/CocoaPods/CocoaPods/issues/3289
# def installer.verify_no_static_framework_transitive_dependencies; end
#end
这个吧,这个应该就是摘除静态库的依赖关系
'FRAMEWORK_SEARCH_PATHS' => '$(inherited) $(PODS_ROOT)/xxxx
}路径啊?
'FRAMEWORK_SEARCH_PATHS' => '$(inherited) $(PODS_ROOT)/BaiduMapKit/BaiduMapKit',
}需要这样设置 我的cocopods私有库(xxx) 包含BaiduMapKit,BaiduMapKit里面包含多个静态的framework
楼主你的意思是不是动态库只要不进行动态更新,其实是可以过审核的?急 在线等
寒哥,符号决议怎么理解
我理解是比如我们有A,B两个动态库。然后各自都需要饮用同一个第三方。之后在工程中同时引用A,B。导致的上面问题。这个问题换个角度想这样设计不利于控制第三方版本。所以正确的做法也应该像最后的结论那样。包装一层,这样第三方更新版本,我们也只需更新自己的包装lib就好了。
666学习了 老乡
毕竟寒神
受教了 寒神
2019年2月26日 阴天 今天来告诉你一些小秘密吧,可都是真实的哟。 就像你看到的,这是我目前的情况,写了6.7万字,攒了9787个喜欢,有了1499个粉丝,一共存了5553个钻。 如果你问我来简书多久了? 时至今日,我算算,有四个月了,当然和别人相比,无论是钻,还是名气,可能早就超过我了。但我想说的是,对于一向被动的我,能这样,我也不委屈了。 如果你问我那些钻怎么来的?自己掏了多少钱买钻? 我拍拍头,算了下,总共花了188元,买了一年的会员,当时送的是700个钻。 你肯定会说,四个月凭着700个钻的底,能赚5553个钻么? 我现在就告诉你怎么来的: 第一个1000钻,是写文攒的...
澜嫣农历二月十五日,家乡常有采茶灯等文娱活动,俗称“掉采茶”。以惠山和北乡士桥等为最盛,家乡老话有“二月半,采茶看”说法。 明朝时候起,家乡还有一风俗,农历二月中旬夜晚到北塘看香灯。 北塘香灯的来历,是明成祖朱棣即位后,在湖北武当山大造道观,当时松江、苏州两府各县乡民逐渐形成到武当山朝山进香的习俗。 这两地的进香船要于农历二月中旬在家乡北塘黄埠墩一带集合,然后一起出发。 所以每年一到此时,北塘沿运河两岸便有数以百计的进香船开来,锣鼓一响,便停泊下来。然后悬香灯为记,挂在船桅上。 所挂香灯以进香人数为准,每一个进香人,都要悬一盏香灯,船上有多少人,就要挂多少香灯。香灯形状为四方形、八角形等,在...
乐道也(续接上文) 餐桌上杯盘狼藉,吃饭的人打着饱嗝,脸上露出惬意的微笑。多美的一顿大餐,直吃得沟满壕平,惬意舒心。大媒人找根扫把棍习惯性的剔着牙,不时地从口腔里吐出塞在牙里的残留物。美娇娘见此情景,一阵恶心。身子一扭,脸冲外,看着大街上过往的行人。 桌子底下,老地主早已鼾声如雷。他今天太高兴了,见到了朝思暮想的可心的人儿,再加上美人一阵撩拨,真是人逢喜事就忘乎所以啊!竟然糊喝得酊大醉,靠在椅子上呼呼大睡。 睡梦中,老地主仿佛看到一辆三驾马车,缓缓驶来,马脖子上的铜铃,叮咚作响,听来是那样的清脆悦耳。马车渐渐近了,就见赶车的把式正是那位大媒人。只见他一身土布新衣,干净利索。头上围一块白色的毛巾...
秋风宜人命运的一语成真 小时候,我和姐姐曾跟随父亲去算命。 父亲刚刚出了车祸,一只胳膊不能动,在家休养,又因为那一段时间接连遭遇了好几起祸事,自认霉星高照,急需人转运避凶。而我和姐姐,只是顺带的。 那人远近闻名,相当有大师范,须发全白,仙风道骨的。 他家在一个村子里,房子倒也与别家一般,不过门前有一颗大树,盘根错节,枝繁叶茂,树上挂满了红布条,树根处有一根结呈碗状,里面总是装满了水。 每每有人来挂布条,然后盛一些水走,说是可以治病。 那日我们早早的去了,院子外已经排了不少人。等到中午,才轮到我们。 一进屋,先生看了我父亲半晌,说:我知道你为什么而来,但是你的事,我看不了。你不是普通人,你的事轮不...
半瓶碳酸练习基本功,对初学写作者来说,是很重要的事。 一、先练习写一人一事 有些人常常说:“我有一肚子故事,就是写不出来!”这是怎么回事呢?你若追问他:那些故事中的人都有什么性格?有哪些特点?他就回答不上来了。他告诉你的尽是一些新闻,一些事情,而没有什么人物。实际上他并没有一肚子故事。尽管他生活在工厂里、农村里,身边有许多激动人心的新人新事,可是他没有仔细观察,人与事都从他的身边溜走了;他只记下了一些破碎不全的事实。 要想把文章写好,要先从练习写一个完完整整的人、一件完完整整的事做起。你要仔细观察身旁的老王或老李是什么性格,有哪些特点,随时注意,随时记录下来。这样的记录很重要,它能锻炼你的文字表...
写作的技巧
188道iOS中高级核心面试题,我只能帮你在这里了!
在2018年底,小编混迹在各种iOS交流群中,整理出了将近两百道大厂最喜欢在面试问到的问题,今天在这里分享给大家[免费获取方式在最后]! 小编就不在这里全部列举出来了,可以在前面的看到,文档里面包括了目前比较受欢迎的所有面试题!有需要获取的话,可以加QQ群:67988454...
直接复制“WYBasisKit”到项目中,在需要使用的地方引入“GlobalHeader”头文件(后续会添加cocoapods支持)。 "WYBasisKit"是做什么的? "WYBasisKit" 不仅可以帮助开发者快速构建一个工程,还有基于常用网络框架和系统API而封装...
iOS开发笔记(十一)— UITableView、ARC、xcconfig、Push
前言 分享iOS开发中遇到的问题,和相关的一些思考,本次内容包括:UITableView滚动问题、ARC、xcconfig、Push证书。 正文 UITableView UITableView在reloadData 的时候,如果height的高度发生较大变化,content...
美团 EasyReact 源码剖析:图论与响应式编程
前言 🎉 🎉 🎉庆贺本文加入美团 EasyReact GitHub 相关文章 🎉 🎉 🎉 18 年 7 月美团开源了 EasyReact,告知 iOS 工程师们响应式编程和函数式编程并非不可分离,似乎一出来就想将 ReactiveCocoa 踢出神坛。该框架使用图论来解决...
深入iOS系统底层之映像操作API介绍
绿树阴浓夏日长,楼台倒影入池塘。--《唐高骈·山亭夏日》 mach-o文件和进程的映像(image) iOS系统生成的可执行程序或者动态库文件的存储布局格式被称之为mach-o格式。文件中存放着程序的代码和数据,而程序运行时系统会为其建立一个进程,以及分配虚拟内存空间。同时...
最近心里感觉好累,好累。压力太大,。感觉力不从心,好想离开,却又不得不留在这里。面对这些冷淡的人们。没有一点感恩的心,我真的好不喜欢,不喜欢这里的一切,不喜欢这里的人,没有上进心,不尊重同事,总是这里痛那里疼,各种病症,也不知道是真的还是装的。我不想理会,真的没什么大不了。...
Mac 虚拟机 CentOS7 走起(一)
1,安装前准备: 下载 Oracle VM VirtualBox 虚拟机下载 CentOS7 系统 2,开始安装: 2.1,打开 VirtualBox 新建虚拟电脑。 2.2,填写名称-类型-版本,CentOS 属于 Red Hat 的发行版。2.3,选择系统运行内存大小。...
摘要:JSON,说白了就是JavaScript用来处理数据的一种格式,这种格式非常简单易用。JSON大部分都是用来处理JavaScript和web服务器端之间的数据交换,把后台web服务器的数据传递到前台,然后使用JavaScript进行处理,例如ajax等 JSON支持的...
T









浙公网安备 33010602011771号