Skip to content

Commit a613420

Browse files
committed
add post
1 parent bd6f7aa commit a613420

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
---
2+
layout: post
3+
title: "[iOS][Objective-C] 동적·정적 라이브러리 혼용 시 발생하는 클래스 중복을 테스트로 검출하기 - objc_getClassList"
4+
tags: [iOS, Swift, XCTest, objc, objc_getClassList]
5+
---
6+
{% include JB/setup %}
7+
8+
라이브러리는 정적 라이브러리와 동적 라이브러리로 나뉘며, 정적 라이브러리는 컴파일 시에 링크되고, 동적 라이브러리는 런타임 시에 링크됩니다.
9+
10+
여러 동적 라이브러리가 정적 라이브러리를 참조할 때, 정적 라이브러리의 코드는 각 동적 라이브러리에 포함되며, 이는 정적 라이브러리의 코드가 중복 포함되는 것을 의미합니다.
11+
12+
하지만 이 중복 포함은 컴파일 시에 문제가 일어나지 않습니다. 그러나 애플리케이션이 실행 될 때, 여러 동적 라이브러리에 있는 정적 라이브러리 코드가 로드 될 때, 중복으로 로드가 되면서 특정 코드의 실행이 예기치 못한 동작이나 크래시를 초래할 수 있습니다.
13+
14+
이는 애플리케이션을 실행하고, 중복된 코드를 호출하면서 발생하는 문제로, 검증하기 쉽지 않습니다. 그래서 콘솔 로그를 통해 확인하는 방법 외에는 다른 방법이 없습니다.
15+
16+
<br/>
17+
<p style="text-align:center;">
18+
<img src="{{ site.production_url }}/image/2025/04/01.png"/>
19+
</p><br/>
20+
21+
위 경고는 정적 라이브러리의 코드가 중복으로 로드되었음을 나타내며, 이를 해결하기 위해서는 정적 라이브러리를 동적 라이브러리로 변경하거나, 정적 라이브러리의 코드를 중복으로 포함하지 않도록 해야 합니다.
22+
23+
하지만 이는 후속 조치일 뿐, 정적 라이브러리의 코드가 중복으로 포함되었는지 확인하는 방법은 아닙니다. 필요시 이를 선제적으로 확인할 수 있는 방법이 필요합니다.
24+
25+
## **objc_getClassList**를 활용하여 클래스 중복을 검출하기
26+
27+
Objective-C의 Run `objc_getClassList` 함수를 사용하여 모든 클래스 목록을 얻고, 이를 Swift의 XCTest를 사용하여 테스트하는 방법을 소개합니다. 이 방법은 동적 라이브러리와 정적 라이브러리를 혼용하여 사용할 때, 클래스 중복을 검출하는데 유용합니다.
28+
29+
다음은 `objc_getClassList` 함수를 이용하여 등록된 클래스 목록을 추출하는 코드입니다.
30+
31+
```swift
32+
// FileName : ClassScanner.swift
33+
34+
import Foundation
35+
import ObjectiveC.runtime
36+
37+
struct ClassScanner {
38+
private var classPtrInfo: (classesPtr: UnsafeMutablePointer<AnyClass>,
39+
numberOfClasses: Int)?
40+
{
41+
let numberOfClasses = Int(objc_getClassList(nil, 0))
42+
guard numberOfClasses > 0 else { return nil }
43+
44+
let classesPtr = UnsafeMutablePointer<AnyClass>.allocate(capacity: numberOfClasses)
45+
let autoreleasingClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(classesPtr)
46+
let count = objc_getClassList(autoreleasingClasses, Int32(numberOfClasses))
47+
assert(numberOfClasses == count)
48+
49+
return (classesPtr, numberOfClasses)
50+
}
51+
52+
func searchClassList() -> [String: UInt] {
53+
guard
54+
let (classesPtr, numberOfClasses) = classPtrInfo
55+
else { return [:] }
56+
57+
defer { classesPtr.deallocate() }
58+
59+
var list = [String: UInt]()
60+
61+
for i in 0 ..< numberOfClasses {
62+
let cls: AnyClass = classesPtr[i]
63+
let clsName = NSStringFromClass(cls)
64+
if let count = list[clsName] {
65+
list[clsName] = count + 1
66+
} else {
67+
list[clsName] = 1
68+
}
69+
}
70+
71+
return list
72+
}
73+
}
74+
```
75+
76+
위 코드에서 동일한 클래스 이름이 나오는 경우, Count를 증가시켜, 중복된 클래스가 존재하는지 검출할 수 있습니다.
77+
78+
## 예제를 통해 클래스 중복을 검출 확인하기
79+
80+
<!--
81+
의존성 그래프 예제 이미지
82+
Application -> FeatureA
83+
Application -> FeatureB
84+
FeatureA -> FeatureC
85+
FeatureB -> FeatureC
86+
-->
87+
88+
위의 의존성 그래프에서 FeatureA, B는 동적 라이브러리, FeatureC는 정적 라이브러리로, FeatureA, B에서 FeatureC를 합니다. FeatureA, B에는 FeatureC 라이브러리 코드가 복사될 것입니다.
89+
90+
FeatureA, B는 FeatureC의 코드를 호출하도록 코드를 작성합니다.
91+
92+
```swift
93+
/// Module : FeatureA
94+
/// FileName : Alpha.swift
95+
import FeatureC
96+
97+
public class Alpha {
98+
public init() {
99+
print(#fileID, Self.self, #function)
100+
_ = Charlie()
101+
_ = Charles()
102+
}
103+
}
104+
105+
/// Module : FeatureB
106+
/// FileName : Beta.swift
107+
import FeatureC
108+
109+
public class Beta {
110+
public init() {
111+
print(#fileID, Self.self, #function)
112+
_ = Charlie()
113+
_ = Charles()
114+
}
115+
}
116+
117+
/// Module : FeatureC
118+
/// FileName : Charlie.swift
119+
public class Charlie {
120+
public init() {
121+
print(#fileID, Self.self, #function)
122+
}
123+
}
124+
125+
/// Module : FeatureC
126+
/// FileName : Charles.swift
127+
public class Charles {
128+
public init() {
129+
print(#fileID, Self.self, #function)
130+
}
131+
}
132+
```
133+
134+
다음으로, Application 타겟을 기반으로 하는 유닛 테스트에서 이전에 작성한 `ClassScanner`를 사용하여 클래스 중복이 있는지 확인합니다.
135+
136+
```swift
137+
import Testing
138+
139+
struct ClassScan {
140+
@Test func searchDuplicateClasses() {
141+
let scanner = ClassScanner()
142+
let allClasses = scanner.searchClassList()
143+
let duplicatedClasses = allClasses
144+
.filter { $0.value > 1 }
145+
146+
#expect(duplicatedClasses.isEmpty)
147+
148+
print("print Duplicated classes")
149+
for (k, v) in duplicatedClasses {
150+
print("\(k): \(v)")
151+
}
152+
}
153+
}
154+
```
155+
156+
해당 테스트는 중복이 없어야 성공하며, 클래스 중복이 발생하는 경우가 테스트가 실패되어 문제를 확인할 수 있도록 하였습니다.
157+
158+
하지만, 해당 테스트를 수행하면 다음과 같이 실패가 발생하는 것을 확인할 수 있습니다.
159+
160+
<br/>
161+
<p style="text-align:center;">
162+
<img src="{{ site.production_url }}/image/2025/04/02.png"/>
163+
</p><br/>
164+
165+
FeatureA, B 에서 FeatureC 코드가 복사되어 문제가 발생했으므로, FeatureC를 동적 라이브러리로 변경하거나, FeatureA, B를 FeatureC와 같은 정적 라이브러리로 변경하면 문제를 해결할 수 있습니다.
166+
167+
## 정리
168+
169+
정적 라이브러리와 동적 라이브러리를 혼용하여 사용할 때, 정적 라이브러리의 코드가 중복으로 포함되는 경우가 발생할 수 있습니다.
170+
이 경우, 런타임에서 중복된 코드가 로드되면서 예기치 못한 동작이나 크래시를 초래할 수 있습니다.
171+
172+
## [예제 코드](https://github.com/minsOne/Experiment-Repo/tree/master/20250413)

image/2025/04/01.png

74 KB
Loading

image/2025/04/02.png

192 KB
Loading

0 commit comments

Comments
 (0)