<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Hexo</title>
  
  
  <link href="https://wkjsos.github.io/atom.xml" rel="self"/>
  
  <link href="https://wkjsos.github.io/"/>
  <updated>2022-04-11T10:05:31.377Z</updated>
  <id>https://wkjsos.github.io/</id>
  
  <author>
    <name>wkj</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>产品-阶段性总结(三)</title>
    <link href="https://wkjsos.github.io/2022/04/11/stage-summary/"/>
    <id>https://wkjsos.github.io/2022/04/11/stage-summary/</id>
    <published>2022-04-11T08:33:06.000Z</published>
    <updated>2022-04-11T10:05:31.377Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>独立产品研发系列文章第三篇</p></blockquote><p></br></p><span id="more"></span>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;独立产品研发系列文章第三篇&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="产品" scheme="https://wkjsos.github.io/tags/%E4%BA%A7%E5%93%81/"/>
    
  </entry>
  
  <entry>
    <title>产品-如何着手做自己的产品(二)</title>
    <link href="https://wkjsos.github.io/2022/04/08/make-your-own-product/"/>
    <id>https://wkjsos.github.io/2022/04/08/make-your-own-product/</id>
    <published>2022-04-08T06:06:33.000Z</published>
    <updated>2022-04-20T03:08:49.767Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>独立产品研发系列文章第二篇</p></blockquote><p>上篇文章从 iOS 开发的视角介绍了独立 App 从确定需求、开发、上线中所用到的一些资源。因为各项资源的限制，也只能完成一些仅上架于苹果 Appstore 的工具类 App。</p><p></br></p><p>这篇文章就着眼于一个具备完善功能的全平台产品的研发，研发过程中我们碰到的一些问题及其解决方案。在这里给大家分享一下我是如何一步步构建自己 <strong>公众号、小程序 + App 完整产品生态</strong> 的。</p><p></br></p><span id="more"></span><p></br></p><h2 id="产品目标">1、产品目标</h2><p>产品目标往大了说，是指产品实现的价值。即使有用户场景存在，但做出来的产品没有价值（不能盈利，用户量极少），那也就没有存在的必要。</p><p>甚至再大点，从 <strong>产品的愿景</strong> 讲起...</p><p></br></p><p><strong>打住，先不用考虑这么多，不然你的产品可能就只能存在于你的不停设想中了。</strong></p><p></br></p><p>在资源有限，人力有限，投入有限的情况下，我们的产品目标就很明确了，它要解决哪些问题？或者它需要完成的功能！</p><p></br></p><p>比如说，我在做 <strong>文件管理器</strong> 的时候，MVP 的产品目标就很明确：实现手机和电脑之间的文件传输。</p><p></br></p><p>在完成了 MVP 需求之后，自然而然的带来了一些其他需求，有的来自我自己的产品计划，更多的来自于用户反馈：</p><ul><li>1.0.0 完成手机和电脑之间的传输</li><li>1.1.0 实现图片、视频、音频文件的预览或者播放</li><li>1.2.0 实现文件分组、移动、删除、压缩、分享功能</li><li>1.3.0 优化播放器</li><li>1.4.0 完成安全验证功能：手势验证码、生物认证</li><li>2.0.0 添加内购功能</li><li>2.1.0 完成文件扫描功能</li><li>....</li></ul><p></br></p><p><img src="/images/preview-1.jpg" /></p><p><img src="/images/preview-2.png" /></p><p></br></p><blockquote><p>明确一个目标，它不需要很大，但可以实施，一次聚焦一个点，错了也先坚持下去（不做完你怎么知道是错的呢 ◔ ‸◔？），把它完成，然后复盘。</p></blockquote><p></br></p><h2 id="产品原型">2、产品原型</h2><p></br></p><p>在上篇文章中，我有提到自己其实尝试了很多原型设计工具，最后结论是 <strong>设计原型的时间都足够自己使用 xib 将页面搭建完成了</strong>。</p><p>在这里推翻一下自己 ,,Ծ‸Ծ,,</p><p></br></p><p>在做个人工具类 App，且毫无后续规划，只想着将其完成上线时，确实会带来这种想法。</p><p></br></p><p>随着产品的迭代，这种毫无原型规划，做一步考虑一步的处理方式，会给自己未来的开发造成很大的困扰。<strong>设计原型的过程其实是梳理产品逻辑的过程</strong>。</p><p></br></p><p>推荐几款 <a href="https://modao.cc/prototyping/prototyping-tools.html">原型设计工具</a>，我目前常用的可能就是 <a href="https://modao.cc/dashboard/me">墨刀</a> 了。</p><p></br></p><h2 id="项目管理">3、项目管理</h2><p>在经历了独立的 iOS App 开发上架之后，我发现了一个很严重的问题。就是 <strong>毫无项目管理概念</strong>，有时间了做一下，想起来了做一下，偷懒了就不做了。</p><p></br></p><p>因为是自己的产品，自己设计，自己开发，且 MVP 版本大概率没有付费用户，也就没有给用户负责的心态。</p><p></br></p><p>这其实会严重阻碍项目进度，长期的无计划开发，还会造成项目流产。</p><p></br></p><p>在公司，经历了几年的迭代管理，发现使用项目管理工具进行个人项目的管理还是行之有效的。当我们有了明确的产品目标，有了对应逻辑自洽的原型，进而将任务进行拆解，就顺理成章了。</p><p>这里我用的是 <a href="https://coding.net/">coding.net</a> 来进行项目管理，同时它也可以用来做代码托管。</p><p></br></p><p><img src="/images/demand.png" /></p><p>将任务项按照小时的粒度拆分，一方面方便任务管理，另一方面也方便测试 case 的编写。在正式编码前，我目前的习惯是先将测试 case 写完，<strong>这很有必要</strong>。</p><p></br></p><p>原型是对产品逻辑的梳理，测试 case 则是对研发细节的梳理，完整的测试 case 可以引导我们完成正确的业务逻辑 ✅，也能避免很多业务错误 ❌。</p><p></br></p><h2 id="技术选型">4、技术选型</h2><p></br></p><p>好了，到目前为止，我们做的都是一些准备工作，仍然没有进入到开发中。很关键的问题在于，开发只有自己，要完成文章开头的目标：<strong>公众号、小程序 + App 完整产品生态</strong>。</p><p></br></p><p>我们需要同时具备小程序、iOS 开发、Android 开发技能。编程语言的上手难度不高，问题在于同样的业务，需要三次开发成本。<strong>这显然不太现实</strong>。</p><p></br></p><p>通常情况下，我们可能会选用 web 来实现多平台的使用体验，甚至于抛弃 App，只做公众号 + 小程序生态。在产品初期，产品需求快速验证的情况下，这种方式也是非常可取的一种方案。</p><p></br></p><p>其实，稍微搜索一下就会发现，市面上还是有挺多跨平台技术实现的。要做到 App + 小程序，三端一套代码，可以用 <a href="https://uniapp.dcloud.io/">Uni-App</a> 来实现。它使用的是 <a href="https://vuejs.org/">Vue.js</a> 前端框架，iOS 和 Android 则借助了 <a href="https://github.com/alibaba/weex">Weex</a> 实现。</p><p>用于做一些个人产品，或者小型商业化产品足够了。</p><p></br></p><h2 id="数据管理">5、数据管理</h2><p></br></p><p>小程序 + App 的实现方案解决了之后，还有另外一个关键问题，<strong>数据存哪？</strong></p><p></br></p><p>如果只是一个工具类产品，那么数据存在 App 本地即可。但我们的目标是一个具备完善用户体系的信息平台，甚至于较为复杂的用户 UGC 社区怎么办呢？</p><p></br></p><p>Uni-App 提供了自己的 uniCloud 服务，使用 js 即可完成服务器开发工作，小程序和 App 可以免域名使用服务器。</p><p></br></p><p>在这里，我选用了另外一种做法，将数据托管到 serverless 平台 <a href="https://console.leancloud.cn/apps">LeanCloud</a>，小程序和 App 通过 LeanCloud 的 js sdk 访问托管数据库。</p><p>其开发版可支撑 3 万次 / 天的免费 Api 访问，同时也提供短信服务。可以顺利完成用户体系建设、用户数据上传、平台数据的拉取。</p><p>当超过开发版的访问次数之后，说明你的产品也有了一定规模的用户量，相信这时候，流量带来的收入也足够你开通<strong>按量付费</strong>的商用版了。</p><p></br></p><p><strong>数据来源之骚操作</strong></p><p>如果你想做一个天气 App，或者咨询类 App，没有天气和资讯类数据怎么办呢？</p><p></br></p><p>国内有很多 Api 市场平台，比如：</p><p><a href="https://market.aliyun.com/products/56956004/?spm=5176.product-detail.102.3.363f7946AL2Ypk">阿里云 Api 市场</a></p><p><a href="https://market.cloud.tencent.com/categories/1006">腾讯云 Api 市场</a></p><p><a href="https://apis.baidu.com/">百度云 Api 商城</a></p><p><a href="https://www.juhe.cn/apiservice?">聚合数据</a></p><p><a href="https://market.easyapi.com/">EastApi</a></p><p>等等</p><p></br></p><p>这些市场一方面提供了收费 Api 服务，另一方面也提供免费的 Api。</p><ul><li><p>如果你的产品有很好的流量变现方式，可以考虑使用收费 Api。</p></li><li><p>如果没有，可以考虑白嫖。但 100 次的试用，测试阶段就很快消耗完了。怎么办呢？</p></li></ul><p></br></p><p>这里就可以将 serverless 数据库托管平台 leancloud 和 Api 商城结合起来使用，以每日天气为例，如果 serverless 数据库托管平台没有查询到数据，则可以请求 Api 商城，之后将数据存入到数据库托管平台。</p><p>可是，100 次还是不足以覆盖所有城市怎么办呢？发挥白嫖精神，多申请几个平台吧。</p><p></br></p><h2 id="上线运营">6、上线运营</h2><p></br></p><p>至此，我们搞定了小程序、App 以及 Server 的开发工作。<strong>上线怎么办呢？</strong></p><p></br></p><p>小程序比较好说，<a href="https://mp.weixin.qq.com/">微信公众平台</a> 按照引导一步步申请小程序即可。</p><p>iOS 上架需要一个付费的开发者账号，这是一个门槛，长期运营的话，还是建议在<a href="https://developer.apple.com/">苹果开发者官网</a>申请一个账号，现在申请比较简单，苹果手机下载 Developer App 就可以付费了。</p><p>Android 上架则需要<a href="https://www.ccopyright.com.cn/index.php?optionid=1033">软件著作权</a>了，正常申请可能需要 1-2 两个月，也可以付费找三方中介完成。</p><p></br></p><p>继续发挥白嫖精神，App 和 小程序配置的一些运营位，图片或者音视频资源存放在什么地方呢？</p><p></br></p><p><strong>公众号媒体资料库</strong>可以解决这个问题。将图片和音视频资源存到公众号后，获取原链接，App 和 小程序即可访问了。</p><p></br></p><h2 id="获取收益">7、获取收益</h2><p></br></p><p>较为简单的流量变现方式就是 <strong>广告</strong> 了。</p><p></br></p><p>可现在各平台对于流量主的接入要求越来越高，个人开发者基本很难完成广告接入。</p><ul><li><p>Uni-App 提供了一键接入功能，具体可以参考 <a href="https://uniad.dcloud.net.cn/login">文档</a>。</p></li><li><p>公众号积累粉丝后，也可以开通文章内广告位。</p></li><li><p>小程序达成 1000 个 UV 访问后，可开通流量主权限。</p></li></ul><p></br></p><h2 id="小结">8、小结</h2><p></br></p><p>至此我们就完成了文章开头介绍的 <strong>公众号、小程序 + App 完整产品生态</strong>。</p><p>按照以上步骤，我也完成了三个产品的操作，可以扫码体验下：</p><p></br></p><p><img src="/images/public-qr.png" /></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;独立产品研发系列文章第二篇&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;上篇文章从 iOS 开发的视角介绍了独立 App 从确定需求、开发、上线中所用到的一些资源。因为各项资源的限制，也只能完成一些仅上架于苹果 Appstore 的工具类 App。&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;这篇文章就着眼于一个具备完善功能的全平台产品的研发，研发过程中我们碰到的一些问题及其解决方案。在这里给大家分享一下我是如何一步步构建自己 &lt;strong&gt;公众号、小程序 + App 完整产品生态&lt;/strong&gt; 的。&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="产品" scheme="https://wkjsos.github.io/tags/%E4%BA%A7%E5%93%81/"/>
    
  </entry>
  
  <entry>
    <title>iOS 无埋点(全埋点)实践</title>
    <link href="https://wkjsos.github.io/2020/01/09/full-buried-point-statistics/"/>
    <id>https://wkjsos.github.io/2020/01/09/full-buried-point-statistics/</id>
    <published>2020-01-09T10:48:54.000Z</published>
    <updated>2022-04-21T11:09:40.638Z</updated>
    
    <content type="html"><![CDATA[<p>客户端埋点大概分为三类：</p><ul><li>代码埋点</li><li>可视化埋点</li><li>无埋点</li></ul><p></br></p><p>这篇文章主要介绍了 <strong>无埋点</strong> 的想法和实践过程。</p><p></br></p><span id="more"></span><h2 id="代码埋点">1、代码埋点</h2><p></br></p><p><strong>代码埋点</strong>，即在需要埋点的节点调用接口直接上传埋点数据，第三方数据统计服务商也大都提供了代码埋点的 <code>api</code>，非常方便。</p><p></br></p><p>但是带来一个问题，埋点代码散落在业务的各个地方，和业务耦合严重，尤其是在页面改版，业务变动的过程中，旧的埋点不知道怎么处理，新的埋点不知道需不需要，当埋点数量上来之后，对散落的埋点代码的维护是个灾难。 当然你可以通过宏、工厂类去简化埋点代码，但并不能改变什么</p><p></br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">- (void)viewDidDisappear:(BOOL)animated &#123;</span><br><span class="line">    [super viewDidDisappear:animated];</span><br><span class="line"></span><br><span class="line">    // 埋点</span><br><span class="line">    APE(kEvent4001);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></br></p><h2 id="可视化埋点">2、可视化埋点</h2><p></br></p><p>可视化埋点，即通过可视化工具配置采集节点，在前端自动解析配置并上报埋点数据，从而实现所谓的“无痕埋点”， 代表方案是已经开源的 <a href="https://github.com/mixpanel">Mixpanel</a>。</p><p></br></p><h2 id="无埋点">3、无埋点</h2><p></br></p><p>无埋点或者叫做全埋点，它并不是真正的不需要埋点，而是采集全部事件上报。剩下交给服务器做过滤，筛选出有用的数据。</p><p></br></p><p>无埋点进一步优化，可以通过服务器下发配置文件，直接由前端进行事件过滤。</p><p></br></p><p><del>为了 kpi</del>，基于无埋点的思想，造了一个轮子 <a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a>。</p><p>这里把 <a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 实现中碰到的一些问题与想法做一下记录。</p><p></br></p><p>实现思想很简单，所有代码如下：</p><p></br></p><p><img src="architecture.jpg" /></p><p></br></p><p>其中 <code>WKTrackingDataManager</code> 负责所有的数据管理，事件追踪配置。</p><p></br></p><p><img src="usage.jpg" /></p><p></br></p><p><code>WKTrackingDataViewPathHelper</code> 负责 <code>event_path</code> 生成。</p><p><code>AOP</code> 模块负责所有事件的追踪。</p><p></br></p><blockquote><p>详细的用法可以点击 <a href="https://github.com/wkjsos/WKTrackingData">github</a> 查看。</p></blockquote><p></br></p><h4 id="如何唯一标示某个事件">如何唯一标示某个事件？</h4><p></br></p><p>这里涉及两个问题，一是事件怎么表示？二是如何确保事件的唯一性？</p><p></br></p><h5 id="事件的表示">事件的表示</h5><p></br></p><p>对于事件的表示，使用了 <code>event_path</code> 实现。核心思想是对于触发了某个事件的 <code>responder</code> ，顺着其响应者链条，构建出其响应者链条的 <code>path</code>。</p><p></br></p><p>生成的响应者 <code>path</code> 如下： <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;event_path&quot; = &quot;#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate</span><br></pre></td></tr></table></figure> </br></p><h5 id="唯一标示某个事件">唯一标示某个事件</h5><p></br></p><p>这里有个问题是，某些业务场景下，同一个 <code>button</code> 或者其他控件，会因为其某些属性的改变，在业务上表示的是多种不同的事件。</p><p></br></p><p>如，首页一个 <code>button</code> 在未登录时显示 <strong>点击登录</strong>，登录未实名时显示 <strong>去实名</strong>等等。</p><p>那么对于这同一个 <code>button</code> 来说，它的视图树并未发生改变，生成的响应者 <code>path</code> 就是相同的。</p><p></br></p><p>类似的业务场景还有 <code>UISwitch</code> 的开关，<code>UISegmentedControl</code> 的 <code>indexSelect</code>，<code>UIStepper</code> ，以及 <code>UITableView</code> 和 <code>UICollectionView</code> 的 <code>cell</code> 点击。</p><p>针对于这种情况，<a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 在生成 <code>event_path</code> 时，有选择的将控件自身的不同属性也拼接上，生成的 <code>event_path</code> 就变成了这样：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&quot;event_path&quot; = &quot;#buttonClick:#UIButton#UIButton[0]#UIView[0]#ViewController#...#UIApplication#AppDelegate#currentTitle=Button#state=1#enabled=1#selected=0&quot;;</span><br></pre></td></tr></table></figure><p></br></p><h5 id="业务扩展">业务扩展</h5><p></br></p><p>继续考虑另外一种业务场景，首页有一个 <code>banner</code> 轮播图，</p><p><code>banner</code> 每一个广告位的图片和跳转 <code>url</code> 都是由服务器下发的，且位置可配置。</p><p>这时 <code>banner</code> 的 每一个 <code>index</code>，对应什么页面都是不固定的，<strong>0</strong> 位所对应的事件，由 <code>event_path</code> 是无法确定的。</p><p></br></p><p>这时就需要拼接上具体的业务参数，才能够唯一标示某个事件，如 <code>url</code>。</p><p></br></p><p><a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 也提供了业务方的参数扩展，允许业务方拼接上自定义参数：</p><p></br></p><p><img src="additional.jpg" /></p><p></br></p><p>对于不希望进行事件追踪的控件，可以通过 wk_ignoreTracking 进行忽略：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">self.slider.wk_ignoreTracking = YES;</span><br></pre></td></tr></table></figure><p></br></p><h4 id="其他问题">其他问题</h4><p></br></p><p>在对于不同事件的追踪上，<a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 基于面向切面的思想，使用 <code>runtime</code> 直接做方法交换。</p><p></br></p><p>但是对于 <code>UIAlertView</code>、<code>UIActionSheet</code>、<code>UITableView</code>、<code>UICollectionView</code> 的统计，需要交换其 <code>delegate</code> 的方法。</p><ul><li><p>如果其 <code>delegate class</code> 已经实现了相应的方法，那么直接交换即可。</p></li><li><p>如果其 <code>delegate class</code> 未实现相应的方法，这时仍然想要追踪到这些事件，那么就需要手动添加一下对应的 <code>delegate  method</code> 的实现。</p></li></ul><p></br></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">- (void)wk_swizzleInstanceSelector:(SEL)origSel_ fromClass:(Class)fromClass replaceSelector:(SEL)replaceSel_ originNotImp:(SEL)notImpSel_ &#123;</span><br><span class="line">    </span><br><span class="line">    Method originalMethod = class_getInstanceMethod([self class], origSel_);</span><br><span class="line">    </span><br><span class="line">    if (originalMethod) &#123;</span><br><span class="line">        [self wk_swizzleInstanceSelector:origSel_ fromClass:fromClass replaceSelector:replaceSel_];</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        Method notImpMethod = class_getInstanceMethod(fromClass, notImpSel_);</span><br><span class="line"></span><br><span class="line">        // 如果delegateClass没有实现 origSel_ 方法</span><br><span class="line">        // 则给delegateClass的 origSel_ 添加 orginReplaceMethod 的实现</span><br><span class="line">        BOOL didAddNotImpMethod =</span><br><span class="line">        class_addMethod([self class],</span><br><span class="line">                        origSel_,</span><br><span class="line">                        method_getImplementation(notImpMethod),</span><br><span class="line">                        method_getTypeEncoding(notImpMethod));</span><br><span class="line">        if (didAddNotImpMethod) &#123;</span><br><span class="line">            NSLog(@&quot;%@ did add not imp method %@&quot; , NSStringFromClass([self class]) , NSStringFromSelector(notImpSel_));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p></br></p><blockquote><p><a href="https://github.com/wkjsos/WKTrackingData/blob/master/WKTrackingData/WKTrackingData/Core/Util/NSObject%2BSwizzling.m">具体实现在这里</a></p></blockquote><p></br></p><p>但在实践过程中发现，<code>UITableView</code>、<code>UICollectionView</code> 的 <code>delegate</code> 对象，在未实现相应方法时，手动给 <code>tableView:didSelectRowAtIndexPath:</code> 添加了 <code>implementation</code>，仍然不会触发。</p><p></br></p><p>我们先来看一下，正常 <code>tableView:didSelectRowAtIndexPath:</code> 调用栈：</p><p></br></p><p><img src="call-stack.jpg" /></p><p></br></p><p>这里面，<code>UITableView</code> 前后会调用这四个方法，进行 <code>cell</code> 点击的响应。</p><ul><li>1、[UITableView _userSelectRowAtPendingSelectionIndexPath:]</li><li>2、[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:]</li><li>3、[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:]</li><li>4、[TableViewController tableView:didSelectRowAtIndexPath:]</li></ul><p></br></p><p>根据调用顺序可以发现 <code>UITableView</code> 在真正调用 <code>delegate class</code> 的 <code>tableView:didSelectRowAtIndexPath:</code> 前，会先触发自己的 <code>_selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:</code> 方法。</p><p></br></p><p>直接给 <code>_selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:isCellMultiSelect:</code> 打一个断点进入汇编查看，发现：</p><p></br></p><p><img src="assembly.jpg" /></p><p></br></p><p>如果 <code>delegate class</code> 如果有实现 <code>tableView:didSelectRowAtIndexPath:</code> ，则一切正常，会完成 <code>tableView:didSelectRowAtIndexPath:</code> 的调用。</p><p>但如果 <code>delegate class</code> 未实现 <code>tableView:didSelectRowAtIndexPath:</code> ，即便手动给 <code>tableView:didSelectRowAtIndexPath:</code> 添加 <code>implementation</code>，也不会尝试响应 <code>tableView:didSelectRowAtIndexPath:</code> 了。</p><p></br></p><p>跟随汇编调用发现，会直接 <code>jump</code> 到另外一个指令。</p><p></br></p><p><a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 这里换了种做法，直接对 <code>[UITableView _userSelectRowAtPendingSelectionIndexPath:]</code> 进行了交换（<code>UICollectionView</code> 类似）。 实现了 <code>UITableView</code>、<code>UICollectionView</code> 的 <code>delegate</code> 对象，在未实现相应方法时对 <code>cell</code> 事件的追踪。</p><p></br></p><h2 id="小结">4、小结</h2><p></br></p><h4 id="多个event_path对应同一个事件">多个event_path对应同一个事件</h4><p></br></p><p>考虑另外一种情况，线上存在多个版本，不同版本的页面都有一些细微的差别，那么对于同一个事件来说，它就可能存在多个 <code>event_path</code>。</p><p>在实践过程中，这一块的映射，就需要服务器同学的配合了，可以让服务器做成后台可配置的。</p><p></br></p><h4 id="事件统计优化">事件统计优化</h4><p></br></p><p>在 <strong>业务扩展</strong> 中提到了如何添加业务参数，<a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 使用了分类实现。</p><p>在实践过程中发现，全埋点的方案，造成了大量流量的浪费，有好多事件不需要啊[(ŏ_ŏ)]。</p><p></br></p><p>进一步优化，可以由服务器下发配置文件，里面直接包含了，希望客户端上报的所有事件，只有和配置文件中符合的 <code>event_path</code> 才进行上报。</p><p>这一步的业务参数获取，也可以不采用 <a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 的实现，直接使用 <code>kvc</code> 获取。</p><p>形如： <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">key_path = &quot;viewController.banner.url&quot;</span><br></pre></td></tr></table></figure></p><p></br></p><p><strong>看到这里就点开 <a href="https://github.com/wkjsos/WKTrackingData">WKTrackingData</a> 给个star吧，有问题可以在guthub上提issue，或者下方评论～</strong></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;客户端埋点大概分为三类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码埋点&lt;/li&gt;
&lt;li&gt;可视化埋点&lt;/li&gt;
&lt;li&gt;无埋点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;
&lt;p&gt;这篇文章主要介绍了 &lt;strong&gt;无埋点&lt;/strong&gt; 的想法和实践过程。&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="iOS" scheme="https://wkjsos.github.io/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>产品-独立App研发心路历程(一)</title>
    <link href="https://wkjsos.github.io/2017/09/01/write-a-personal-app/"/>
    <id>https://wkjsos.github.io/2017/09/01/write-a-personal-app/</id>
    <published>2017-09-01T03:08:08.000Z</published>
    <updated>2022-04-19T03:18:46.035Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>独立产品研发系列文章第一篇</p></blockquote><p>作为一个移动端的开发者，最大的愿望当然是<strong>写一个app坐等收钱了( ͡° ͜ʖ ͡°)</strong> 。</p><p>那么在独立完成一个app的路上会碰到些什么问题呢？ 最近上线了一个自己的 App，在这里把碰到的问题和自己的解决方案分享给大家。</p><p></br></p><span id="more"></span><h2 id="确定需求">1、确定需求</h2><p></br></p><p>每个人想做独立 App 的出发点可能都会略有不同。</p><p>往大了说，看到了一个人们迫切的需求，这个需求的解决方案可以改变世界（就差一个程序员了）。</p><p>往小了说，就是想在 AppStore 上看到自己的 App，它可能只是新技术的试水之作（ iOS8 的 today extension 出了之后，AppStore上出现了很多特色的基于 today extension的 App），也可能是真正能解决一些自己生活上某些不方便的辅助 App（文件操作类app，各种功能贴心的天气类应用）。</p><p><strong>确定一个需求，不管大小，这是能支撑你一直做下去的动力。</strong></p><p></br></p><p>我自己基于媳妇的需求，做了一款文件操作管理的 App ----“文件管理器”。</p><p>“文件管理器” 将手机作为服务器，允许其他同wifi下的终端能够通过浏览器管理操作手机上的文件，可以通过浏览器进行文件夹分组，文件的拷贝、移动、下载与上传。</p><p>当确定了这个需求点之后，前前后后做了差不多三个月时间，才把这个项目上线。（ 顺便学习了下swift )</p><p></br></p><h2 id="app-的原型设计和界面交互">2、App 的原型设计和界面交互</h2><p></br></p><p>当有了需求及其解决方案之后，一些模糊的原型和用户操作脑海里也会有一些概念。下面就是将具体的页面与交互确定下来了。</p><p>笔者也尝试了几个原型设计工具，然后放弃了。</p><p>尝试过程中发现，熟悉原型设计工具并使用其将页面设计出来耗费的时间，已经足够我使用xib把界面搭好了。之后笔者使用纯手工画草稿的方式，解决了自己原型的问题。</p><p></br></p><blockquote><p>后来做其他产品的过程中，发现原型还是很有必要的，这其实是一个逻辑梳理，逻辑自洽的过程。</p></blockquote><h2 id="app-的小图标怎么办">3、App 的小图标怎么办？</h2><p></br></p><p>确定了原型之后，整个项目的框架，自己内心也大致有了个概念。抛开代码层面的问题，还剩下一个独立开发者怎么都绕不过去的问题！！</p><p><strong>项目中使用的各种小图标怎么办？</strong></p><p>在这里给大家推荐几个笔者使用过的图标库：</p><ul><li><a href="https://www.iconfont.cn/">阿里爸爸的图标库</a> -&gt; 整套整套的矢量图标，随意搭配颜色和大小，业界良心呐。当然如果是商用还是要获得作者授权的～</li><li><a href="https://icons8.com/">icons8</a> -&gt; 56,500 Free Flat Icons！！各种平台下的图标都有，还有mac版软件哦～</li><li><a href="https://www.iconfinder.com/">iconfinder</a> -&gt; 同样整套的图标，商业化模式更加明显。</li></ul><p></br></p><h2 id="自测与内测">4、自测与内测</h2><p></br></p><p>在公司我们有测试，有产品，有其他同事一起合作，帮助我们不断完善 App。但是独立开发者怎么能最大程度上提早发现和避免 bug 呢。</p><p></br></p><h5 id="自测">4.1 自测</h5><p>以前交由测试美眉的工作就只能自己来喽～当然除了手动测试之外，我们还可以养一个<a href="https://kemchenj.github.io/2017-03-16/">测试猴子</a>，帮助我们完成测试。</p><p></br></p><h5 id="招募内测">4.2 招募内测</h5><p>既然是独立 App，那么我们就不只是一个开发了，还身兼产品，运营，客服....同样的 App推广与宣传也需要我们自己身体力行了，从朋友、同学、家人开始，在各种社交网络上表示我的独立 App快要完成喽，来帮我点点吧～</p><p>内测用户的作用不只是在测试上，他们对我们更大的帮助是可以提出新的需求和建议，这是能更大程度丰满和完善我们 App的。</p><p></br></p><h5 id="测试包分发">4.3 测试包分发</h5><p>如果比较方便能获取到内测用户设备标示的，可以使用诸如<a href="https://www.pgyer.com/">蒲公英</a>、<a href="https://www.betaqr.com/">fir.im</a>等的内测托管平台。（设备有限制）</p><p>如果内测用户设备标示不太容易获取到的，我们可以使用苹果 iOS8 之后开放的 TestFlight。(多达 1 万个内测名额)</p><p></br></p><h2 id="上架的截图">5、上架的截图</h2><p></br></p><p>为什么要把app上线的截图单拎出来呢？对于我们独立开发者来说，没有那么多推广渠道。我们 App的入口和门面就是我们 Appstore 产品展示页面的截图和描述了。</p><p>如果产品展示页的截图太 low，那么我们就有可能会流失掉这个看到我们 App的用户。</p><p>在公司我们有 UI 有产品帮助我们完成产品展示页面的截图，如果是我们自己怎么获取合适尺寸的截图呢。</p><p></br></p><h5 id="在模拟器上截图">5.1 在模拟器上截图</h5><p>直接使用模拟器截取应用某些页面当然是可以的，但会显的有些粗糙。</p><p></br></p><h5 id="稍微美化的方法">5.2 稍微美化的方法</h5><p>在这里给大家推荐一个免费网站<a href="https://shotbot.io/?utm_source=makeappicon&amp;utm_medium=web&amp;utm_campaign=makeappicon">shotbot</a>，可以上传素材，制作出一整套格式统一的产品展示图。</p><blockquote><p>2022-04-11 备注：我自己后来写了一个 Mac App 专门用于自己制作上架应用的截图</p></blockquote><p></br></p><h2 id="笔者用到的其他资源">6、笔者用到的其他资源</h2><p></br></p><p>当涉及到某些页面交互与设计时，单纯的三方提供的工具也好，人家做好的图标也好，或多或少会有那么些不尽如人意的地方。</p><p><strong>那么，自己来ps吧！</strong></p><p>当然对于大部分开发来说 ps 的功底并不扎实，还有些童鞋压根不会，怎么办呢。</p><p>给大家推荐两个网站：<a href="https://www.uupoop.com/#/">在线ps</a>、<a href="https://pc.meitu.com/">美图秀秀网页版</a></p><p>不用下载繁重的 adobe 软件，使用浏览器就可以轻松的实现一些简单的ps效果，对于我们不怎么会 ps 的童鞋来说，这是个福音。</p><p>除了 ps 相关的，我们 App 的 Icon 也是一个非常困扰的问题。同样给大家推荐一个网站<a href="https://makeappicon.com/">MakeIcon</a>，直接上传 1024x1024 的 logo ，就能自动切出来其他所有尺寸的 Icon了。而且，支持 iOS10 的 iMessage 哦。</p><p></br></p><h2 id="小结">7、小结</h2><p></br></p><p>笔者是在没有 UI 帮助下使用现有的一些资源完成自己的开发，有 UI 的童鞋请出门右转朝南墙不送￣ヘ￣。</p><p>开始有自己做 App 的心思不是一两天了，直到最近把一些需要的东西捋顺了之后才正式开始着手做。当然最重要的是需求确定下来了，顺便学了一下 Swift。</p><p>在开发过程中笔者也有过1周没敲过几句代码的经历，然后发现在做独立 App 的时候需求才是真正能驱动自己坚持下去的动力。需求不在大小，最重要的是能解决问题。</p><p>随着开发的深入，需求也在慢慢的增加，Airdrop 共享，iCloud 集成，ShareExtension 开发等等都未完成，嗯，加油一点点迭代吧～ʘʚʘ（我是不会说出来我做这个 App 的初衷是为了在 pc、手机、pad 上共享我的"收藏.avi"的）</p><p>附上个人app的地址----<a href="https://link.jianshu.com/?t=https%3A%2F%2Fitunes.apple.com%2Fus%2Fapp%2F%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86%E5%99%A8%2Fid1166927583%3Fl%3Dzh%26ls%3D1%26mt%3D8">最简洁的文件操作管理方式-文件管理器</a></p><p></br></p><blockquote><p>2022-04-11 备注：App 已经搜不到了，没有一个好的变现方式，卖给了一家外包公司。卖出之后，也引起了我很多思考，详细的总结可以看下一篇文章。</p></blockquote><p></br></p><p>原文发在<a href="https://www.jianshu.com/p/1cfce03b0ff9">简书</a>上，后来简书被盗，其他文章一直被封，现将所有博客都迁移到 github.io。</p><p></br></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;独立产品研发系列文章第一篇&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;作为一个移动端的开发者，最大的愿望当然是&lt;strong&gt;写一个app坐等收钱了( ͡° ͜ʖ ͡°)&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;那么在独立完成一个app的路上会碰到些什么问题呢？ 最近上线了一个自己的 App，在这里把碰到的问题和自己的解决方案分享给大家。&lt;/p&gt;
&lt;p&gt;&lt;/br&gt;&lt;/p&gt;</summary>
    
    
    
    
    <category term="产品" scheme="https://wkjsos.github.io/tags/%E4%BA%A7%E5%93%81/"/>
    
  </entry>
  
</feed>
